#include #include #include #include "dlib/cpp_pretty_printer.h" #include "dlib/cmd_line_parser.h" #include "dlib/queue.h" #include "dlib/misc_api.h" #include "dlib/dir_nav.h" #include "to_xml.h" const char* VERSION = "3.5"; using namespace std; using namespace dlib; typedef cpp_pretty_printer::kernel_1a cprinter; typedef cpp_pretty_printer::kernel_2a bprinter; typedef dlib::map::kernel_1a map_string_to_string; typedef dlib::set::kernel_1a set_of_string; typedef queue::kernel_1a queue_of_files; typedef queue::kernel_1a queue_of_dirs; void print_manual ( ); /*! ensures - prints detailed information about this program. !*/ void htmlify ( const map_string_to_string& file_map, bool colored, bool number_lines, const std::string& title ); /*! ensures - for all valid out_file: - the file out_file is the html transformed version of file_map[out_file] - if (number_lines) then - the html version will have numbered lines - if (colored) then - the html version will have colors - title will be the first part of the HTML title in the output file !*/ void htmlify ( istream& in, ostream& out, const std::string& title, bool colored, bool number_lines ); /*! ensures - transforms in into html with the given title and writes it to out. - if (number_lines) then - the html version of in will have numbered lines - if (colored) then - the html version of in will have colors !*/ void add_files ( const directory& dir, const std::string& out_dir, map_string_to_string& file_map, bool flatten, bool cat, const set_of_string& filter, unsigned long search_depth, unsigned long cur_depth = 0 ); /*! ensures - searches the directory dir for files matching the filter and adds them to the file_map. only looks search_depth deep. !*/ int main(int argc, char** argv) { if (argc == 1) { cout << "\nTry the -h option for more information.\n"; return 0; } string file; try { command_line_parser parser; parser.add_option("b","Pretty print in black and white. The default is to pretty print in color."); parser.add_option("n","Number lines."); parser.add_option("h","Displays this information."); parser.add_option("index","Create an index."); parser.add_option("v","Display version."); parser.add_option("man","Display the manual."); parser.add_option("f","Specifies a list of file extensions to process when using the -i option. The list elements should be separated by spaces. The default is \"cpp h c\".",1); parser.add_option("i","Specifies an input directory.",1); parser.add_option("cat","Puts all the output into a single html file with the given name.",1); parser.add_option("depth","Specifies how many directories deep to search when using the i option. The default value is 30.",1); parser.add_option("o","This option causes all the output files to be created inside the given directory. If this option is not given then all output goes to the current working directory.",1); parser.add_option("flatten","When this option is given it prevents the input directory structure from being replicated."); parser.add_option("title","This option specifies a string which is prepended onto the title of the generated HTML",1); parser.add_option("to-xml","Instead of generating HTML output, create a single output file called output.xml that contains " "a simple XML database which lists all documented classes and functions."); parser.add_option("t", "When creating XML output, replace tabs in comments with spaces.", 1); parser.parse(argc,argv); parser.check_incompatible_options("cat","o"); parser.check_incompatible_options("cat","flatten"); parser.check_incompatible_options("cat","index"); parser.check_option_arg_type("depth"); parser.check_option_arg_range("t", 1, 100); parser.check_incompatible_options("to-xml", "b"); parser.check_incompatible_options("to-xml", "n"); parser.check_incompatible_options("to-xml", "index"); parser.check_incompatible_options("to-xml", "cat"); parser.check_incompatible_options("to-xml", "o"); parser.check_incompatible_options("to-xml", "flatten"); parser.check_incompatible_options("to-xml", "title"); const char* singles[] = {"b","n","h","index","v","man","f","cat","depth","o","flatten","title","to-xml", "t"}; parser.check_one_time_options(singles); const char* i_sub_ops[] = {"f","depth","flatten"}; parser.check_sub_options("i",i_sub_ops); const char* to_xml_sub_ops[] = {"t"}; parser.check_sub_options("to-xml",to_xml_sub_ops); const command_line_parser::option_type& b_opt = parser.option("b"); const command_line_parser::option_type& n_opt = parser.option("n"); const command_line_parser::option_type& h_opt = parser.option("h"); const command_line_parser::option_type& index_opt = parser.option("index"); const command_line_parser::option_type& v_opt = parser.option("v"); const command_line_parser::option_type& o_opt = parser.option("o"); const command_line_parser::option_type& man_opt = parser.option("man"); const command_line_parser::option_type& f_opt = parser.option("f"); const command_line_parser::option_type& cat_opt = parser.option("cat"); const command_line_parser::option_type& i_opt = parser.option("i"); const command_line_parser::option_type& flatten_opt = parser.option("flatten"); const command_line_parser::option_type& depth_opt = parser.option("depth"); const command_line_parser::option_type& title_opt = parser.option("title"); const command_line_parser::option_type& to_xml_opt = parser.option("to-xml"); string filter = "cpp h c"; bool cat = false; bool color = true; bool number = false; unsigned long search_depth = 30; string out_dir; // the name of the output directory if the o option is given. "" otherwise string full_out_dir; // the full name of the output directory if the o option is given. "" otherwise const char separator = directory::get_separator(); bool no_run = false; if (v_opt) { cout << "Htmlify v" << VERSION << "\nCompiled: " << __TIME__ << " " << __DATE__ << "\nWritten by Davis King\n"; cout << "Check for updates at http://dlib.net\n\n"; no_run = true; } if (h_opt) { cout << "This program pretty prints C or C++ source code to HTML.\n"; cout << "Usage: htmlify [options] [file]...\n"; parser.print_options(); cout << "\n\n"; no_run = true; } if (man_opt) { print_manual(); no_run = true; } if (no_run) return 0; if (f_opt) { filter = f_opt.argument(); } if (cat_opt) { cat = true; } if (depth_opt) { search_depth = string_cast(depth_opt.argument()); } if (to_xml_opt) { unsigned long expand_tabs = 0; if (parser.option("t")) expand_tabs = string_cast(parser.option("t").argument()); generate_xml_markup(parser, filter, search_depth, expand_tabs); return 0; } if (o_opt) { // make sure this directory exists out_dir = o_opt.argument(); create_directory(out_dir); directory dir(out_dir); full_out_dir = dir.full_name(); // make sure the last character of out_dir is a separator if (out_dir[out_dir.size()-1] != separator) out_dir += separator; if (full_out_dir[out_dir.size()-1] != separator) full_out_dir += separator; } if (b_opt) color = false; if (n_opt) number = true; // this is a map of output file names to input file names. map_string_to_string file_map; // add all the files that are just given on the command line to the // file_map. for (unsigned long i = 0; i < parser.number_of_arguments(); ++i) { string in_file, out_file; in_file = parser[i]; string::size_type pos = in_file.find_last_of(separator); if (pos != string::npos) { out_file = out_dir + in_file.substr(pos+1) + ".html"; } else { out_file = out_dir + in_file + ".html"; } if (file_map.is_in_domain(out_file)) { if (file_map[out_file] != in_file) { // there is a file name colision in the output folder. definitely a bad thing cout << "Error: Two of the input files have the same name and would overwrite each\n"; cout << "other. They are " << in_file << " and " << file_map[out_file] << ".\n" << endl; return 1; } else { continue; } } file_map.add(out_file,in_file); } // pick out the filter strings set_of_string sfilter; istringstream sin(filter); string temp; sin >> temp; while (sin) { if (sfilter.is_member(temp) == false) sfilter.add(temp); sin >> temp; } // now get all the files given by the i options for (unsigned long i = 0; i < i_opt.count(); ++i) { directory dir(i_opt.argument(0,i)); add_files(dir, out_dir, file_map, flatten_opt, cat, sfilter, search_depth); } if (cat) { file_map.reset(); ofstream fout(cat_opt.argument().c_str()); if (!fout) { throw error("Error: unable to open file " + cat_opt.argument()); } fout << "" << cat_opt.argument() << ""; const char separator = directory::get_separator(); string file; while (file_map.move_next()) { ifstream fin(file_map.element().value().c_str()); if (!fin) { throw error("Error: unable to open file " + file_map.element().value()); } string::size_type pos = file_map.element().value().find_last_of(separator); if (pos != string::npos) file = file_map.element().value().substr(pos+1); else file = file_map.element().value(); std::string title; if (title_opt) title = title_opt.argument(); htmlify(fin, fout, title + file, color, number); } } else { std::string title; if (title_opt) title = title_opt.argument(); htmlify(file_map,color,number,title); } if (index_opt) { ofstream index((out_dir + "index.html").c_str()); ofstream menu((out_dir + "menu.html").c_str()); if (!index) { cout << "Error: unable to create " << out_dir << "index.html\n\n"; return 0; } if (!menu) { cout << "Error: unable to create " << out_dir << "menu.html\n\n"; return 0; } index << ""; index << ""; index << ""; menu << "
"; file_map.reset(); while (file_map.move_next()) { if (o_opt) { file = file_map.element().key(); if (file.find(full_out_dir) != string::npos) file = file.substr(full_out_dir.size()); else file = file.substr(out_dir.size()); } else { file = file_map.element().key(); } // strip the .html from file file = file.substr(0,file.size()-5); menu << "" << file << "
"; } menu << ""; } } catch (ios_base::failure&) { cout << "ERROR: unable to write to " << file << endl; cout << endl; } catch (exception& e) { cout << e.what() << endl; cout << "\nTry the -h option for more information.\n"; cout << endl; } } // ------------------------------------------------------------------------------------------------- void htmlify ( istream& in, ostream& out, const std::string& title, bool colored, bool number_lines ) { if (colored) { static cprinter cp; if (number_lines) { cp.print_and_number(in,out,title); } else { cp.print(in,out,title); } } else { static bprinter bp; if (number_lines) { bp.print_and_number(in,out,title); } else { bp.print(in,out,title); } } } // ------------------------------------------------------------------------------------------------- void htmlify ( const map_string_to_string& file_map, bool colored, bool number_lines, const std::string& title ) { file_map.reset(); const char separator = directory::get_separator(); string file; while (file_map.move_next()) { ifstream fin(file_map.element().value().c_str()); if (!fin) { throw error("Error: unable to open file " + file_map.element().value() ); } ofstream fout(file_map.element().key().c_str()); if (!fout) { throw error("Error: unable to open file " + file_map.element().key()); } string::size_type pos = file_map.element().value().find_last_of(separator); if (pos != string::npos) file = file_map.element().value().substr(pos+1); else file = file_map.element().value(); htmlify(fin, fout,title + file, colored, number_lines); } } // ------------------------------------------------------------------------------------------------- void add_files ( const directory& dir, const std::string& out_dir, map_string_to_string& file_map, bool flatten, bool cat, const set_of_string& filter, unsigned long search_depth, unsigned long cur_depth ) { const char separator = directory::get_separator(); queue_of_files files; queue_of_dirs dirs; dir.get_files(files); // look though all the files in the current directory and add the // ones that match the filter to file_map string name, ext, in_file, out_file; files.reset(); while (files.move_next()) { name = files.element().name(); string::size_type pos = name.find_last_of('.'); if (pos != string::npos && filter.is_member(name.substr(pos+1))) { in_file = files.element().full_name(); if (flatten) { pos = in_file.find_last_of(separator); } else { // figure out how much of the file's path we need to keep // for the output file name pos = in_file.size(); for (unsigned long i = 0; i <= cur_depth && pos != string::npos; ++i) { pos = in_file.find_last_of(separator,pos-1); } } if (pos != string::npos) { out_file = out_dir + in_file.substr(pos+1) + ".html"; } else { out_file = out_dir + in_file + ".html"; } if (file_map.is_in_domain(out_file)) { if (file_map[out_file] != in_file) { // there is a file name colision in the output folder. definitely a bad thing ostringstream sout; sout << "Error: Two of the input files have the same name and would overwrite each\n"; sout << "other. They are " << in_file << " and " << file_map[out_file] << "."; throw error(sout.str()); } else { continue; } } file_map.add(out_file,in_file); } } // while (files.move_next()) files.clear(); if (search_depth > cur_depth) { // search all the sub directories dir.get_dirs(dirs); dirs.reset(); while (dirs.move_next()) { if (!flatten && !cat) { string d = dirs.element().full_name(); // figure out how much of the directorie's path we need to keep. string::size_type pos = d.size(); for (unsigned long i = 0; i <= cur_depth && pos != string::npos; ++i) { pos = d.find_last_of(separator,pos-1); } // make sure this directory exists in the output directory tree d = d.substr(pos+1); create_directory(out_dir + separator + d); } add_files(dirs.element(), out_dir, file_map, flatten, cat, filter, search_depth, cur_depth+1); } } } // ------------------------------------------------------------------------------------------------- void print_manual ( ) { ostringstream sout; const unsigned long indent = 2; cout << "\n"; sout << "Htmlify v" << VERSION; cout << wrap_string(sout.str(),indent,indent); sout.str(""); sout << "This is a fairly simple program that takes source files and pretty prints them " << "in HTML. There are two pretty printing styles, black and white or color. The " << "black and white style is meant to look nice when printed out on paper. It looks " << "a little funny on the screen but on paper it is pretty nice. The color version " << "on the other hand has nonprintable HTML elements such as links and anchors."; cout << "\n\n" << wrap_string(sout.str(),indent,indent); sout.str(""); sout << "The colored style puts HTML anchors on class and function names. This means " << "you can link directly to the part of the code that contains these names. For example, " << "if you had a source file bar.cpp with a function called foo in it you could link " << "directly to the function with a link address of \"bar.cpp.html#foo\". It is also " << "possible to instruct Htmlify to place HTML anchors at arbitrary spots by using a " << "special comment of the form /*!A anchor_name */. You can put other things in the " << "comment but the important bit is to have it begin with /*!A then some white space " << "then the anchor name you want then more white space and then you can add whatever " << "you like. You would then refer to this anchor with a link address of " << "\"file.html#anchor_name\"."; cout << "\n\n" << wrap_string(sout.str(),indent,indent); sout.str(""); sout << "Htmlify also has the ability to create a simple index of all the files it is given. " << "The --index option creates a file named index.html with a frame on the left side " << "that contains links to all the files."; cout << "\n\n" << wrap_string(sout.str(),indent,indent); sout.str(""); sout << "Finally, Htmlify can produce annotated XML output instead of HTML. The output will " << "contain all functions which are immediately followed by comments of the form /*! comment body !*/. " << "Similarly, all classes or structs that immediately contain one of these comments following their " << "opening { will also be output as annotated XML. Note also that if you wish to document a " << "piece of code using one of these comments but don't want it to appear in the output XML then " << "use either a comment like /* */ or /*!P !*/ to mark the code as \"private\"."; cout << "\n\n" << wrap_string(sout.str(),indent,indent) << "\n\n"; sout.str(""); } // -------------------------------------------------------------------------------------------------