From 471556a19e68b372155095c1b11aa568735fe867 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Thu, 11 Feb 2016 04:25:00 +0200 Subject: First part of TOC generation support (no links yet) Currently only supported in the HTML output. --- cli/context.cxx | 299 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 292 insertions(+), 7 deletions(-) (limited to 'cli/context.cxx') diff --git a/cli/context.cxx b/cli/context.cxx index 9ba20ab..ce58779 100644 --- a/cli/context.cxx +++ b/cli/context.cxx @@ -98,12 +98,14 @@ namespace context:: context (ostream& os_, + output_type ot_, semantics::cli_unit& unit_, options_type const& ops) : data_ (new (shared) data), os (os_), unit (unit_), options (ops), + ot (ot_), modifier (options.generate_modifier ()), specifier (options.generate_specifier ()), inl (data_->inl_), @@ -112,7 +114,9 @@ context (ostream& os_, cli (data_->cli_), reserved_name_map (options.reserved_name ()), keyword_set (data_->keyword_set_), - link_regex (data_->link_regex_) + link_regex (data_->link_regex_), + toc (data_->toc_), + tocs (data_->tocs_) { if (options.suppress_usage ()) usage = ut_none; @@ -150,6 +154,8 @@ context (ostream& os_, throw generation_failed (); } } + + toc = 0; } context:: @@ -158,6 +164,7 @@ context (context& c) os (c.os), unit (c.unit), options (c.options), + ot (c.ot), modifier (c.modifier), specifier (c.specifier), usage (c.usage), @@ -167,7 +174,9 @@ context (context& c) cli (c.cli), reserved_name_map (c.reserved_name_map), keyword_set (c.keyword_set), - link_regex (c.link_regex) + link_regex (c.link_regex), + toc (c.toc), + tocs (c.tocs) { } @@ -1120,7 +1129,7 @@ html_margin (string& v) string context:: -format (semantics::scope& scope, output_type ot, string const& s, bool para) +format (semantics::scope& scope, string const& s, bool para) { stack blocks; blocks.push (block (block::text, para)); // Top-level. @@ -1165,15 +1174,61 @@ format (semantics::scope& scope, output_type ot, string const& s, bool para) l = s.c_str () + b; n = (last ? s.size () : e) - b; - // Perform variable expansions (\$var$). + // Perform variable expansions (\$var$). Also detect the switch + // to/from TOC mode. // + unsigned short t (toc); if (substitute (scope, l, n, subst)) { + if (subst.empty ()) + continue; + + if (t != toc) + { + // This is a TOC prologue/epilogue (returned by start/end_toc()) and + // we shouldn't be formatting it. + // + assert (blocks.size () == 1); + string& v (blocks.top ().value); + + // Require that \$TOC$ appears in its own doc string. Failed this + // it is tricky to get indentation right. + // + if (!v.empty () || !last) + { + cerr << "error: TOC variable should be in its own documentation " + << "string" << endl; + throw generation_failed (); + } + + switch (ot) + { + case ot_plain: break; + case ot_html: + { + // Different "newline protocol" inside TOC. + // + v += subst; + + if (toc) + v += '\n'; + } + case ot_man: break; + } + + continue; + } + l = subst.c_str (); n = subst.size (); } } + // If this is TOC phase 2, then we simply ignore everything. + // + if (toc == 2) + continue; + const char* ol (l); // Original, full line for diagnostics. size_t on (n); @@ -1412,8 +1467,13 @@ format (semantics::scope& scope, output_type ot, string const& s, bool para) case block::pre: break; // No push. } - // Output paragraph text. + // Output paragraph text. If we are in TOC mode and this is a top-level + // block, then pretend we don't have anything to write. For non-top-level + // blocks, we handle this below, when we pop them. // + if (toc && blocks.size () == 1) + n = 0; + if (n != 0) { block& b (blocks.top ()); @@ -1557,6 +1617,135 @@ format (semantics::scope& scope, output_type ot, string const& s, bool para) block& b (blocks.top ()); string& v (b.value); + // Handle poping into top-level block in TOC mode. + // + if (toc && blocks.size () == 1) + { + if (pb.kind == block::h) + { + switch (ot) + { + case ot_plain: break; + case ot_html: + { + char t (ph[0]); + + // Unwind heading levels that are deeper ("more sub") than us. + // + for (; tocs.size () != 1; tocs.pop_back ()) + { + toc_entry const& e (tocs.back ()); + + bool pop (true); + + switch (t) + { + case '0': + case 'H': + case '1': break; // Always unwind. + case 'h': + { + pop = html_h[1] == '1' || e.type == 'h' || e.type == '2'; + break; + } + case '2': pop = e.type == '2'; break; + } + + if (!pop) + break; + + // If we have sub-headings, then we need to close the table. + // + if (e.count != 0) + { + size_t l (tocs.size ()); + + v += string (l * 4 - 2, ' ') + "\n"; + v += string (l * 4 - 4, ' ') + "\n"; + } + else + // Otherwise it is inline. + // + v += "\n"; + } + + size_t l (tocs.size ()); + toc_entry& e (tocs.back ()); + + // If this is a first sub-entry, then we need to open the + // sub-table. + // + string in ((l * 4) - 2, ' '); + + if (l > 1 && e.count == 0) + { + v += "\n"; + v += in + "\n"; + } + + in += " "; + + switch (t) + { + case 'H': + { + v += in + "\n"; + break; + } + case '0': + case '1': + case 'h': + case '2': + { + // Calculate Chapter(X)/Section(X.Y)/Subsection(X.Y.Z) unless + // it is the preface (or a subsection thereof). + // + string n; + if (t != '0') + { + ++e.count; + + for (toc_stack::const_iterator i (tocs.begin ()); + i != tocs.end (); + ++i) + { + if (i->type == '0') + { + n.clear (); + break; + } + + if (!n.empty ()) + n += '.'; + + ostringstream os; + os << i->count; + n += os.str (); + } + } + + v += in + "\n"; + } + else + // Otherwise it is inline. + // + v += "\n"; + } + + v += "
" + pv + "
" + n + "" + pv; // No newline + tocs.push_back (toc_entry (t)); + break; + } + } + + // Same as in non-TOC mode below. + // + // @@ This only works for a single string fragment. + // + if (ph[0] == '0' || ph[0] == '1') + html_h = "h2"; + + break; + } + case ot_man: break; + } + } + + continue; + } + switch (ot) { case ot_plain: @@ -1752,7 +1941,61 @@ format (semantics::scope& scope, output_type ot, string const& s, bool para) } string context:: -substitute (const string& s, semantics::cli_unit& u, const path* d) +start_toc () +{ + switch (ot) + { + case ot_plain: break; + case ot_html: + { + tocs.push_back (toc_entry ('\0')); + return " "; + } + case ot_man: break; + } +} + +string context:: +end_toc () +{ + switch (ot) + { + case ot_plain: break; + case ot_html: + { + string v; + + // Unwind the TOC stack until we reach top level. Same code as in + // format(). + // + for (; tocs.size () != 1; tocs.pop_back ()) + { + toc_entry const& e (tocs.back ()); + + // If we have sub-headings, then we need to close the table. + // + if (e.count != 0) + { + size_t l (tocs.size ()); + + v += string (l * 4 - 2, ' ') + "
\n"; + v += string (l * 4 - 4, ' ') + "
"; + return v; + } + case ot_man: break; + } +} + +string context:: +substitute (const string& s, const path* d) { string r; @@ -1832,9 +2075,31 @@ substitute (const string& s, semantics::cli_unit& u, const path* d) } else { + // Handle special variables. + // + if (v == "TOC") + { + if (!r.empty () || p + 1 != n) + { + cerr << "error: TOC variable should be on its own line" << endl; + throw generation_failed (); + } + + // Invert the TOC mode. + // + if ((toc = toc ? 0 : 1)) + r = start_toc (); + else + r = end_toc (); + + return r; + } + // Lookup and substiute the variable. // - if (semantics::doc* d = u.lookup ("", "var: " + v)) + using semantics::doc; + + if (doc* d = unit.lookup ("", "var: " + v)) r += d->front (); else { @@ -1900,6 +2165,26 @@ substitute (semantics::scope& scope, const char* s, size_t n, string& result) ++e; string v (s, e, p - e); + // Handle special variables. + // + if (v == "TOC") + { + if (!result.empty () || p + 1 != n) + { + cerr << "error: TOC variable should be its own paragraph" << endl; + throw generation_failed (); + } + + // Invert the TOC mode. + // + if ((toc = toc ? 0 : 1)) + result = start_toc (); + else + result = end_toc (); + + return true; + } + // Lookup and substiute. // using semantics::doc; -- cgit