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 +++++++++++++++++++++++++++++++++++++++++++++-------- cli/context.hxx | 14 ++++++ cli/generator.cxx | 6 +++ cli/source.cxx | 10 ++++ tests/toc/toc.cli | 31 ++++++------ tests/toc/toc.html | 32 +++++++------ 6 files changed, 180 insertions(+), 50 deletions(-) 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) { diff --git a/cli/context.hxx b/cli/context.hxx index 79c9aab..c67cec5 100644 --- a/cli/context.hxx +++ b/cli/context.hxx @@ -99,6 +99,11 @@ public: regex_mapping const& link_regex; + typedef std::set id_set_type; + id_set_type& id_set; + id_set_type& ref_set; + + // TOC phase. // // 0 - non-TOC @@ -126,6 +131,8 @@ private: string cli_; keyword_set_type keyword_set_; regex_mapping link_regex_; + id_set_type id_set_; + id_set_type ref_set_; unsigned short toc_; toc_stack tocs_; }; @@ -166,6 +173,13 @@ public: string end_toc (); + // Make sure each local fragment reference has the corresponding id. Issue + // diagnostics and throw generation_failed if fails. Otherwise clear the + // id and ref sets. + // + void + verify_id_ref (); + // Substitute doc variable expansions ($var$). Var must be a C identifier. // If the path is not NULL, then also recognize names that start with either // ./ or ../ and treat them as files relative to path. Such file expansions diff --git a/cli/generator.cxx b/cli/generator.cxx index cbb3ec7..01f77d5 100644 --- a/cli/generator.cxx +++ b/cli/generator.cxx @@ -452,6 +452,8 @@ generate (options const& ops, semantics::cli_unit& unit, path const& p) ctx.toc++; // TOC phase after restart. } } + + ctx.verify_id_ref (); } // HTML output @@ -499,6 +501,8 @@ generate (options const& ops, semantics::cli_unit& unit, path const& p) ctx.toc++; // TOC phase after restart. } } + + ctx.verify_id_ref (); } // txt output @@ -543,6 +547,8 @@ generate (options const& ops, semantics::cli_unit& unit, path const& p) ctx.toc++; // TOC phase after restart. } } + + ctx.verify_id_ref (); } auto_rm.cancel (); diff --git a/cli/source.cxx b/cli/source.cxx index 57f5922..0184da6 100644 --- a/cli/source.cxx +++ b/cli/source.cxx @@ -751,6 +751,8 @@ namespace len = max; } + + verify_id_ref (); } string up (cli + "::usage_para"); @@ -791,6 +793,8 @@ namespace os << "return p;" << "}"; + verify_id_ref (); + // Long version. // if (usage == ut_both) @@ -826,6 +830,8 @@ namespace os << "return p;" << "}"; + + verify_id_ref (); } } @@ -1126,6 +1132,8 @@ generate_source (context& ctx) os << "return p;" << "}"; + + ctx.verify_id_ref (); } // Long version. @@ -1166,6 +1174,8 @@ generate_source (context& ctx) os << "return p;" << "}"; + + ctx.verify_id_ref (); } ctx.ns_close (qn, false); diff --git a/tests/toc/toc.cli b/tests/toc/toc.cli index 5e9c43a..cb6fd8c 100644 --- a/tests/toc/toc.cli +++ b/tests/toc/toc.cli @@ -2,57 +2,56 @@ "\$TOC$" " -\h0|Preface| +\h0#preface|Preface| This document describes something awesome. -\h|About This Document| +\h#about-document|About This Document| And this document is also awesome. -\h|More Information| +\h#more-information|More Information| It is so awesome that no further information will be required." " -\H|PART I| +\H#part1|PART I| Start of part one. -\h1|Introduction| +\h1#intro|Introduction| Beginning of the first chapter. -\h|Architecture and Workflow| +\h#arch-flow|Architecture and Workflow| Some basics. -\h|Benefits| +\h#benefits|Benefits| You will like them. -\h1|Hello World| +\h1#hello|Hello World| Beginning of the second chapter. -\h|Setup| +\h#hell-setup|Setup| More basics. -\h|Compiling| +\h#hello-compile|Compiling| How to build the example -\h2|Compiling with GCC| +\h2#hello-compile-gcc|Compiling with GCC| -GCC +GCC. For Clang see \l{#hello-compile-clang Compiling with Clang}. -\h2|Compiling with Clang| +\h2#hello-compile-clang|Compiling with Clang| -Clang +Clang. For GCC see \l{#hello-compile-gcc Compiling with GCC}. -\h|Conclusion| +\h#hello-conclusion|Conclusion| Some remarks. - " diff --git a/tests/toc/toc.html b/tests/toc/toc.html index 383ff12..7da2f0c 100644 --- a/tests/toc/toc.html +++ b/tests/toc/toc.html @@ -28,55 +28,57 @@ -

Preface

+

Preface

This document describes something awesome.

-

About This Document

+

About This Document

And this document is also awesome.

-

More Information

+

More Information

It is so awesome that no further information will be required.

-

PART I

+

PART I

Start of part one.

-

Introduction

+

Introduction

Beginning of the first chapter.

-

Architecture and Workflow

+

Architecture and Workflow

Some basics.

-

Benefits

+

Benefits

You will like them.

-

Hello World

+

Hello World

Beginning of the second chapter.

-

Setup

+

Setup

More basics.

-

Compiling

+

Compiling

How to build the example

-

Compiling with GCC

+

Compiling with GCC

-

GCC

+

GCC. For Clang see Compiling with + Clang.

-

Compiling with Clang

+

Compiling with Clang

-

Clang

+

Clang. For GCC see Compiling with + GCC.

-

Conclusion

+

Conclusion

Some remarks.

-- cgit v1.1