
Estoy usando el controlador del compilador texify MikTex y tengo un documento que usa el paquete multibib. La compilación de pdflatex genera main.aux
(que coincide main.tex
) y own.aux
(generado usando multibib). Sin embargo, Texify solo ejecutará bibtex, main.aux
por lo que algunas de las referencias no se actualizarán correctamente. He estado usando un script de shell simple:
bibtex main
bibtex own
que debe ejecutarse cada vez que cito una nueva referencia. ¿Existe tal vez una manera de hacer que texify ejecute bibtex dos veces o una manera de duplicar lo que está haciendo texify y crear un script que realice todo el proceso de composición tipográfica?
Respuesta1
Busqué en los códigos fuente de texify, mcd.cpp
función de archivo Driver::Ready()
. Busca la aparición de "Rerun to get"
en el .log
archivo y cambios en los archivos auxiliares para ver si debe volver a ejecutar latex. Sería bastante sencillo realizar un script en un entorno Unix, pero como estoy en Windows, preparé lo siguiente:
#include <vector>
#include <string>
#include <algorithm>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <assert.h>
#include <stdarg.h>
#include <ctype.h>
#if defined(_WIN32) || defined(_WIN64)
#define NOMINMAX
#include <windows.h>
#else // _WIN32 || _WIN64
// ...
#endif // _WIN32 || _WIN64
#define SHA1HashSize 20
/*
* This structure will hold context information for the SHA-1
* hashing operation
*/
typedef struct SHA1Context
{
uint32_t Intermediate_Hash[SHA1HashSize/4]; /* Message Digest */
uint32_t Length_Low; /* Message length in bits */
uint32_t Length_High; /* Message length in bits */
/* Index into message block array */
int_least16_t Message_Block_Index;
uint8_t Message_Block[64]; /* 512-bit message blocks */
int Computed; /* Is the digest computed? */
int Corrupted; /* Is the message digest corrupted? */
} SHA1Context;
/*
* Function Prototypes
*/
int SHA1Reset(SHA1Context*);
int SHA1Input(SHA1Context*, const void*, unsigned int);
int SHA1Result(SHA1Context*, uint8_t Message_Digest[SHA1HashSize]);
void Format(std::string &r_s_result, const char *p_s_fmt, ...) // throw(std::bad_alloc)
{
va_list ap;
size_t n_size = strlen(p_s_fmt) + 1;
r_s_result.resize(n_size);
// alloc str as long as formatting str (should be enough in some cases)
for(;;) {
va_start(ap, p_s_fmt);
#if defined(_MSC_VER) && !defined(__MWERKS__)
#if _MSC_VER >= 1400
int n = _vsnprintf_s(&r_s_result[0], n_size * sizeof(char), _TRUNCATE, p_s_fmt, ap);
#else // _MSC_VER >= 1400
int n = _vsnprintf(&r_s_result[0], n_size - 1, p_s_fmt, ap);
// maximum characters to write, not the size of buffer
#endif // _MSC_VER >= 1400
#else // _MSC_VER && !__MWERKS__
int n = vsnprintf(&r_s_result[0], n_size * sizeof(char), p_s_fmt, ap);
#endif // _MSC_VER && !__MWERKS__
va_end(ap);
// try to sprintf
if(n >= 0 && unsigned(n) < n_size) {
#if defined(_MSC_VER) && !defined(__MWERKS__)
r_s_result.resize(n);
#else // _MSC_VER && !__MWERKS__
r_s_result.resize(strlen(r_s_result.data())); // doesn't need c_str(), terminating zero is already there
#endif // _MSC_VER && !__MWERKS__
assert(r_s_result.length() == strlen(r_s_result.data()));
return;
}
// see if we made it
if(n >= 0) // glibc 2.1
n_size = n + 1; // precisely what is needed
else // glibc 2.0, msvc
n_size *= 2;
r_s_result.resize(n_size);
// realloc to try again
}
}
/**
* @brief gets temporary directory the user can write to
* @param[out] r_s_path is the path to the temporary directory, never ends with a slash
* @return Returns true on success, false on failure.
*/
bool Get_TempDirectory(std::string &r_s_path) // throw(std::bad_alloc)
{
#if defined(_WIN32) || defined(_WIN64)
r_s_path.resize(GetTempPath(0, NULL) + 1);
if(!GetTempPathA((DWORD)r_s_path.size(), &r_s_path[0])) // size won't exceed DWORD_MAX, since it's read from DWORD
return false; // something went wrong
assert(strlen(r_s_path.c_str()) > 0 && r_s_path[strlen(r_s_path.c_str()) - 1] == '\\'); // the returned string ends with a backslash
r_s_path.resize(strlen(r_s_path.c_str()) - 1); // cut the backslash here
// get temp path (eg. "c:\\windows\\temp")
#else // _WIN32 || _WIN64
#if 0 // g++ linker warns about using mktemp(), don't want to use that anymore
char p_s_temp[256] = "/tmp/fileXXXXXX";
if(!mktemp(p_s_temp)) // do *not* use mkstemp(), do not want the file to be lying around
return false;
assert(strrchr(p_s_temp + 1, '/')); // should contain slash
*(char*)strrchr(p_s_temp + 1, '/') = 0; // erase the last slash (hence the string does not contain it)
r_s_path = p_s_temp;
// get temp file name and erase the file name to get the path
#else // 0
const char *p_s_temp = getenv("TMPDIR"); // environment variable
// The caller must take care not to modify this string, since that would change the
// environment of the process. Do not free it either.
// (e.g. on cluster computers, temp is often directory specific to the job id, like "/tmp/pbs.132048.dm2")
if(!p_s_temp) {
if(P_tmpdir)
p_s_temp = P_tmpdir; // in stdio.h
else {
p_s_temp = "/tmp"; // just hope it is there
/*TFileInfo t_temp_info(p_s_temp);
if(!t_temp_info.b_exists || !t_temp_info.b_directory)
return false;*/
// don't want to depend on Dir.cpp, some apps already use only the header
}
}
// fallbacks if the environment variable is not set
r_s_path = p_s_temp;
if(!r_s_path.empty() && r_s_path[r_s_path.length() - 1] == '/')
r_s_path.erase(r_s_path.end() - 1);
// get rid of the trailing slash
#endif // 0
#endif // _WIN32 || _WIN64
/*assert(!b_EndsWithSlash(r_s_path));
assert(b_Is_Normalized(r_s_path));
assert(b_Is_Absolute(r_s_path));*/
// make sure there is no slash at the end, and that the path is normalized
return true;
}
bool Get_TempFileName(std::string &r_s_temp_file_name, const char *p_s_app_id) // throw(std::bad_alloc)
{
assert(p_s_app_id && strlen(p_s_app_id));
// may not be emtpy
#if defined(_WIN32) || defined(_WIN64)
std::string s_temp_path;
s_temp_path.resize(GetTempPath(0, NULL) + 1);
if(!GetTempPathA((DWORD)s_temp_path.size(), &s_temp_path[0])) // size won't exceed DWORD_MAX, since it's read from DWORD
return false; // something went wrong
s_temp_path.resize(strlen(s_temp_path.c_str()));
// get temp path (eg. "c:\windows\temp")
r_s_temp_file_name.resize(s_temp_path.length() + 16 + strlen(p_s_app_id));
if(!GetTempFileNameA(s_temp_path.c_str(), p_s_app_id, 0, &r_s_temp_file_name[0]))
return false; // something went wrong
r_s_temp_file_name.resize(strlen(r_s_temp_file_name.c_str()));
// get temp filename
#else // _WIN32 || _WIN64
std::string s_tmp;
if(!Get_TempDirectory(s_tmp)) // use "proper" temp (e.g. on cluster computers, temp is often directory specific to the job id, like "/tmp/pbs.132048.dm2")
return false;
Format(r_s_temp_file_name, "%s/%sXXXXXX", s_tmp.c_str(), p_s_app_id); // had 8 X's // 2012-07-17 change // t_odo - carry this change to documentation
// 2013-11-13 changed template to not include the /tmp folder as that might not be the location of tmp
// make template
int n_file;
if((n_file = mkstemp((char*)r_s_temp_file_name.c_str())) < 0)
return false;
close(n_file);
// create temp file
#endif // _WIN32 || _WIN64
return true;
}
bool ReadFile(std::string &r_s_output, const char *p_s_filename) // throw(std::bad_alloc)
{
FILE *p_fr;
#if defined(_MSC_VER) && !defined(__MWERKS__) && _MSC_VER >= 1400
if(fopen_s(&p_fr, p_s_filename, "rb"))
#else //_MSC_VER && !__MWERKS__ && _MSC_VER >= 1400
if(!(p_fr = fopen(p_s_filename, "rb")))
#endif //_MSC_VER && !__MWERKS__ && _MSC_VER >= 1400
return false;
unsigned int n_length;
if(fseek(p_fr, 0, SEEK_END) ||
(n_length = ftell(p_fr)) < 0 ||
fseek(p_fr, 0, SEEK_SET)) {
fclose(p_fr);
return false;
}
try {
r_s_output.resize(n_length);
} catch(std::bad_alloc &r_exc) {
fclose(p_fr);
throw r_exc; // rethrow
}
if(fread(&r_s_output[0], sizeof(char), n_length, p_fr) != n_length) {
fclose(p_fr);
return false;
}
fclose(p_fr);
return true;
}
void TrimSpace(std::string &r_s_string)
{
size_t b = 0, e = r_s_string.length();
while(e > 0 && isspace(uint8_t(r_s_string[e - 1])))
-- e;
while(b < e && isspace(uint8_t(r_s_string[b])))
++ b;
r_s_string.erase(e);
r_s_string.erase(0, b);
}
void Split(std::vector<std::string> &r_s_dest, const std::string &r_s_string,
const char *p_s_separator, int n_thresh)
{
r_s_dest.clear();
const size_t n_separator_skip = strlen(p_s_separator);
size_t n_pos = 0;
size_t n_next_pos;
while((n_next_pos = r_s_string.find(p_s_separator, n_pos)) != std::string::npos) {
if(n_thresh < 0 || n_next_pos - n_pos > unsigned(n_thresh)) {
r_s_dest.resize(r_s_dest.size() + 1);
std::string &r_s_new = r_s_dest.back();
r_s_new.insert(r_s_new.begin(), r_s_string.begin() + n_pos,
r_s_string.begin() + n_next_pos);
}
n_pos = n_next_pos + n_separator_skip; // skip over the separator
}
if(n_thresh < 0 || r_s_string.length() - n_pos > unsigned(n_thresh)) {
r_s_dest.resize(r_s_dest.size() + 1);
std::string &r_s_new = r_s_dest.back();
r_s_new.insert(r_s_new.begin(), r_s_string.begin() + n_pos,
r_s_string.end());
}
}
int main(int n_arg_num, const char **p_arg_list)
{
try {
if(n_arg_num != 2) {
fprintf(stderr, "error: use IsTexReady <jobname>\n");
return -1;
}
const char *p_s_jobname = p_arg_list[1];
if(!strcmp(p_s_jobname, "-h") || !strcmp(p_s_jobname, "--help") ||
!strcmp(p_s_jobname, "/?") || !strcmp(p_s_jobname, "--usage")) {
printf("use: IsTexReady <jobname>\n\n"
"It will return 0 in case there is no need to re-run tex,\n"
"1 in case tex should be run once more or -1 on error.\n\n"
"Run in the same folder where <jobname>.log can be found.\n");
return 0; // not sure
}
// get jobname
std::string s_logfile_name = std::string(p_s_jobname) + ".log";
bool b_rerun = false;
std::string s_logfile;
if(!ReadFile(s_logfile, s_logfile_name.c_str())) {
fprintf(stderr, "error: while reading \'%s\'\n", s_logfile_name.c_str());
b_rerun = true; // should re-run as the log file might be simply missing
}
// read logfile
if(s_logfile.find("Rerun to get") != std::string::npos) {
printf("IsTexReady: should rerun latex as per the logfile\n");
b_rerun = true;
}
// have "Rerun to get" something, should run again
std::vector<std::string> aux_list;
{
std::string s_aux_list_name;
if(!Get_TempFileName(s_aux_list_name, "trdy"))
throw std::runtime_error("Get_TempFileName() failed");
// get a temp file
#if defined(_WIN32) || defined(_WIN64)
system(("dir /B /S *.aux *.toc *.lof *.lot *.loa *.lol *.idx > " + s_aux_list_name).c_str());
#else // _WIN32 || _WIN64
system(("find . -name \'*.aux\' -or -name \'*.toc\' -or -name \'*.lof\' "
"-or -name *.lot -or -name *.loa -or -name \'*.lol\' -or -name \'*.idx\' > " + s_aux_list_name).c_str());
#endif // _WIN32 || _WIN64
// list the files in it (aux, toc, list of figures / tables / algorithms / listings, idx)
std::string s_aux_list;
if(!ReadFile(s_aux_list, s_aux_list_name.c_str()))
throw std::runtime_error("getting a list of aux files failed");
// read it back
Split(aux_list, s_aux_list, "\n", 0); // split the list by newlines
std::for_each(aux_list.begin(), aux_list.end(), TrimSpace); // remove leading or trailing spaces
std::sort(aux_list.begin(), aux_list.end()); // sort the filenames
aux_list.erase(std::unique(aux_list.begin(), aux_list.end()), aux_list.end()); // remove duplicates (empty entries, whitespace, ...)
std::vector<std::string>::iterator p_empty_it;
if((p_empty_it = std::find(aux_list.begin(), aux_list.end(), std::string())) != aux_list.end())
aux_list.erase(p_empty_it); // erase any empty entries
// split to the individual entries
remove(s_aux_list_name.c_str());
// remove the temp file
}
// get a list of all the aux files
std::string s_hash_string;
{
SHA1Context sha1;
SHA1Reset(&sha1);
for(size_t i = 0, n = aux_list.size(); i < n; ++ i) {
SHA1Input(&sha1, aux_list[i].c_str(), aux_list[i].size() * sizeof(char));
// hash the file name
std::string s_aux_file;
if(!ReadFile(s_aux_file, aux_list[i].c_str()))
throw std::runtime_error("failed to read one or more aux file(s)");
SHA1Input(&sha1, s_aux_file.c_str(), s_aux_file.size() * sizeof(char));
// hash the file contents
}
uint8_t Message_Digest[SHA1HashSize];
SHA1Result(&sha1, Message_Digest);
for(int i = 0; i < SHA1HashSize; ++ i) {
std::string s_hash_digit;
Format(s_hash_digit, "%02x", Message_Digest[i]);
s_hash_string += s_hash_digit;
}
}
// get SHA1 of the concatenated files
std::string s_hashes_name = std::string(p_s_jobname) + "_aux.sha1";
std::string s_hashes;
if(!b_rerun && !ReadFile(s_hashes, s_hashes_name.c_str())) {
printf("IsTexReady: should rerun latex as per missing aux file hashes\n");
b_rerun = true;
}
// read aux file hashes. if not found, should run again
TrimSpace(s_hashes);
if(!b_rerun && s_hashes != s_hash_string) {
printf("IsTexReady: should rerun latex as the aux files have changed\n");
b_rerun = true;
}
if(b_rerun) {
FILE *p_fw;
#if defined(_MSC_VER) && !defined(__MWERKS__) && _MSC_VER >= 1400
if(fopen_s(&p_fw, s_hashes_name.c_str(), "w"))
#else //_MSC_VER && !__MWERKS__ && _MSC_VER >= 1400
if(!(p_fw = fopen(s_hashes_name.c_str(), "w")))
#endif //_MSC_VER && !__MWERKS__ && _MSC_VER >= 1400
throw std::runtime_error("failed to write the digest of the aux files");
fprintf(p_fw, "%s\n", s_hash_string.c_str());
fclose(p_fw);
// save a new hashes file
return 1;
// signal to rerun
}
// should we rerun?
printf("IsTexReady: no need to run latex again\n");
} catch(std::bad_alloc&) {
fprintf(stderr, "error: uncaught std::bad_alloc\n");
return -1;
} catch(std::runtime_error &r_exc) {
fprintf(stderr, "error: uncaught std::runtime_error: \'%s\'\n", r_exc.what());
return -1;
} catch(std::exception&) {
fprintf(stderr, "error: uncaught std::exception\n");
return -1;
}
return 0; // noneed to run again, all is ready.
}
// ------------------------------------------------------------------------------------------------
// SHA-1 due to https://tools.ietf.org/html/rfc3174
#ifndef _SHA_enum_
#define _SHA_enum_
enum
{
shaSuccess = 0,
shaNull, /* Null pointer parameter */
shaInputTooLong, /* input data too long */
shaStateError /* called Input after Result */
};
#endif
TODO - paste sha1.c from https://tools.ietf.org/html/rfc3174 in here
Simplemente compílelo como una aplicación de consola IsTexReady.exe
y luego el script se puede modificar para:
@echo off
IsTexReady.exe main > nul
rem run once just to hash the aux files, ignore the results
set N=0
:again
bibtex main
bibtex own
pdflatex -synctex=1 main
rem run tex (use -synctex=1 to get clickable cross-referencing between tex and pdf)
set /A N=%N%+1
IsTexReady.exe main
rem run again, see if log prompts to re-run or whether the aux files changed since the beginning
if %ERRORLEVEL% == 1 (
if %N% == 5 (
echo "maximum number of iterations reached"
exit
)
rem see if the maximum number of iterations would be exceeded
goto again
)
rem see if we need to loop
echo "done in %N% iterations"
Luego vuelve a pasar el látex tantas veces como sea necesario. Guarda un hash SHA1 de los archivos auxiliares para detectar cambios en lugar de hacer una copia de los archivos auxiliares como lo hace texify. Tenga en cuenta que texify también tiene una limitación en el número máximo de bucles (predeterminado 5
).
Los códigos fuente también deberían funcionar en Unix (compilarlos g++ --ansi
y tal vez solucionar algunos problemas ya que no los he probado). Pero , de nuevo, podrías hacer un script similar en bash puro usando grep
, y .ls
for
md5sum
Respuesta2
La forma correcta y, en última instancia, portátil sería utilizar elvolver a ejecutar la comprobación del archivopaquete. Entonces todo lo que necesitas hacer es:
bibtex main
bibtex own
#makeindex main # or not
pdflatex -synctex=1 main
y luego verifique main.log
si hay apariciones de Rerun to get
o Rerun LaTeX
. Si lo encuentra, repita el script nuevamente.