From 01314f53987e57c4fcb49fb86d3971bdb206857f Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Thu, 11 Feb 2016 05:53:12 +0200 Subject: Add support for ids in paragraphs, local fragment references in links For example: " \h#hello|Hello Example| See the \l{#hello Hello Example} " --- cli/context.cxx | 137 ++++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 118 insertions(+), 19 deletions(-) (limited to 'cli/context.cxx') diff --git a/cli/context.cxx b/cli/context.cxx index ce58779..b923c4b 100644 --- a/cli/context.cxx +++ b/cli/context.cxx @@ -115,6 +115,8 @@ context (ostream& os_, reserved_name_map (options.reserved_name ()), keyword_set (data_->keyword_set_), link_regex (data_->link_regex_), + id_set (data_->id_set_), + ref_set (data_->ref_set_), toc (data_->toc_), tocs (data_->tocs_) { @@ -175,6 +177,8 @@ context (context& c) reserved_name_map (c.reserved_name_map), keyword_set (c.keyword_set), link_regex (c.link_regex), + id_set (c.id_set), + ref_set (c.ref_set), toc (c.toc), tocs (c.tocs) { @@ -684,6 +688,15 @@ format_line (output_type ot, string& r, const char* s, size_t n) else link_section.clear (); + // If this is a local fragment reference, add it to the set to be + // verified at the end. + // + if (t[0] == '#') + { + assert (link_section.empty ()); // Not a man page. + ref_set.insert (string (t, 1, string::npos)); + } + link_empty = i + 1 < n && s[i + 1] == '}'; blocks.push_back (link); @@ -1063,12 +1076,13 @@ struct block kind_type kind; bool para; // True if first text fragment should be in own paragraph. + string id; string header; string value; string trailer; - block (kind_type k, bool p, const string& h = "") - : kind (k), para (p), header (h) {} + block (kind_type k, bool p, const string& i, const string& h = "") + : kind (k), para (p), id (i), header (h) {} }; static const char* block_kind_str[] = { @@ -1132,7 +1146,7 @@ string context:: format (semantics::scope& scope, string const& s, bool para) { stack blocks; - blocks.push (block (block::text, para)); // Top-level. + blocks.push (block (block::text, para, "")); // Top-level. // Number of li in ol. Since we don't support nested lists, we don't // need to push it into the stack. @@ -1235,6 +1249,7 @@ format (semantics::scope& scope, string const& s, bool para) // First determine what kind of paragraph block this is. // block::kind_type k; + string id; string header; string trailer; @@ -1246,15 +1261,25 @@ format (semantics::scope& scope, string const& s, bool para) } else { + // \h \H + // if (n >= 3 && - l[0] == '\\' && (l[1] == 'h' || l[1] == 'H') && l[2] == '|') + l[0] == '\\' && + (l[1] == 'h' || l[1] == 'H') && + (l[2] == '|' || l[2] == '#')) { k = block::h; header = l[1]; l += 3; n -= 3; } - else if (n >= 4 && l[0] == '\\' && l[1] == 'h' && l[3] == '|') + // + // \h0 \h1 \h2 + // + else if (n >= 4 && + l[0] == '\\' && + l[1] == 'h' && + (l[3] == '|' || l[3] == '#')) { if (l[2] != '0' && l[2] != '1' && l[2] != '2') { @@ -1268,10 +1293,14 @@ format (semantics::scope& scope, string const& s, bool para) l += 4; n -= 4; } + // + // \ul \ol \dl + // else if (n >= 4 && - (strncmp (l, "\\ul|", 4) == 0 || - strncmp (l, "\\ol|", 4) == 0 || - strncmp (l, "\\dl|", 4) == 0)) + l[0] == '\\' && + (l[1] == 'u' || l[1] == 'o' || l[1] == 'd') && + l[2] == 'l' && + (l[3] == '|' || l[3] == '#')) { switch (l[1]) { @@ -1283,7 +1312,14 @@ format (semantics::scope& scope, string const& s, bool para) l += 4; n -= 4; } - else if (n >= 4 && strncmp (l, "\\li|", 4) == 0) + // + // \li + // + else if (n >= 4 && + l[0] == '\\' && + l[1] == 'l' && + l[2] == 'i' && + (l[3] == '|' || l[3] == '#')) { k = block::li; l += 4; @@ -1292,10 +1328,27 @@ format (semantics::scope& scope, string const& s, bool para) else k = block::text; + // Get the id, if present. + // + if (k != block::text && *(l - 1) == '#') + { + for (; n != 0 && *l != '|'; ++l, --n) + id += *l; + + if (n == 0) + { + cerr << "error: paragraph begin '|' expected after id in '" + << string (ol, 0, on) << "'" << endl; + throw generation_failed (); + } + + ++l; --n; // Skip '|'. + } + // Skip leading spaces after opening '|'. // if (k != block::text) - while (n != 0 && (*l == 0x20 || *l == 0x0D || *l == 0x09)) {l++; n--;} + while (n != 0 && (*l == 0x20 || *l == 0x0D || *l == 0x09)) {++l; --n;} // Next figure out how many blocks we need to pop at the end of this // paragraph. Things get a bit complicated since '|' could be escaped. @@ -1356,6 +1409,18 @@ format (semantics::scope& scope, string const& s, bool para) } } + // Check id for duplicates. Do it only on the non-TOC pass. + // + if (!toc && !id.empty ()) + { + if (!id_set.insert (id).second) + { + cerr << "error: duplicate id '" << id << "' in documentation " + << "string '" << s << "'" << endl; + throw generation_failed (); + } + } + // Verify the block itself. // switch (k) @@ -1435,10 +1500,10 @@ format (semantics::scope& scope, string const& s, bool para) // switch (k) { - case block::h: blocks.push (block (k, false, header)); break; + case block::h: blocks.push (block (k, false, id, header)); break; case block::ul: case block::ol: ol_count = 0; // Fall through. - case block::dl: blocks.push (block (k, true)); break; + case block::dl: blocks.push (block (k, true, id)); break; case block::li: { switch (blocks.top ().kind) @@ -1460,7 +1525,7 @@ format (semantics::scope& scope, string const& s, bool para) break; } - blocks.push (block (k, false, header)); + blocks.push (block (k, false, id, header)); break; } case block::text: break; // No push. @@ -1609,8 +1674,9 @@ format (semantics::scope& scope, string const& s, bool para) for (; pop != 0; --pop) { block pb (blocks.top ()); // move - string& pv (pb.value); + string& pi (pb.id); string& ph (pb.header); + string& pv (pb.value); blocks.pop (); @@ -1825,15 +1891,25 @@ format (semantics::scope& scope, string const& s, bool para) { case block::h: { + string h; + string c; + switch (ph[0]) { - case '0': v += "

" + pv + "

"; break; - case 'H': v += "

" + pv + "

"; break; - case '1': v += "

" + pv + "

"; break; - case '2': v += "

" + pv + "

"; break; - case 'h': v += '<' + html_h + '>' + pv + "'; + case '0': h = "h1"; c = "preface"; break; + case 'H': h = "h1"; c = "part"; break; + case '1': h = "h1"; break; + case 'h': h = html_h; break; + case '2': h = "h3"; break; } + v += '<' + h; + if (!pi.empty ()) v += " id=\"" + pi + '"'; + if (!c.empty ()) v += " class=\"" + c + '"'; + v += '>'; + v += pv; + v += "'; + // @@ This only works for a single string fragment. // if (ph[0] == '0' || ph[0] == '1') @@ -1994,6 +2070,29 @@ end_toc () } } +void context:: +verify_id_ref () +{ + bool f (false); + + for (id_set_type::const_iterator i (ref_set.begin ()); + i != ref_set.end (); + ++i) + { + if (id_set.find (*i) == id_set.end ()) + { + cerr << "error: no id for fragment link '#" << *i << "'" << endl; + f = true; + } + } + + if (f) + throw generation_failed (); + + id_set.clear (); + ref_set.clear (); +} + string context:: substitute (const string& s, const path* d) { -- cgit v1.1