summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cli/context.cxx137
-rw-r--r--cli/context.hxx14
-rw-r--r--cli/generator.cxx6
-rw-r--r--cli/source.cxx10
-rw-r--r--tests/toc/toc.cli31
-rw-r--r--tests/toc/toc.html32
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<block> 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 += "<h1 class=\"preface\">" + pv + "</h1>"; break;
- case 'H': v += "<h1 class=\"part\">" + pv + "</h1>"; break;
- case '1': v += "<h1>" + pv + "</h1>"; break;
- case '2': v += "<h3>" + pv + "</h3>"; break;
- case 'h': v += '<' + html_h + '>' + pv + "</" + html_h + '>';
+ 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 += "</" + h + '>';
+
// @@ 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<string> 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 @@
</td></tr>
</table>
- <h1 class="preface">Preface</h1>
+ <h1 id="preface" class="preface">Preface</h1>
<p>This document describes something awesome.</p>
- <h2>About This Document</h2>
+ <h2 id="about-document">About This Document</h2>
<p>And this document is also awesome.</p>
- <h2>More Information</h2>
+ <h2 id="more-information">More Information</h2>
<p>It is so awesome that no further information will be required.</p>
- <h1 class="part">PART I</h1>
+ <h1 id="part1" class="part">PART I</h1>
<p>Start of part one.</p>
- <h1>Introduction</h1>
+ <h1 id="intro">Introduction</h1>
<p>Beginning of the first chapter.</p>
- <h2>Architecture and Workflow</h2>
+ <h2 id="arch-flow">Architecture and Workflow</h2>
<p>Some basics.</p>
- <h2>Benefits</h2>
+ <h2 id="benefits">Benefits</h2>
<p>You will like them.</p>
- <h1>Hello World</h1>
+ <h1 id="hello">Hello World</h1>
<p>Beginning of the second chapter.</p>
- <h2>Setup</h2>
+ <h2 id="hell-setup">Setup</h2>
<p>More basics.</p>
- <h2>Compiling</h2>
+ <h2 id="hello-compile">Compiling</h2>
<p>How to build the example</p>
- <h3>Compiling with GCC</h3>
+ <h3 id="hello-compile-gcc">Compiling with GCC</h3>
- <p>GCC</p>
+ <p>GCC. For Clang see <a href="#hello-compile-clang">Compiling with
+ Clang</a>.</p>
- <h3>Compiling with Clang</h3>
+ <h3 id="hello-compile-clang">Compiling with Clang</h3>
- <p>Clang</p>
+ <p>Clang. For GCC see <a href="#hello-compile-gcc">Compiling with
+ GCC</a>.</p>
- <h2>Conclusion</h2>
+ <h2 id="hello-conclusion">Conclusion</h2>
<p>Some remarks.</p>