Si ha observado atentamente los códigos de ejemplo de este libro, habrá observado que ciertos elementos en los comentarios envuelven el código. Son usados por un programa en Python que escribió Bruce para extraer el código en ficheros y configurar makefiles para construir el código. Por ejemplo, una doble barra segida de dos puntos en el comienzo de una línea denota la primera línea de un fichero de código . El resto de la línea contiene información describiendo el nombre del fichero y su locaización y cuando deberia ser solo compilado en vez constituir un fichero ejecutable. Por ejemplo, la primera línea del programa anterior contiene la cadena C03:IWCompare.cpp, indicando que el fichero IWCompare.cpp deberia ser extraido en el directorio C03.
La última línea del fichero fuente contiene una triple barra seguida de dos puntos y un signo "~". Es la primera línea tiene una exclamación inmediatamente después de los dos puntos, la primera y la última línea del código fuente no son para ser extraídas en un fichero (solo es para ficheros solo de datos). (Si se está preguntando por que evitamos mostrar estos elementos, es por que no queremos romper el extractor de código cuando lo aplicamos al texto del libro!).
El programa en Python de Bruce hace muchas más cosas que simplemente extraer el código. Si el elemento "{O}" sigue al nombre del fichero, su entrada en el makefile solo será configurada para compilar y no para enlazarla en un ejecutable. (El Test Framework en el Capítulo 2 está contruida de esta manera). Para enlazar un fichero con otro fuente de ejemplo, el fichero fuente del ejecutable objetivo contendrá una directiva "{L}", como aquí:
//{L} ../TestSuite/Test
Esta sección le presentará un programa para extraer todo el
código para que pueda compilarlo e inspeccionarlo
manualmente. Puede usar este programa para extraer todo el
codigo de este libro salvando el fichero como un fichero de
texto[9] (llamémosle TICV2.txt)y
ejecutando algo como la siguiente línea de comandos: C:>
extractCode TICV2.txt /TheCode
Este comando lee el fichero de texto TICV2.txt y escribe todos los archivos de código fuente en subdirectorios bajo el definido /TheCode. El arbol de directorios se mostrará como sigue:
TheCode/
C0B/
C01/
C02/
C03/
C04/
C05/
C06/
C07/
C08/
C09/
C10/
C11/
TestSuite/
Los ficheros de código fuente que contienen los ejemplos de cada capítulo estarán en el correspondiente directorio.
Aquí está el programa:
//: C03:ExtractCode.cpp {-edg} {RunByHand} // Extracts code from text. #include <cassert> #include <cstddef> #include <cstdio> #include <cstdlib> #include <fstream> #include <iostream> #include <string> using namespace std; // Legacy non-standard C header for mkdir() #if defined(__GNUC__) || defined(__MWERKS__) #include <sys/stat.h> #elif defined(__BORLANDC__) || defined(_MSC_VER) \ || defined(__DMC__) #include <direct.h> #else #error Compiler not supported #endif // Check to see if directory exists // by attempting to open a new file // for output within it. bool exists(string fname) { size_t len = fname.length(); if(fname[len-1] != '/' && fname[len-1] != '\\') fname.append("/"); fname.append("000.tmp"); ofstream outf(fname.c_str()); bool existFlag = outf; if(outf) { outf.close(); remove(fname.c_str()); } return existFlag; } int main(int argc, char* argv[]) { // See if input file name provided if(argc == 1) { cerr << "usage: extractCode file [dir]" << endl; exit(EXIT_FAILURE); } // See if input file exists ifstream inf(argv[1]); if(!inf) { cerr << "error opening file: " << argv[1] << endl; exit(EXIT_FAILURE); } // Check for optional output directory string root("./"); // current is default if(argc == 3) { // See if output directory exists root = argv[2]; if(!exists(root)) { cerr << "no such directory: " << root << endl; exit(EXIT_FAILURE); } size_t rootLen = root.length(); if(root[rootLen-1] != '/' && root[rootLen-1] != '\\') root.append("/"); } // Read input file line by line // checking for code delimiters string line; bool inCode = false; bool printDelims = true; ofstream outf; while(getline(inf, line)) { size_t findDelim = line.find("//" "/:~"); if(findDelim != string::npos) { // Output last line and close file if(!inCode) { cerr << "Lines out of order" << endl; exit(EXIT_FAILURE); } assert(outf); if(printDelims) outf << line << endl; outf.close(); inCode = false; printDelims = true; } else { findDelim = line.find("//" ":"); if(findDelim == 0) { // Check for '!' directive if(line[3] == '!') { printDelims = false; ++findDelim; // To skip '!' for next search } // Extract subdirectory name, if any size_t startOfSubdir = line.find_first_not_of(" \t", findDelim+3); findDelim = line.find(':', startOfSubdir); if(findDelim == string::npos) { cerr << "missing filename information\n" << endl; exit(EXIT_FAILURE); } string subdir; if(findDelim > startOfSubdir) subdir = line.substr(startOfSubdir, findDelim - startOfSubdir); // Extract file name (better be one!) size_t startOfFile = findDelim + 1; size_t endOfFile = line.find_first_of(" \t", startOfFile); if(endOfFile == startOfFile) { cerr << "missing filename" << endl; exit(EXIT_FAILURE); } // We have all the pieces; build fullPath name string fullPath(root); if(subdir.length() > 0) fullPath.append(subdir).append("/"); assert(fullPath[fullPath.length()-1] == '/'); if(!exists(fullPath)) #if defined(__GNUC__) || defined(__MWERKS__) mkdir(fullPath.c_str(), 0); // Create subdir #else mkdir(fullPath.c_str()); // Create subdir #endif fullPath.append(line.substr(startOfFile, endOfFile - startOfFile)); outf.open(fullPath.c_str()); if(!outf) { cerr << "error opening " << fullPath << " for output" << endl; exit(EXIT_FAILURE); } inCode = true; cout << "Processing " << fullPath << endl; if(printDelims) outf << line << endl; } else if(inCode) { assert(outf); outf << line << endl; // Output middle code line } } } exit(EXIT_SUCCESS); } ///:~
Listado 4.33. C03/ExtractCode.cpp
Primero observará algunas directivas de compilación
condicionales. La función mkdir()
, que crea
un directorio en el sistema de ficheros, se define por el
estándar POSIX[10] en la cabecera (<direct.h>). La respectiva
signatura de mkdir()
también difiere: POSIX
especifica dos argumentos, las viejas versiones sólo uno. Por
esta razón, existe más de una directiva de compilación
condicional después en el programa para elegir la llamada
correcta a mkdir()
. Normalmente no usamos
compilaciones condicionales en los ejemplos de este libro, pero
en este programa en particular es demasiado útil para no poner
un poco de trabajo extra dentro, ya que puede usarse para
extraer todo el código con él.
La función exists()
en
ExtractCode.cpp prueba que un directorio
existe abriendo un fiechero temporal en él. Si la obertura
falla, el directorio no existe. Borre el fichero enviando su
nombre como unchar*
a
std::remove()
.
El programa principal valida los argumentos de la línea de
comandos y después lee el fichero de entrada línea por línea,
mirando por los delimitadores especiales de código fuente. La
bandera booleana inCode
indica que el
programa esta en el medio de un fichero fuente, así que las
lineas deben ser extraídas. La bandera
printDelims
será verdadero si el elemento de
obertura no está seguido de un signo de exclamanción; si no la
primera y la última línea no son escritas. Es importante
comprobar el último delimitador primero, por que el elemnto
inicial es un subconjuntom y buscando por el elemento inicial
debería retornar cierto en ambos casos. Si encontramos el
elemento final, verificamos que estamos en el medio del
procesamiento de un fichero fuente; sino, algo va mal con la
manera en que los delimitadores han sido colocados en el fichero
de texto. Si inCode
es verdadero, todo está
bien, y escribiremos (opcionalmente) la última linea y
cerraremos el fichero. Cuando el elemento de obertura se
encuentra, procesamos el directorio y el nombre del fichero y
abrimos el fichero. Las siguientes funciones relacionadas con
string
fueron usadas en este ejemplo:
length( )
, append( )
,
getline( )
, find( )
(dos versiones), find_first_not_of( )
,
substr( )
,find_first_of(
)
, c_str( )
, y, por supuesto,
operator<<( )
[9] Esté alerta porque algunas versiones de Microsoft Word que substituyen erroneamente los caracteres con comilla simple con un carácter ASCII cuando salva el documento como texto, causan un error de compilación. No tenemos idea de porqué pasa esto. Simplemente reemplace el carácter manualmente con un apóstrofe.
[10] POSIX, un estándar IEEE, es un "Portable Operating System Interface" (Interficie de Sistema Operativo Portable) y es una generalización de muchas de las llamadas a sistema de bajo nivel encontradas en los sistemas UNIX.