|
#include <fstream> |
|
#include <iostream> |
|
#include <string> |
|
|
|
|
|
#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<string,string>::kernel_1a map_string_to_string; |
|
typedef dlib::set<string>::kernel_1a set_of_string; |
|
typedef queue<file>::kernel_1a queue_of_files; |
|
typedef queue<directory>::kernel_1a queue_of_dirs; |
|
|
|
void print_manual ( |
|
); |
|
|
|
|
|
|
|
|
|
|
|
void htmlify ( |
|
const map_string_to_string& file_map, |
|
bool colored, |
|
bool number_lines, |
|
const std::string& title |
|
); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void htmlify ( |
|
istream& in, |
|
ostream& out, |
|
const std::string& title, |
|
bool colored, |
|
bool 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 = 0 |
|
); |
|
|
|
|
|
|
|
|
|
|
|
|
|
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 <arg> 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<unsigned long>("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; |
|
string full_out_dir; |
|
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<unsigned long>(depth_opt.argument()); |
|
} |
|
|
|
if (to_xml_opt) |
|
{ |
|
unsigned long expand_tabs = 0; |
|
if (parser.option("t")) |
|
expand_tabs = string_cast<unsigned long>(parser.option("t").argument()); |
|
|
|
generate_xml_markup(parser, filter, search_depth, expand_tabs); |
|
return 0; |
|
} |
|
|
|
if (o_opt) |
|
{ |
|
|
|
out_dir = o_opt.argument(); |
|
create_directory(out_dir); |
|
directory dir(out_dir); |
|
full_out_dir = dir.full_name(); |
|
|
|
|
|
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; |
|
|
|
|
|
map_string_to_string 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) |
|
{ |
|
|
|
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); |
|
} |
|
|
|
|
|
set_of_string sfilter; |
|
istringstream sin(filter); |
|
string temp; |
|
sin >> temp; |
|
while (sin) |
|
{ |
|
if (sfilter.is_member(temp) == false) |
|
sfilter.add(temp); |
|
sin >> temp; |
|
} |
|
|
|
|
|
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 << "<html><title>" << cat_opt.argument() << "</title></html>"; |
|
|
|
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 << "<html><frameset cols='200,*'>"; |
|
index << "<frame src='menu.html' name='menu'>"; |
|
index << "<frame name='main'></frameset></html>"; |
|
|
|
menu << "<html><body><br>"; |
|
|
|
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(); |
|
} |
|
|
|
file = file.substr(0,file.size()-5); |
|
menu << "<a href='" << file << ".html' target='main'>" |
|
<< file << "</a><br>"; |
|
} |
|
|
|
menu << "</body></html>"; |
|
|
|
} |
|
|
|
} |
|
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); |
|
|
|
|
|
|
|
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 |
|
{ |
|
|
|
|
|
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) |
|
{ |
|
|
|
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); |
|
|
|
} |
|
} |
|
files.clear(); |
|
|
|
if (search_depth > cur_depth) |
|
{ |
|
|
|
dir.get_dirs(dirs); |
|
dirs.reset(); |
|
while (dirs.move_next()) |
|
{ |
|
if (!flatten && !cat) |
|
{ |
|
string d = dirs.element().full_name(); |
|
|
|
|
|
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); |
|
} |
|
|
|
|
|
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(""); |
|
} |
|
|
|
|
|
|
|
|