dlib/tools/htmlify/htmlify.cpp
2020-01-28 21:02:41 -05:00

633 lines
21 KiB
C++

#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 (
);
/*!
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 <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; // 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<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)
{
// 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 << "<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();
}
// strip the .html from file
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);
// 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("");
}
// -------------------------------------------------------------------------------------------------