-*- mode: org -*- #+TITLE: sisudoc spine (doc_reform) object-centric document abstraction #+DESCRIPTION: documents - structuring, publishing in multiple formats & search #+SUMMARY: process markup document, create document abstraction #+FILETAGS: :spine:abstraction: #+AUTHOR: Ralph Amissah #+EMAIL: [[mailto:ralph.amissah@gmail.com][ralph.amissah@gmail.com]] #+COPYRIGHT: Copyright (C) 2015 (continuously updated, current 2026) Ralph Amissah #+LANGUAGE: en #+STARTUP: content hideblocks hidestars noindent entitiespretty #+PROPERTY: header-args+ :eval never-export :exports code #+PROPERTY: header-args+ :noweb yes :padline no #+PROPERTY: header-args+ :results silent :cache no #+PROPERTY: header-args+ :mkdirp yes #+OPTIONS: H:3 num:nil toc:t \n:t ::t |:t ^:nil -:t f:t *:t - magic single double-quote → " ← FIX changes hilighting behavior (occuring after it) in org document. INVESTIGATE (org-mode CONFIG?) FIND & FIX - [[./doc-reform.org][doc-reform.org]] [[./][org/]] * reserved file names #+HEADER: :tangle "../src/sisudoc/ocda/meta/reserved_names.d" #+HEADER: :noweb yes #+BEGIN_SRC d <> /++ the segment names spine keeps for itself

the one list of them, and the check that says when a document's own heading has claimed one

[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; } } #+END_SRC * org includes ** project version #+NAME: spine_version #+HEADER: :noweb yes #+BEGIN_SRC emacs-lisp <<./sisudoc_spine_version_info_and_doc_header_including_copyright_and_license.org:spine_project_version()>> #+END_SRC ** year #+NAME: year #+HEADER: :noweb yes #+BEGIN_SRC emacs-lisp <<./sisudoc_spine_version_info_and_doc_header_including_copyright_and_license.org:year()>> #+END_SRC ** document header including copyright & license #+NAME: doc_header_including_copyright_and_license #+HEADER: :noweb yes #+BEGIN_SRC emacs-lisp <<./sisudoc_spine_version_info_and_doc_header_including_copyright_and_license.org:spine_doc_header_including_copyright_and_license()>> #+END_SRC * __END__