diff options
author | Boris Kolpackov <boris@codesynthesis.com> | 2015-11-13 15:30:55 +0200 |
---|---|---|
committer | Boris Kolpackov <boris@codesynthesis.com> | 2015-11-13 15:30:55 +0200 |
commit | 418792e491764cc0e11e86522d43835a3da82fa6 (patch) | |
tree | 779116bc14395673e4029da48321ea9bc9e58a08 | |
parent | 124341e83bb9e8959508fac8c798506f82f21938 (diff) |
Add support for man formatting
-rw-r--r-- | cli/context.cxx | 154 | ||||
-rw-r--r-- | cli/generator.cxx | 17 | ||||
-rw-r--r-- | cli/html.cxx | 7 | ||||
-rw-r--r-- | cli/man.cxx | 143 | ||||
-rw-r--r-- | cli/parser.cxx | 19 | ||||
-rw-r--r-- | doc/cli.1 | 157 | ||||
-rw-r--r-- | doc/cli.xhtml | 1 |
7 files changed, 292 insertions, 206 deletions
diff --git a/cli/context.cxx b/cli/context.cxx index 4b41132..d07b60b 100644 --- a/cli/context.cxx +++ b/cli/context.cxx @@ -641,8 +641,10 @@ struct block kind_type kind; bool para; // True if first text fragment should be in own paragraph. - string header; // Term in dl's li. + + string header; string value; + string trailer; block (kind_type k, bool p, const string& h = "") : kind (k), para (p), header (h) {} @@ -927,35 +929,86 @@ format (output_type ot, string const& s, bool para) string& v (b.value); bool first (v.empty ()); - // Separate paragraphs with a blank line. - // - if (!first) - v += "\n\n"; - - if (k == block::pre) + switch (ot) { - if (ot == ot_html) - v += "<pre>"; + case ot_plain: + { + // Separate paragraphs with a blank line. + // + if (!first) + v += "\n\n"; - v.append (l, n); + if (k == block::pre) + v.append (l, n); + else + format_line (ot, v, l, n); - if (ot == ot_html) - v += "</pre>"; - } - else - { - if (!first || b.para) - { - if (ot == ot_html) - v += "<p>"; + break; } + case ot_html: + { + // Separate paragraphs with a blank line. + // + if (!first) + v += "\n\n"; - format_line (ot, v, l, n); + if (k == block::pre) + { + v += "<pre>"; + v.append (l, n); + v += "</pre>"; + } + else + { + if (!first || b.para) v += "<p>"; + format_line (ot, v, l, n); + if (!first || b.para) v += "</p>"; + } - if (!first || b.para) + break; + } + case ot_man: { - if (ot == ot_html) - v += "</p>"; + if (b.para) + { + if (!first) + v += "\n"; + + v += ".PP\n"; + } + else + { + if (!first) + v += "\n\n"; + } + + if (k == block::pre) + { + v += ".nf\n"; + + // Note that if we have several consequtive blank lines, they + // will be collapsed into a single one. No, .sp doesn't work. + // + char c, p ('\n'); // Current and previous characters. + for (size_t i (0); i != n; p = c, ++i) + { + switch (c = l[i]) + { + case '\\': v += '\\'; break; + case '.': v += p != '\n' ? "\\" : "\\&\\"; break; + } + + v += c; + } + + v += "\n.fi"; + } + else + { + format_line (ot, v, l, n); + } + + break; } } } @@ -1043,7 +1096,51 @@ format (output_type ot, string const& s, bool para) } case ot_man: { - break; // @@ TODO + // Seeing that we always write a macro, one newline is enough. + // + if (!v.empty ()) + v += "\n"; + + switch (pb.kind) + { + case block::h: v += ".SH \"" + pv + "\""; break; + case block::ul: + case block::ol: + case block::dl: + { + if (!b.para) // First list inside .IP. + { + // .IP within .IP? Just shoot me in the head already! We + // have to manually indent it with .RS/.RE *and* everything + // that comes after it (since .PP resets the indent). Why + // not just indent the whole list content? Because then the + // first line will never start on the same line as the term. + // + v += ".RS\n"; + b.trailer = "\n.RE"; + b.para = true; // Start emitting .PP from now on. + } + + v += pv; + break; + } + case block::li: + { + switch (b.kind) + { + case block::ul: v += ".IP \\(bu 2em\n" + pv; break; + case block::ol: v += ".IP " + ph + ". 4em\n" + pv; break; + case block::dl: v += ".IP \"" + ph + "\"\n" + pv; break; + default: break; + } + + break; + } + case block::text: + case block::pre: assert (false); + } + + break; } } } @@ -1058,7 +1155,14 @@ format (output_type ot, string const& s, bool para) throw generation_failed (); } - return blocks.top ().value; + block& b (blocks.top ()); + + switch (ot) + { + case ot_plain: + case ot_html: return b.value; + case ot_man: return b.value + b.trailer; + } } string context:: diff --git a/cli/generator.cxx b/cli/generator.cxx index f67b8aa..04f2b89 100644 --- a/cli/generator.cxx +++ b/cli/generator.cxx @@ -42,19 +42,6 @@ namespace "// compiler for C++.\n" "//\n\n"; - static char const man_header[] = - ".\\\"\n" - ".\\\" The following documentation was generated by CLI, a command\n" - ".\\\" line interface compiler for C++.\n" - ".\\\"\n"; - - static char const html_header[] = - "\n" - "<!-- \n" - " The following documentation was generated by CLI, a command\n" - " line interface compiler for C++.\n" - "-->\n\n"; - string make_guard (string const& file, context& ctx) { @@ -488,8 +475,6 @@ generate (options const& ops, semantics::cli_unit& unit, path const& p) append (os, ops.man_prologue (), ops.man_prologue_file (), unit); - os << man_header; - context ctx (os, unit, ops); generate_man (ctx); @@ -527,8 +512,6 @@ generate (options const& ops, semantics::cli_unit& unit, path const& p) append (os, ops.html_prologue (), ops.html_prologue_file (), unit); - os << html_header; - context ctx (os, unit, ops); generate_html (ctx); diff --git a/cli/html.cxx b/cli/html.cxx index e91fdd7..41d4c59 100644 --- a/cli/html.cxx +++ b/cli/html.cxx @@ -46,15 +46,12 @@ namespace static void wrap_lines (ostream& os, const string& d, size_t indent) { - assert (!d.empty ()); - size_t lim (78 - indent); string ind (indent, ' '); - size_t b (0), e (0), i (0); - bool nl (true); // True if last written to os character is a newline. + size_t b (0), e (0), i (0); for (size_t n (d.size ()); i < n; ++i) { // First handle <pre>. @@ -218,8 +215,6 @@ namespace os << endl << endl; } - - private: }; // diff --git a/cli/man.cxx b/cli/man.cxx index d4ef7c5..2cbc766 100644 --- a/cli/man.cxx +++ b/cli/man.cxx @@ -12,6 +12,93 @@ using namespace std; namespace { + static string + escape_line (const string& s, size_t b, size_t e) + { + string r; + size_t n (e - b); + + // Escaping leading '.' with '\' is not sufficient. + // + if (n > 1 && s[b] == '\\' && s[b + 1] == '.') + r = "\\&"; + + r.append (s, b, n); + return r; + } + + static void + wrap_lines (ostream& os, const string& d) + { + size_t b (0), e (0), i (0); + for (size_t n (d.size ()); i < n; ++i) + { + // First handle preformatted text (.nf/.fi). + // + if (d.compare (i, 4, ".nf\n") == 0 && (i == 0 || d[i - 1] == '\n')) + { + assert (b == i); // We should have nothing accumulated. + + // Output everything until (and including) closing .fi as is. + // + e = d.find ("\n.fi", i + 4); + assert (e != string::npos); + e += 4; // Now points past 'i'. + + os << string (d, i, e - i); + + b = e; + i = e - 1; // For ++i in loop header. + continue; + } + + if (d[i] == ' ' || d[i] == '\n') + e = i; + + if (d[i] == '\n' || (i - b >= 78 && e != b)) + { + os << escape_line (d, b, e) << endl; + b = e = e + 1; + } + } + + // Flush the last line. + // + if (b != i) + os << escape_line (d, b, i); + } + + struct doc: traversal::doc, context + { + doc (context& c): context (c) {} + + virtual void + traverse (type& ds) + { + if (ds.name ().compare (0, 3, "doc") != 0) // Ignore doc variables. + return; + + // n = 1 - common doc string + // n = 2 - arg string, common doc string + // n > 2 - arg string, usage string, man string + // + size_t n (ds.size ()); + const string& d (n == 1 ? ds[0] : n == 2 ? ds[1] : ds[2]); + + if (d.empty ()) + return; + + std::set<string> arg_set; + if (n > 1) + translate_arg (ds[0], arg_set); + + string s (format (ot_man, translate (d, arg_set), true)); + + wrap_lines (os, s); + os << endl; + } + }; + struct option: traversal::option, context { option (context& c) : context (c) {} @@ -79,42 +166,8 @@ namespace // d = format (ot_man, translate (d, arg_set), false); - if (!d.empty ()) - { - size_t b (0), e (0), i (0); - - for (size_t n (d.size ()); i < n; ++i) - { - if (d[i] == ' ' || d[i] == '\n') - e = i; - - if (d[i] == '\n' || (i - b >= 76 && e != b)) - { - if (b != 0) - os << endl; - - os << string (d, b, e - b); - - if (d[i] == '\n') - os << endl; - - b = e = e + 1; - } - } - - // Flush the last line. - // - if (b != i) - { - if (b != 0) - os << endl; - - os << string (d, b, i - b); - } - } - - os << endl - << endl; + wrap_lines (os, d); + os << endl; } }; @@ -125,9 +178,7 @@ namespace class_ (context& c) : context (c), option_ (c) { - *this >> inherits_base_ >> base_ >> inherits_base_; - base_ >> names_option_; - + *this >> inherits_base_ >> *this; names_option_ >> option_; } @@ -141,11 +192,10 @@ namespace } private: + traversal::inherits inherits_base_; + option option_; traversal::names names_option_; - - traversal::class_ base_; - traversal::inherits inherits_base_; }; } @@ -155,14 +205,19 @@ generate_man (context& ctx) traversal::cli_unit unit; traversal::names unit_names; traversal::namespace_ ns; + doc dc (ctx); class_ cl (ctx); - unit >> unit_names >> ns; + unit >> unit_names; + unit_names >> ns; + unit_names >> dc; unit_names >> cl; traversal::names ns_names; - ns >> ns_names >> ns; + ns >> ns_names; + ns_names >> ns; + ns_names >> dc; ns_names >> cl; if (ctx.options.class_ ().empty ()) diff --git a/cli/parser.cxx b/cli/parser.cxx index faac290..164a3c0 100644 --- a/cli/parser.cxx +++ b/cli/parser.cxx @@ -946,25 +946,34 @@ option_def (token& t) string parser:: doc_string (const char* l, size_t n) { - // Get rid of '"'. + // Get rid of '"', convert '\"' to just '"'. // string t1, t2, t3; char p ('\0'); for (size_t i (0); i < n; ++i) { - if (l[i] == '"' && p != '\\') + char c (l[i]); + + if (c == '"') + { + if (p == '\\') + { + t1[t1.size () - 1] = '"'; // Replace '\' with '"'. + p = c; + } continue; + } // We need to keep track of \\ escapings so we don't confuse // them with \", as in "\\". // - if (l[i] == '\\' && p == '\\') + if (c == '\\' && p == '\\') p = '\0'; else - p = l[i]; + p = c; - t1 += l[i]; + t1 += c; } // Get rid of leading and trailing spaces in each line. Also handle @@ -63,233 +63,174 @@ option can be used to redirect the output to STDOUT instead of a file. .\" .IP "\fB--help\fP" Print usage information and exit\. - .IP "\fB--version\fP" Print version and exit\. - .IP "\fB--include-path\fP|\fB-I\fP \fIdir\fP" Search \fIdir\fP for bracket-included (\fB<>\fP) options files\. - .IP "\fB--output-dir\fP|\fB-o\fP \fIdir\fP" Write the generated files to \fIdir\fP instead of the current directory\. - .IP "\fB--generate-modifier\fP" Generate option value modifiers in addition to accessors\. - .IP "\fB--generate-specifier\fP" Generate functions for determining whether the option was specified on the command line\. - .IP "\fB--generate-parse\fP" Generate \fBparse()\fP functions instead of parsing constructors\. This is primarily useful for being able to parse into an already initialized options class instance, for example, to implement merging/overriding\. - .IP "\fB--generate-description\fP" Generate the option description list that can be examined at runtime\. - .IP "\fB--generate-file-scanner\fP" Generate the argv_file_scanner implementation\. This scanner is capable of -reading command line arguments from the argv array as well as files -specified with command line options\. - +reading command line arguments from the argv array as well as files specified +with command line options\. .IP "\fB--suppress-inline\fP" Generate all functions non-inline\. By default simple functions are made inline\. This option suppresses creation of the inline file\. - .IP "\fB--ostream-type\fP \fItype\fP" Output stream type instead of the default \fBstd::ostream\fP that should be used to print usage and exception information\. - .IP "\fB--suppress-undocumented\fP" Suppress the generation of documentation entries for undocumented options\. - .IP "\fB--suppress-usage\fP" Suppress the generation of the usage printing code\. - .IP "\fB--long-usage\fP" If no short documentation string is provided, use the complete long documentation string in usage\. By default, in this situation only the first sentence from the long string is used\. - .IP "\fB--short-usage\fP" If specified together with \fB--long-usage\fP, generate both short and long usage versions\. In this mode, the usage printing functions are called -\fBprint_short_usage()\fP and \fBprint_long_usage()\fP and for the long -usage the long documentation string is always used, even if the short -version is provided\. - +\fBprint_short_usage()\fP and \fBprint_long_usage()\fP and for the long usage +the long documentation string is always used, even if the short version is +provided\. .IP "\fB--option-length\fP \fIlen\fP" -Indent option descriptions \fIlen\fP characters when printing usage\. This -is useful when you have multiple options classes, potentially in separate -files, and would like their usage to have the same indentation level\. - +Indent option descriptions \fIlen\fP characters when printing usage\. This is +useful when you have multiple options classes, potentially in separate files, +and would like their usage to have the same indentation level\. .IP "\fB--exclude-base\fP" Exclude base class information from usage and documentation\. - .IP "\fB--cli-namespace\fP \fIns\fP" Generate the CLI support types in the \fIns\fP namespace (\fBcli\fP by default)\. The namespace can be nested, for example \fBdetails::cli\fP\. If the namespace is empty, then the support types are generated in the global namespace\. - .IP "\fB--generate-cxx\fP" -Generate C++ code\. If neither \fB--generate-man\fP nor -\fB--generate-html\fP is specified, this mode is assumed by default\. - +Generate C++ code\. If neither \fB--generate-man\fP nor \fB--generate-html\fP +is specified, this mode is assumed by default\. .IP "\fB--generate-man\fP" Generate documentation in the man page format\. - .IP "\fB--generate-html\fP" Generate documentation in the HTML format\. - .IP "\fB--hxx-prologue\fP \fItext\fP" Insert \fItext\fP at the beginning of the generated C++ header file\. - .IP "\fB--ixx-prologue\fP \fItext\fP" Insert \fItext\fP at the beginning of the generated C++ inline file\. - .IP "\fB--cxx-prologue\fP \fItext\fP" Insert \fItext\fP at the beginning of the generated C++ source file\. - .IP "\fB--man-prologue\fP \fItext\fP" Insert \fItext\fP at the beginning of the generated man page file\. - .IP "\fB--html-prologue\fP \fItext\fP" Insert \fItext\fP at the beginning of the generated HTML file\. - .IP "\fB--hxx-epilogue\fP \fItext\fP" Insert \fItext\fP at the end of the generated C++ header file\. - .IP "\fB--ixx-epilogue\fP \fItext\fP" Insert \fItext\fP at the end of the generated C++ inline file\. - .IP "\fB--cxx-epilogue\fP \fItext\fP" Insert \fItext\fP at the end of the generated C++ source file\. - .IP "\fB--man-epilogue\fP \fItext\fP" Insert \fItext\fP at the end of the generated man page text\. - .IP "\fB--html-epilogue\fP \fItext\fP" Insert \fItext\fP at the end of the generated HTML text\. - .IP "\fB--hxx-prologue-file\fP \fIfile\fP" -Insert the content of \fIfile\fP at the beginning of the generated C++ -header file\. - +Insert the content of \fIfile\fP at the beginning of the generated C++ header +file\. .IP "\fB--ixx-prologue-file\fP \fIfile\fP" -Insert the content of \fIfile\fP at the beginning of the generated C++ -inline file\. - +Insert the content of \fIfile\fP at the beginning of the generated C++ inline +file\. .IP "\fB--cxx-prologue-file\fP \fIfile\fP" -Insert the content of \fIfile\fP at the beginning of the generated C++ -source file\. - +Insert the content of \fIfile\fP at the beginning of the generated C++ source +file\. .IP "\fB--man-prologue-file\fP \fIfile\fP" Insert the content of \fIfile\fP at the beginning of the generated man page file\. - .IP "\fB--html-prologue-file\fP \fIfile\fP" -Insert the content of \fIfile\fP at the beginning of the generated HTML -file\. - +Insert the content of \fIfile\fP at the beginning of the generated HTML file\. .IP "\fB--hxx-epilogue-file\fP \fIfile\fP" -Insert the content of \fIfile\fP at the end of the generated C++ header -file\. - +Insert the content of \fIfile\fP at the end of the generated C++ header file\. .IP "\fB--ixx-epilogue-file\fP \fIfile\fP" -Insert the content of \fIfile\fP at the end of the generated C++ inline -file\. - +Insert the content of \fIfile\fP at the end of the generated C++ inline file\. .IP "\fB--cxx-epilogue-file\fP \fIfile\fP" -Insert the content of \fIfile\fP at the end of the generated C++ source -file\. - +Insert the content of \fIfile\fP at the end of the generated C++ source file\. .IP "\fB--man-epilogue-file\fP \fIfile\fP" Insert the content of \fIfile\fP at the end of the generated man page file\. - .IP "\fB--html-epilogue-file\fP \fIfile\fP" Insert the content of \fIfile\fP at the end of the generated HTML file\. - .IP "\fB--class\fP \fIfq-name\fP" -Generate the man page or HTML documentation only for the \fIfq-name\fP -options class\. The \fIfq-name\fP name should be a fully-qualified options -class name, for example, \fBapp::options\fP\. To generate documentation for -multiple classes, repeat this option and the documentation will be produced -in the order specified\. This functionality is useful if you need to -assemble documentation from multiple classes in a specific order or to -insert custom documentation between options belonging to different classes\. - +Generate the man page or HTML documentation only for the \fIfq-name\fP options +class\. The \fIfq-name\fP name should be a fully-qualified options class name, +for example, \fBapp::options\fP\. To generate documentation for multiple +classes, repeat this option and the documentation will be produced in the +order specified\. This functionality is useful if you need to assemble +documentation from multiple classes in a specific order or to insert custom +documentation between options belonging to different classes\. .IP "\fB--stdout\fP" Write output to STDOUT instead of a file\. This option is not valid when generating C++ code and is normally used to combine generated documentation for several option classes in a single file\. - .IP "\fB--hxx-suffix\fP \fIsuffix\fP" Use \fIsuffix\fP instead of the default \fB\.hxx\fP to construct the name of the generated header file\. - .IP "\fB--ixx-suffix\fP \fIsuffix\fP" Use \fIsuffix\fP instead of the default \fB\.ixx\fP to construct the name of the generated inline file\. - .IP "\fB--cxx-suffix\fP \fIsuffix\fP" Use \fIsuffix\fP instead of the default \fB\.cxx\fP to construct the name of the generated source file\. - .IP "\fB--man-suffix\fP \fIsuffix\fP" -Use \fIsuffix\fP instead of the default \fB\.1\fP to construct the name of -the generated man page file\. - +Use \fIsuffix\fP instead of the default \fB\.1\fP to construct the name of the +generated man page file\. .IP "\fB--html-suffix\fP \fIsuffix\fP" -Use \fIsuffix\fP instead of the default \fB\.html\fP to construct the name -of the generated HTML file\. - +Use \fIsuffix\fP instead of the default \fB\.html\fP to construct the name of +the generated HTML file\. .IP "\fB--option-prefix\fP \fIprefix\fP" -Use \fIprefix\fP instead of the default \fB-\fP as an option prefix\. -Unknown command line arguments that start with this prefix are treated as -unknown options\. If you set the option prefix to the empty value, then all -the unknown command line arguments will be treated as program arguments\. - +Use \fIprefix\fP instead of the default \fB-\fP as an option prefix\. Unknown +command line arguments that start with this prefix are treated as unknown +options\. If you set the option prefix to the empty value, then all the +unknown command line arguments will be treated as program arguments\. .IP "\fB--option-separator\fP \fIsep\fP" -Use \fIsep\fP instead of the default \fB--\fP as an optional separator -between options and arguments\. All the command line arguments that are -parsed after this separator are treated as program arguments\. Set the -option separator to the empty value if you don't want this functionality\. - +Use \fIsep\fP instead of the default \fB--\fP as an optional separator between +options and arguments\. All the command line arguments that are parsed after +this separator are treated as program arguments\. Set the option separator to +the empty value if you don't want this functionality\. .IP "\fB--include-with-brackets\fP" -Use angle brackets (<>) instead of quotes ("") in the generated -\fB#include\fP directives\. - +Use angle brackets (<>) instead of quotes ("") in the generated \fB#include\fP +directives\. .IP "\fB--include-prefix\fP \fIprefix\fP" Add \fIprefix\fP to the generated \fB#include\fP directive paths\. - .IP "\fB--guard-prefix\fP \fIprefix\fP" Add \fIprefix\fP to the generated header inclusion guards\. The prefix is transformed to upper case and characters that are illegal in a preprocessor macro name are replaced with underscores\. - .IP "\fB--reserved-name\fP \fIname\fP=\fIrep\fP" Add \fIname\fP with an optional \fIrep\fP replacement to the list of names -that should not be used as identifiers\. If provided, the replacement name -is used instead\. All C++ keywords are already in this list\. - +that should not be used as identifiers\. If provided, the replacement name is +used instead\. All C++ keywords are already in this list\. .IP "\fB--options-file\fP \fIfile\fP" Read additional options from \fIfile\fP with each option appearing on a separate line optionally followed by space and an option value\. Empty lines -and lines starting with \fB#\fP are ignored\. Option values can be enclosed -in double (\fB"\fP) or single (\fB'\fP) quotes to preserve leading and -trailing whitespaces as well as to specify empty values\. If the value -itself contains trailing or leading quotes, enclose it with an extra pair of -quotes, for example \fB'"x"'\fP\. Non-leading and non-trailing quotes are -interpreted as being part of the option value\. +and lines starting with \fB#\fP are ignored\. Option values can be enclosed in +double (\fB"\fP) or single (\fB'\fP) quotes to preserve leading and trailing +whitespaces as well as to specify empty values\. If the value itself contains +trailing or leading quotes, enclose it with an extra pair of quotes, for +example \fB'"x"'\fP\. Non-leading and non-trailing quotes are interpreted as +being part of the option value\. The semantics of providing options in a file is equivalent to providing the same set of options in the same order on the command line at the point where the \fB--options-file\fP option is specified except that the shell escaping and quoting is not required\. Repeat this option to specify more than one options file\. - .\" .\" DIAGNOSTICS .\" diff --git a/doc/cli.xhtml b/doc/cli.xhtml index edc0110..896f237 100644 --- a/doc/cli.xhtml +++ b/doc/cli.xhtml @@ -77,7 +77,6 @@ --> <dl class="options"> - <dt><code><b>--help</b></code></dt> <dd>Print usage information and exit.</dd> |