From 418792e491764cc0e11e86522d43835a3da82fa6 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Fri, 13 Nov 2015 15:30:55 +0200 Subject: Add support for man formatting --- cli/context.cxx | 154 +++++++++++++++++++++++++++++++++++++++++++++--------- cli/generator.cxx | 17 ------ cli/html.cxx | 7 +-- cli/man.cxx | 143 ++++++++++++++++++++++++++++++++++---------------- cli/parser.cxx | 19 +++++-- 5 files changed, 243 insertions(+), 97 deletions(-) (limited to 'cli') 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 += "
";
+      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 += "
"; - } - else - { - if (!first || b.para) - { - if (ot == ot_html) - v += "

"; + 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 += "

";
+            v.append (l, n);
+            v += "
"; + } + else + { + if (!first || b.para) v += "

"; + format_line (ot, v, l, n); + if (!first || b.para) v += "

"; + } - if (!first || b.para) + break; + } + case ot_man: { - if (ot == ot_html) - v += "

"; + 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\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
.
@@ -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 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
-- 
cgit v1.1