Thinking in C++ Vol 2 - Practical Programming |
Prev |
Home |
Next |
If you ve looked at the sample code
in this book closely, you ve noticed that certain tokens in the comments
surround the code. These are used by a Python program that Bruce wrote to
extract the code into files and set up makefiles for building the code. For
example, a double-slash followed by a colon at the beginning of a line denotes
the first line of a source file. The rest of the line contains information
describing the file s name and location and whether it should be only compiled
rather than fully built into an executable file. For example, the first line in
the previous program above contains the string C03:IWCompare.cpp,
indicating that the file IWCompare.cpp should be extracted into the
directory C03.
The last line of a source file contains a triple-slash
followed by a colon and a tilde. If the first line has an exclamation point
immediately after the colon, the first and last lines of the source code are
not to be output to the file (this is for data-only files). (If you re
wondering why we re avoiding showing you these tokens, it s because we don t
want to break the code extractor when applied to the text of the book!)
Bruce s Python program does a lot more than just extract
code. If the token {O} follows the file name, its makefile entry will
only be set up to compile the file and not to link it into an executable. (The
Test Framework in Chapter 2 is built this way.) To link such a file with
another source example, the target executable s source file will contain an {L}
directive, as in:
This section will present a program to just extract all the
code so that you can compile and inspect it manually. You can use this program
to extract all the code in this book by saving the document file as a text file (let s call it
TICV2.txt) and by executing something like the following on a shell command
line:
C:> extractCode TICV2.txt /TheCode
This command reads the text file TICV2.txt and writes
all the source code files in subdirectories under the top-level directory /TheCode.
The directory tree will look like the following:
TheCode/
C0B/
C01/
C02/
C03/
C04/
C05/
C06/
C07/
C08/
C09/
C10/
C11/
TestSuite/
The source files containing the examples from each chapter
will be in the corresponding directory.
Here s the program:
//: 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);
} ///:~
First, you ll notice some conditional compilation directives.
The mkdir( ) function, which creates a directory in the file
system, is defined by the POSIX standard
in the header <sys/stat.h>. Unfortunately, many compilers still
use a different header (<direct.h>). The respective signatures for
mkdir( ) also differ: POSIX specifies two arguments, the older
versions just one. For this reason, there is more conditional compilation later
in the program to choose the right call to mkdir( ). We normally
don t use conditional compilation in the examples in this book, but this
particular program is too useful not to put a little extra work into, since you
can use it to extract all the code with it.
The exists( ) function in ExtractCode.cpp
tests whether a directory exists by opening a temporary file in it. If the open
fails, the directory doesn t exist. You remove a file by sending its name as a char*
to std::remove( ).
The main program validates the command-line arguments and
then reads the input file a line at a time, looking for the special source code
delimiters. The Boolean flag inCode indicates that the program is in the
middle of a source file, so lines should be output. The printDelims flag
will be true if the opening token is not followed by an exclamation point;
otherwise the first and last lines are not written. It is important to check
for the closing delimiter first, because the start token is a subset, and
searching for the start token first would return a successful find for both
cases. If we encounter the closing token, we verify that we are in the middle
of processing a source file; otherwise, something is wrong with the way the
delimiters are laid out in the text file. If inCode is true, all is
well, and we (optionally) write the last line and close the file. When the
opening token is found, we parse the directory and file name components and
open the file. The following string-related functions were used in this
example: length( ), append( ), getline( ), find( )
(two versions), find_first_not_of( ), substr( ), find_first_of( ),
c_str( ), and, of course, operator<<( ).
Thinking in C++ Vol 2 - Practical Programming |
Prev |
Home |
Next |