Inhaltsverzeichnis
extract : Herausziehen von Quelltexten aus Textdatei
Dokumentation ist das Rizinusöl des Programmierens:
Manager denken, es sei gut für Programmierer, und
Programmierer hassen es. Tatsächlich wissen Manager:
Es muss ja schließlich gut sein für die Programmierer,
weil die Programmierer es so hassen.— Gerald Weinberg
Problemstellung
nach: [Eckel] Bruce Eckel: Thinking in C++. 2nd edn. Prentice-Hall, (2000). Auch in deutsch: 2. Aufl. Markt & Technik. IBSN 3-827-29567-X.
Bruce Eckel gibt in seinem Buch vollständige Programmquellen an. Er hat beobachtet, dass viele Lehrbücher Quelltexte oder Bruchstücke enthalten, die nicht lauffähig sind, da sie nie getestet wurden oder sich Fehler beim Textsatz eingeschlichen haben. Um seine Bücher vor solchen Fehlern zu bewahren, filtert Eckel alle Quelltexte aus seinem Buch in Word-Format heraus und lässt sie einen automatischen Regressionstest durchlaufen. Der Prozess ist simpel und mit jedem Textverarbeitungsprogramm durchführbar:
Speichere den Buchtext als unformatierten Text (ASCII-Format).
Schicke diesen Text durch einen Quelltext-Extrahierer (extract).
Übersetze die Programme und führe sie aus (make test).
Den ersten Schritt kann man manuell im Textverarbeitungsprogramm erledigen oder ein Hilfsprogramm nutzen (z.B. doc2txt). Der dritte Schritt erfordert ein Makefile für den Programmgenerator make. Den zweiten Schritt soll ein selbst geschriebenes Werkzeug ausführen, das erkennt, welche Abschnitte des Textes Quelltext sind und wohin diese geschrieben werden müssen. Der Extrahierer soll den Text zeilenweise verarbeiten. Die Steuer-Informationen werden manuell in den Text eingebaut. Drei besondere Zeichen am Zeilenanfang steuern die Ausgabe des Extrahierers. Quelltexte beginnen mit
//: dateiname (die Startzeile wird mit in den Quelltext uebernommen)
oder mit
//# dateiname (diese Steuerzeile wird verschluckt)
mit einer Zeichenfolge ohne Leerzeichen als Dateiname. Schon begonnene Quelltexte werden fortgesetzt mit
//> dateiname (ohne Startzeile)
durch Anhängen an die existierende Datei. Das ermöglicht es, abschnittsweise Erläuterungen in den Quelltext einzubauen, eine Art des "Literate Programming". Ein Quelltext kann mit
//~
am Zeilenanfang abgeschlossen werden. Nicht-Quelltextzeilen werden in die Standardausgabe geschrieben.
Lösung
Der Quelltext beginnt mit einem erläuternden Kommentar und der Angabe der benutzten Bibliotheken:
//: extract.cpp : Extrahieren von Quelltexten - R. Richter 2001-08-07 // aus Textdokumenten // //> added 2002-02-24 // //# added 2002-05-11 // // using std::string 2011-04-16 ///////////////////////////////////////////////////////////////////// /* Quelltextabschnitte werden aus ASCII-Textdokumenten herausgezogen. Die Quelltexte beginnen mit //: dateiname (mit Startzeile) oder mit //# dateiname (ohne Startzeile) Quelltextfortsetzungen mit //> dateiname (ohne Startzeile) und enden vor //~ (jeweils am Zeilenanfang). Nicht-Quelltextzeilen werden in die Standardausgabe geschrieben. */ #include <iostream> #include <fstream> #include <sstream>
Der schwierigste Teil ist das Verarbeiten einer (schon gelesenen) Zeile:
void processline(std::string line, std::ofstream& out) { if (line.find("//:") == 0 // Dateianfang (mit Startzeile) || line.find("//#") == 0 // Dateianfang (ohne Startzeile) || line.find("//>") == 0) // Dateifortsetzung (ohne Startzeile) { if (out.is_open()) out.close(); std::istringstream in(line.substr(3)); std::string filename; in >> filename; switch (line[2]) { case ':': out.open(filename.c_str()); break; case '#': out.open(filename.c_str()); return; case '>': out.open(filename.c_str(), std::ios::app); return; } } if (line.find("//~") == 0) // Dateiende { out.close(); return; } if (out.is_open()) out << line << '\n'; else std::cout << line << '\n'; }
Zuerst muss geprüft werden, ob Steuerzeichen am Dateianfang stehen. Falls ein Ausgabedateistrom zu öffnen ist, steht der Dateiname als erstes "Wort" hinter den drei Steuerzeichen. Dazwischenstehende Leerzeichen sind unschädlich. Je nach Art des dritten Zeichens wird die Datei geöffnet. Soll die Steuerzeile nicht in die Datei übernommen werden, ist die Zeilenverarbeitung schon zu Ende. Gleiches gilt beim Schließen der Datei. Ansonsten muss entschieden werden, ob die Zeile in die Datei oder auf die Standardausgabe (Bildschirm) gelenkt werden muss.
Die Eingabe des Programms wird zeilenweise verarbeitet (Hauptschleife). Evtl. muss am Ende der noch offene Datenstrom geschlossen werden, um Datenverlust zu vermeiden:
void extract(std::istream& in) { std::ofstream out; std::string line; while (std::getline(in, line)) processline(line, out); }
Jedes Programm sollte eine kurze Dokumentation mitführen, die bei Fehlbedienung oder bei Abfrage der Hilfe die Verwendung des Programms erläutert. Diese Hilfe wird auch dann aufgerufen, wenn die angegebene Textdatei nicht geöffnet werden kann:
void help() { std::cerr << "Usage:\n" " extract (reads from standard input)\n" " extract inputfilename\n\n" "Synopsis:\n" "Extract all sources from one generator file.\n\n" "Description:\n" "Input introduced by a control line starting with\n" "//: filename ... is written to a new file including start line\n" "//# filename excluding start line\n" "//> filename appends to existing file excluding start line\n\n" "The file output is closed by a input line\n" "//~ \n" "(especially before opening another file) or by end of input.\n\n" "Input lines not written to file are copied to standard output.\n\n" "Rene Richter 2002-05-11\n" << std::endl; } int main(int argc, char* argv[]) { if (argc == 1) { extract(std::cin); } else if (argc == 2 && std::string(argv[1]) != "--help") { std::ifstream in(argv[1]); if (in) { extract(in); } else { std::cerr << "File " << argv[1] << " not found.\n\n"; help(); return 1; } } else { help(); } return 0; } //~
Übersetzen und Ausführen
Speichere den gesamten Text als ASCII-Text (Klartext, Text mit Zeilenenden) unter dem Namen extract.txt. Kopiere diesen Text in eine Datei mit der Endung .cpp und übersetze mit dem C++-Compiler. Dies ist möglich, weil der erläuternde Text stets in C-Kommentare eingeklammert wurde. Danach kann das Programm auf seinen eigenen Quelltext extract.txt angewendet werden, um einen Quelltext ohne die ausführliche Dokumentation zu erzeugen (Bootstrapping-Prozess). Beispiel unter Unix:
cp extract.txt extract.cpp make extract ./extract extract.txt
Für alle folgenden Dokumentationen können ab jetzt die hässlichen Schrägstrich-Stern-Kommentare entfallen und die Quelltext-Überprüfung automatisiert werden.
Diese Dokumentation wurde als Textdatei mit Programmer's File Editor verfasst und mit dokgen als HTML-Seite aufbereitet.