aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/sisudoc/ocda
diff options
context:
space:
mode:
authorRalph Amissah <ralph.amissah@gmail.com>2026-09-21 16:12:56 -0400
committerRalph Amissah <ralph.amissah@gmail.com>2026-09-23 08:22:15 -0400
commit8b81e9a11c7af642c4d0d8584a9cb348cc887d6e (patch)
tree8ba9c356ff71bf18f5623c22d30a8b2513321cfe /src/sisudoc/ocda
parentuid and paths separator "~" in place of ":" (diff)
ocda: warn if heading claims reserved segment name
spine automatically builds some segments, including: (toc, endnotes, glossary, bibliography, bookindex, blurb, _the_title) these are now identified as reserved names and a user is now warned if any of these names have been manually assigned to a heading by markup. A document still builds but to disambiguate the ocn of the heading is attached to the markup (reserved) name and this is seeded before a document not after. It is read off the finished abstraction rather than reported by the parser, and beside the ocn alignment check for the same reason: it is a statement about a document rather than a step in building one. WARNING reserved segment name: the_autonomous_contract... [en] heading 1 at ocn 135 asks for "endnotes", which spine gives its own generated section it is named "endnotes-135" instead; ... A warning: the document is correct and complete and the name it ends up with works. --strict makes it a failure for the run, as it does for ocn alignment, and by the same reasoning: the outputs are written and can be looked at, and the exit status is taken at the end. Two of the thirty-six sample documents have reserved segment names, "1~endnotes" heading. Output is unchanged: nothing here touches the abstraction. (assisted by Claude-Code)
Diffstat (limited to 'src/sisudoc/ocda')
-rw-r--r--src/sisudoc/ocda/meta/metadoc_from_src.d12
-rw-r--r--src/sisudoc/ocda/meta/metadoc_from_src_functions.d15
-rw-r--r--src/sisudoc/ocda/meta/reserved_names.d206
3 files changed, 225 insertions, 8 deletions
diff --git a/src/sisudoc/ocda/meta/metadoc_from_src.d b/src/sisudoc/ocda/meta/metadoc_from_src.d
index 2731573..3670a39 100644
--- a/src/sisudoc/ocda/meta/metadoc_from_src.d
+++ b/src/sisudoc/ocda/meta/metadoc_from_src.d
@@ -97,6 +97,18 @@ template docAbstraction() {
anchor_tag = "";
_heading_anchor_tags_seen = _reserved_heading_anchor_tags();
}
+ /+ ↓ seeded here, on the way in, which is the seeding that matters.
+ .
+ The two assignments in scope(exit) and at the tail of this function run
+ after a document has been abstracted, so they seed the *next* one. The
+ first document of a run had nothing before it and began with the set as
+ declared, empty: it alone was not held to the reserved names, and a heading
+ of its own claiming "endnotes" kept the name of the generated section.
+ Which document that was depended on what else was in the run, so a
+ document's abstraction depended on its company, and a single document
+ re-parsed to check an artefact made in a collection run disagreed with it.
+ +/
+ _heading_anchor_tags_seen = _reserved_heading_anchor_tags();
mixin spineNode;
int[string] node_para_int_ = node_metadata_para_int;
string[string] node_para_str_ = node_metadata_para_str;
diff --git a/src/sisudoc/ocda/meta/metadoc_from_src_functions.d b/src/sisudoc/ocda/meta/metadoc_from_src_functions.d
index 0deeb72..c5a63cd 100644
--- a/src/sisudoc/ocda/meta/metadoc_from_src_functions.d
+++ b/src/sisudoc/ocda/meta/metadoc_from_src_functions.d
@@ -77,16 +77,15 @@ template docAbstractionFunctions() {
these names means the document's heading is disambiguated the same
way a repeated anchor tag is, and the generated section keeps the
name that the table of contents links to.
+ .
+ The list itself is in sisudoc.ocda.meta.reserved_names, with the
+ check that reports such a heading to its author. Two lists would be
+ two answers to what spine has taken.
+/
bool[string] _reserved_heading_anchor_tags() {
- bool[string] _reserved;
- foreach (_name; [
- "toc", "endnotes", "glossary", "bibliography", "bookindex", "blurb",
- "_the_title",
- ]) {
- _reserved[_name] = true;
- }
- return _reserved;
+ import sisudoc.ocda.meta.reserved_names;
+ mixin spineReservedNames _rn;
+ return _rn.reservedHeadingAnchorTags();
}
string lev_anchor_tag;
string[string][string] tag_assoc;
diff --git a/src/sisudoc/ocda/meta/reserved_names.d b/src/sisudoc/ocda/meta/reserved_names.d
new file mode 100644
index 0000000..8e49ae5
--- /dev/null
+++ b/src/sisudoc/ocda/meta/reserved_names.d
@@ -0,0 +1,206 @@
+/+
+- Name: SisuDoc Spine, Doc Reform [a part of]
+ - Description: documents, structuring, processing, publishing, search
+ - static content generator
+
+ - Author: Ralph Amissah
+ [ralph.amissah@gmail.com]
+
+ - Copyright: (C) 2015 (continuously updated, current 2026) Ralph Amissah, All Rights Reserved.
+
+ - License: AGPL 3 or later:
+
+ Spine (SiSU), a framework for document structuring, publishing and
+ search
+
+ Copyright (C) Ralph Amissah
+
+ This program is free software: you can redistribute it and/or modify it
+ under the terms of the GNU AFERO General Public License as published by the
+ Free Software Foundation, either version 3 of the License, or (at your
+ option) any later version.
+
+ This program is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ more details.
+
+ You should have received a copy of the GNU General Public License along with
+ this program. If not, see [https://www.gnu.org/licenses/].
+
+ If you have Internet connection, the latest version of the AGPL should be
+ available at these locations:
+ [https://www.fsf.org/licensing/licenses/agpl.html]
+ [https://www.gnu.org/licenses/agpl.html]
+
+ - Spine (by Doc Reform, related to SiSU) uses standard:
+ - docReform markup syntax
+ - standard SiSU markup syntax with modified headers and minor modifications
+ - docReform object numbering
+ - standard SiSU object citation numbering & system
+
+ - Homepages:
+ [https://www.sisudoc.org]
+ [https://www.doc-reform.org]
+
+ - Git
+ [https://git.sisudoc.org/]
+
++/
+/++
+ the segment names spine keeps for itself<br><br>
+
+ the one list of them, and the check that says when a document's own
+ heading has claimed one<br><br>
+
+ [sisudoc.ocda.meta.reserved_names]
++/
+module sisudoc.ocda.meta.reserved_names;
+@safe:
+/+ ↓ spine names the sections it generates, these names are reserved.
+ .
+ The table of contents, the gathered endnotes, the glossary, the
+ bibliography, the book index and the blurb are not in the markup: spine
+ makes them, and gives each a segment name of its own. A document whose
+ own heading asks for one of those names ("1~endnotes Endnote") is asking
+ for a name that is already taken, and before this was noticed both wrote
+ into the same file: the generated section and the author's heading, with
+ the navigation listing that file twice, the second time behind its own
+ earlier entry in reading order.
+ .
+ The parser therefore seeds the set of heading anchor names it has seen
+ with these before it reads a document, so that a heading claiming one is
+ disambiguated by ocn suffix the same way a repeated anchor tag is, and
+ the generated section keeps the name the table of contents links to.
+ .
+ Two things live here rather than in the parser:
+ .
+ the list so that the parser's seeding and the check below cannot
+ come to hold different ideas of what is reserved;
+ .
+ the check which runs over a finished abstraction, names the
+ headings that were renamed, and is what tells an author
+ that a heading of theirs is not called what they wrote.
+ A warning: the document is correct and complete, and the
+ name it ends up with works. --strict makes it a failure,
+ for the run that is meant to be publishable.
+ .
+ The renaming is not something the author has to accept. A heading given
+ any other anchor name ("1~my-notes Endnote") keeps it, and the warning
+ goes away.
++/
+/+ ↓ mixed in as a named mixin (mixin spineReservedNames _name;) and its
+ imports are inside its functions rather than at template scope: what a
+ mixin template declares at its own scope is visible in the scope it is
+ mixed into, and an import there can hijack a name the host was already
+ resolving by UFCS.
++/
+template spineReservedNames() {
+ /+ ↓ the names spine makes for itself, in one place.
+ _the_title is the head section's own; the rest are the generated
+ sections, and are the section keys of the abstraction.
+ +/
+ string[] reservedHeadingAnchorNames() {
+ return [
+ "toc", "endnotes", "glossary", "bibliography", "bookindex", "blurb",
+ "_the_title",
+ ];
+ }
+ /+ ↓ the same, as the set the parser seeds itself with +/
+ bool[string] reservedHeadingAnchorTags() {
+ bool[string] _reserved;
+ foreach (_name; reservedHeadingAnchorNames()) { _reserved[_name] = true; }
+ return _reserved;
+ }
+ /+ ↓ one heading that asked for a name spine had already taken +/
+ struct ST_ReservedNameUse {
+ string section; // where in the abstraction it sits
+ string reserved; // the name it asked for
+ string anchor; // the name it was given instead
+ string level; // as marked up, :A :B 1 2 ...
+ int ocn;
+ }
+ /+ ↓ was this heading renamed out of the way of a reserved name?
+ .
+ Read off the anchor rather than reported by the parser: the parser
+ renames by appending the object's number, so "endnotes" becomes
+ "endnotes-135" on the object whose ocn is 135, and that pairing is what
+ identifies it. Requiring the number to be the object's own is what keeps
+ a heading an author really did call "endnotes-135" from being reported as
+ something it is not.
+ +/
+ private string _reservedNameClaimedBy(O)(O obj) {
+ import std.conv : to;
+ string _anchor = obj.tags.anchor_tag_html;
+ if (_anchor.length == 0) { return ""; }
+ string _suffix = "-" ~ obj.metainfo.ocn.to!string;
+ if (_anchor.length <= _suffix.length) { return ""; }
+ if (_anchor[($ - _suffix.length) .. $] != _suffix) { return ""; }
+ string _stem = _anchor[0 .. ($ - _suffix.length)];
+ foreach (_name; reservedHeadingAnchorNames()) {
+ if (_stem == _name) { return _name; }
+ }
+ return "";
+ }
+ /+ ↓ every heading of a document that claimed a reserved name.
+ document order, the section order the .ssp is written in, so that two
+ runs over the same document report it the same way round.
+ +/
+ ST_ReservedNameUse[] reservedNameHeadings(D)(D doc) {
+ import std.algorithm : canFind, sort;
+ import std.conv : to;
+ ST_ReservedNameUse[] _out;
+ string[] _sections = ["head", "toc", "body", "endnotes",
+ "glossary", "bibliography", "bookindex", "blurb", "tail"];
+ string[] _extra;
+ foreach (_k; doc.abstraction.byKey) {
+ if (!_sections.canFind(_k)) { _extra ~= _k; }
+ }
+ foreach (_k; _extra.sort) { _sections ~= _k; }
+ foreach (section; _sections) {
+ if (section !in doc.abstraction) { continue; }
+ foreach (obj; doc.abstraction[section]) {
+ if (obj.metainfo.is_a != "heading") { continue; }
+ string _claimed = _reservedNameClaimedBy(obj);
+ if (_claimed.length == 0) { continue; }
+ /+ ↓ the generated sections carry the reserved names themselves and are
+ not what this is about: they are headings spine made, and they keep
+ the plain name. Only an object that was renamed reaches here, and a
+ generated one never is.
+ +/
+ ST_ReservedNameUse _u;
+ _u.section = section;
+ _u.reserved = _claimed;
+ _u.anchor = obj.tags.anchor_tag_html;
+ _u.level = obj.metainfo.marked_up_level;
+ _u.ocn = obj.metainfo.ocn.to!int;
+ _out ~= _u;
+ }
+ }
+ return _out;
+ }
+ /+ ↓ the report, as the lines to print.
+ the document is named once and its headings indented under it, as the ocn
+ alignment check does, so that a run over a collection reads as a list of
+ documents.
+ +/
+ string[] reservedNameReportLines(
+ string doc_key,
+ string lang,
+ ST_ReservedNameUse[] _uses,
+ ) {
+ import std.conv : to;
+ string[] _out;
+ if (_uses.length == 0) { return _out; }
+ _out ~= "WARNING reserved segment name: " ~ doc_key
+ ~ ((lang.length > 0) ? " [" ~ lang ~ "]" : "");
+ foreach (_u; _uses) {
+ _out ~= " heading " ~ _u.level ~ " at ocn " ~ _u.ocn.to!string
+ ~ " asks for \"" ~ _u.reserved
+ ~ "\", which spine gives its own generated section";
+ _out ~= " it is named \"" ~ _u.anchor
+ ~ "\" instead; give the heading another name to keep one of your own";
+ }
+ return _out;
+ }
+}