aboutsummaryrefslogtreecommitdiffhomepage
path: root/org
diff options
context:
space:
mode:
authorRalph Amissah <ralph.amissah@gmail.com>2026-09-11 13:57:47 -0400
committerRalph Amissah <ralph.amissah@gmail.com>2026-09-12 12:14:39 -0400
commitf41bf1cc525c70cced764a5e4df70d499d82f9c9 (patch)
tree5825a95d525fcaa332bd378c1493bbd4a14613a0 /org
parentocda: one constructor for doc_matters (diff)
spine: process a document from .ocda.db or .ssp
Process a document from its .ocda.db or .ssp abstraction. A .ssp or a .ocda.db given as an argument is now a document like any other: read rather than parsed, and handed to the output writers as the same doc they already take. The 35 document sample collection built from its .ssp files is byte identical to the same collection built from markup: 1457 files, no differences. Three things had to be rebuilt rather than read, all of them a value parsed rather than a structure derived: - classify_topic_register_arr and its expanded twin. The split is not a plain one, so the rule moved to sisudoc.ocda.meta.topic_register and both the yaml reader and this one call it. - creator_author_arr, which is creator.author split on the ", " it was joined with. - title_sub, a copy of title_subtitle made where the header is read, and what epub3 puts in dc:title id="subtitle". Fixed ordering bug found by the acceptance test. A heading's own anchor can be a bare number taken from its text and this can collide with the ocn of an unrelated object. Whole output comparison found that before the fix there was one wrong link in one epub's table of contents. From a .ocda.db, 1446 of 1457 files are identical. The 11 that are not are the images: a database carries its own image blobs and nothing yet extracts them, so the five sisu_markup images are not copied and the epub that embeds them differs. That is the next step and is not a defect in this one. --source and --pod2 are refused with a warning rather than half done, no artefact carrying the markup. (assisted by Claude-Code)
Diffstat (limited to 'org')
-rw-r--r--org/in_source_artefact.org284
-rw-r--r--org/in_source_files.org12
-rw-r--r--org/meta_conf_make_meta.org33
-rw-r--r--org/ocda.org1
-rw-r--r--org/ocda_get.org43
-rw-r--r--org/ocda_obj_setter.org64
-rw-r--r--org/spine.org54
7 files changed, 444 insertions, 47 deletions
diff --git a/org/in_source_artefact.org b/org/in_source_artefact.org
new file mode 100644
index 0000000..0f7077d
--- /dev/null
+++ b/org/in_source_artefact.org
@@ -0,0 +1,284 @@
+-*- mode: org -*-
+#+TITLE: sisudoc spine (doc_reform) markup source raw
+#+DESCRIPTION: documents - structuring, publishing in multiple formats & search
+#+FILETAGS: :spine:sourcefile:read:
+#+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/]]
+
+* A. get doc from artefact
+** _module template_ :module:config_files:
+
+#+HEADER: :tangle "../src/sisudoc/ocda/abstraction/doc_from_artefact.d"
+#+HEADER: :noweb yes
+#+BEGIN_SRC d
+<<doc_header_including_copyright_and_license>>
+module sisudoc.ocda.abstraction.doc_from_artefact;
+@safe:
+/+ ↓ a document, from an artefact rather than from markup
+
+ spineAbstraction() parses markup and hands the output writers a doc:
+ an abstraction and a doc_matters. This does the same from a .ssp or a
+ .ocda.db, filling the same docMattersMake() arguments, so that what
+ comes out is the value the writers already take and they never learn
+ which of the two made it.
+
+ Three of the seven arguments come from the run and are the same either
+ way: program_info, opt_action, and the conf half of conf_make_meta,
+ which is site and run scoped (urls, papersize, the search database) and
+ has no business in a document's own artefact. The other four come from
+ the artefact:
+
+ conf_make_meta meta and make from the @meta and @make blocks, over
+ the site config the run assembled
+ doc_has docHasFromAbstraction, over the objects
+ doc_digest the markup digest from @source
+ manifest synthesised from the artefact's path and the
+ document's own name and language
+
+ What is not here, deliberately: insert_file_list, which is read only by
+ source_pod.d for --source and --pod2. No artefact carries the markup,
+ so a pod cannot be rebuilt from one, and the empty list is the truthful
+ answer rather than a gap.
++/
+template spineDocFromArtefact() {
+ import std.algorithm : endsWith;
+ import std.array;
+ import std.conv : to;
+ import std.path;
+ import std.regex;
+ import std.stdio;
+ import std.string;
+ import sisudoc.ocda.meta.conf_make_meta_structs;
+ import sisudoc.ocda.meta.doc_matters;
+ import sisudoc.ocda.io_in.paths_source;
+ import sisudoc.ocda.abstraction.doc_has;
+ import sisudoc.ocda.abstraction.load;
+ import sisudoc.ocda.meta.topic_register;
+ mixin spineDocMatters;
+ mixin spineDocHasFromAbstraction;
+ mixin spineAbstractionLoad;
+ mixin spineTopicRegister;
+ /+ ↓ a .ssp header key from a field name: the first underscore becomes the
+ dot that separates the group, so title_main is title.main and
+ rights_copyright_text is rights.copyright_text. The writer's keys are
+ formed the same way, which is what lets this be read off the struct
+ rather than kept as a second list to fall out of step with it.
+ +/
+ private string _sspKeyOf(string _member) {
+ foreach (i, c; _member) {
+ if (c == '_') { return _member[0 .. i] ~ "." ~ _member[i + 1 .. $]; }
+ }
+ return _member;
+ }
+ /+ ↓ the document's own header, over the site config the run assembled.
+
+ every string field of MetaComposite is looked for under its own key,
+ so a field added there and emitted by the writer arrives here without
+ anything being added. The arr fields are split from the string they
+ come from, as agreed: that is parsing a value, not deriving a
+ structure.
+ +/
+ ConfComposite confMakeMetaFromHeader(S)(S _ssp, ConfComposite _site_conf) {
+ ConfComposite _out = _site_conf; // conf: site and run scoped, kept
+ static foreach (_m; __traits(allMembers, MetaComposite)) {
+ static if (is(typeof(__traits(getMember, _out.meta, _m)) == string)) {{
+ enum string _key = _sspKeyOf(_m);
+ if (auto _v = _key in _ssp.meta) { __traits(getMember, _out.meta, _m) = *_v; }
+ }}
+ }
+ /+ ↓ title_sub is a copy of title_subtitle, made where the header is
+ read, and is what epub3 puts in dc:title id="subtitle". It is not
+ emitted, being the same string twice; it is remade here the way the
+ yaml reader makes it +/
+ _out.meta.title_sub = _out.meta.title_subtitle;
+ /+ ↓ creator.author is the author list joined with ", ", so splitting on
+ it gives the list back +/
+ if (_out.meta.creator_author.length > 0) {
+ foreach (_a; _out.meta.creator_author.split(", ")) {
+ if (_a.strip.length > 0) { _out.meta.creator_author_arr ~= _a.strip; }
+ }
+ }
+ /+ ↓ the topic register arrays, by the one rule the yaml reader uses:
+ the split is not a plain one and deriving it twice would be two
+ answers to the same question +/
+ {
+ auto _tr = topicRegisterArrays(_out.meta.classify_topic_register);
+ _out.meta.classify_topic_register_arr = _tr.arr;
+ _out.meta.classify_topic_register_expanded_arr = _tr.expanded;
+ }
+ /+ ↓ make: the keys are the field names, with no group to separate +/
+ if (auto _v = "doc_type" in _ssp.make) {
+ _out.make.doc_type = *_v; _out.make_str.doc_type = *_v;
+ }
+ if (auto _v = "auto_num_top_at_level" in _ssp.make) {
+ _out.make.auto_num_top_at_level = *_v;
+ }
+ if (auto _v = "auto_num_top_lv" in _ssp.make) {
+ try { _out.make.auto_num_top_lv = (*_v).to!int; } catch (Exception ex) {}
+ }
+ if (auto _v = "auto_num_depth" in _ssp.make) {
+ try { _out.make.auto_num_depth = (*_v).to!int; } catch (Exception ex) {}
+ }
+ if (auto _v = "breaks" in _ssp.make) {
+ _out.make.breaks = *_v; _out.make_str.breaks = *_v;
+ }
+ if (auto _v = "home_button_text" in _ssp.make) {
+ _out.make.home_button_text = *_v; _out.make_str.home_button_text = *_v;
+ }
+ /+ ↓ footer is a list, joined with the separator the format escapes +/
+ if (auto _v = "footer" in _ssp.make) {
+ _out.make.footer = sspUnescapeSplit(*_v);
+ _out.make_str.footer = _out.make.footer;
+ }
+ return _out;
+ }
+ /+ ↓ where a document loaded from an artefact says it is.
+
+ an artefact names its document (% Source:) and its language (@source
+ language) but not where the pod holding its images sits, so that is
+ taken from where the artefact itself was found:
+
+ pod/<doc>/media/abstraction/<uid>.ssp -> pod/<doc>/
+ pod/<uid>.ocda.db -> pod/<doc>/
+
+ which is where --pod2 puts them both. The images beside a .ssp are
+ then found where html.d already looks; a .ocda.db carries its own and
+ does not need the directory to exist.
+ +/
+ struct ST_ArtefactPaths {
+ string pod_dir;
+ string src_file_with_path;
+ }
+ ST_ArtefactPaths artefactPaths(S)(string _artefact, S _ssp) {
+ ST_ArtefactPaths _out;
+ string _lang = ("language" in _ssp.source_info) ? _ssp.source_info["language"] : "en";
+ string _dir = _artefact.dirName;
+ if (_artefact.endsWith(".ssp")) {
+ /+ ↓ <pod>/media/abstraction/<uid>.ssp: two directories up is the pod +/
+ _out.pod_dir = (_dir.baseName == "abstraction")
+ ? _dir.dirName.dirName : _dir;
+ } else {
+ /+ ↓ <dir>/<uid>.ocda.db, the pod tree beside it named for the document +/
+ string _uid = _artefact.baseName;
+ foreach (_sfx; [".ocda.db", ".db"]) {
+ if (_uid.endsWith(_sfx)) { _uid = _uid[0 .. $ - _sfx.length]; break; }
+ }
+ if (_uid.endsWith("." ~ _lang)) { _uid = _uid[0 .. $ - _lang.length - 1]; }
+ _out.pod_dir = (_dir.chainPath(_uid).array).to!string;
+ }
+ _out.src_file_with_path = (_out.pod_dir
+ .chainPath("media").chainPath("text").chainPath(_lang)
+ .chainPath(_ssp.source).array).to!string;
+ return _out;
+ }
+ /+ ↓ the doc the output writers take, from an artefact.
+
+ the reading is done here rather than by the caller, and that is not
+ only tidiness: main() is @system, so a template mixed in there infers
+ @system for everything it declares, and the output writers are @safe
+ and will not take objects of such a type. Doing it inside this module,
+ which is @safe:, is what makes the loaded objects the same kind of
+ thing the parser's are. Called as spineDocFromArtefact!()(...), the
+ way spineAbstraction!() is, rather than mixed in.
+ +/
+ auto spineDocFromArtefact(P,O,E)(
+ string _artefact,
+ P program_info,
+ O _opt_action,
+ E _env,
+ ConfComposite _site_conf,
+ ) {
+ auto _loaded = abstractionLoad(_artefact);
+ auto _ssp = _loaded.doc;
+ auto _pths = artefactPaths(_artefact, _ssp);
+ auto _manifest = PathMatters!()(_opt_action, _env, _pths.pod_dir, _pths.src_file_with_path);
+ auto _cmm = confMakeMetaFromHeader(_ssp, _site_conf);
+ auto _has = docHasFromAbstraction(_ssp.abstraction, _ssp.doc_has, _opt_action);
+ /+ ↓ the digests: the markup digest is what @source carries, and it is
+ the one that says which source this abstraction was made from. The
+ header and text digests are of parts an artefact does not keep
+ separately, and are left unset rather than guessed at
+ +/
+ ST_ArtefactDigest _dig;
+ if (auto _v = "digest" in _ssp.source_info) {
+ _dig.markup_doc = _hexToDigest(*_v);
+ }
+ auto _abstraction = _ssp.abstraction;
+ auto _matters = docMattersMake(
+ program_info, _opt_action, _manifest, _cmm, _has, _dig, string[].init,
+ );
+ /+ ↓ the same shape spineAbstraction() returns, built the same way: a
+ struct of accessors over the locals, with whether the artefact
+ could be read at all, which the caller has to know +/
+ auto theDOC() {
+ struct ST_DOC {
+ const auto abstraction() { return _abstraction; }
+ auto matters() { return _matters; }
+ bool loaded() { return _loaded.loaded; }
+ string note() { return _loaded.note; }
+ string source_name() { return abstractionSourceName(_loaded.source); }
+ }
+ return ST_DOC();
+ }
+ return theDOC();
+ }
+ /+ ↓ the digests doc_matters carries. Only markup_doc can be filled from
+ an artefact, @source having recorded it; the header and text digests
+ are of parts no artefact keeps separately and are left unset rather
+ than guessed at. Shaped to match ST_doc_digest, which is declared
+ inside spineRawMarkupContent and reachable only by parsing a file.
+ +/
+ struct ST_ArtefactDigest {
+ ubyte[32] markup_doc;
+ ubyte[32] header;
+ ubyte[32] text;
+ }
+ private ubyte[32] _hexToDigest(string _hex) {
+ ubyte[32] _out;
+ if (_hex.length < 64) { return _out; }
+ foreach (i; 0..32) {
+ try { _out[i] = _hex[i*2 .. i*2+2].to!ubyte(16); } catch (Exception ex) {}
+ }
+ 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__
diff --git a/org/in_source_files.org b/org/in_source_files.org
index 4ba1b10..7d869c0 100644
--- a/org/in_source_files.org
+++ b/org/in_source_files.org
@@ -798,7 +798,7 @@ module sisudoc.ocda.abstraction.ssp_in;
record) and require the result to be byte identical to the file read.
+/
template spineAbstractionRead() {
- import std.algorithm : startsWith;
+ import std.algorithm : startsWith, findSplit;
import std.array;
import std.conv : to;
import std.file;
@@ -1136,7 +1136,7 @@ template spineAbstractionRead() {
the digest is going to be recorded, and on the largest document in the
sample set it is a tenth of what reading the file costs
+/
- SSPdocument sspReadFile(string file_path, bool with_digest = false) {
+ @trusted SSPdocument sspReadFile(string file_path, bool with_digest = false) {
string _text;
string[] _lines;
try {
@@ -1184,7 +1184,7 @@ template spineAbstractionDbRead() {
import d2sqlite3;
import sisudoc.ocda.abstraction.ssp_in : spineAbstractionRead;
mixin spineAbstractionRead; // for SSPdocument, and the shared unescape
- private int[8] _jsonEight(string _s) {
+ @trusted private int[8] _jsonEight(string _s) {
int[8] _out;
if (_s.length == 0) { return _out; }
try {
@@ -1195,7 +1195,7 @@ template spineAbstractionDbRead() {
} catch (Exception ex) {}
return _out;
}
- private int[] _jsonInts(string _s) {
+ @trusted private int[] _jsonInts(string _s) {
int[] _out;
if (_s.length == 0) { return _out; }
try {
@@ -1204,7 +1204,7 @@ template spineAbstractionDbRead() {
} catch (Exception ex) {}
return _out;
}
- private string[] _jsonStrs(string _s) {
+ @trusted private string[] _jsonStrs(string _s) {
string[] _out;
if (_s.length == 0) { return _out; }
try {
@@ -1213,7 +1213,7 @@ template spineAbstractionDbRead() {
} catch (Exception ex) {}
return _out;
}
- private double[] _jsonDoubles(string _s) {
+ @trusted private double[] _jsonDoubles(string _s) {
double[] _out;
if (_s.length == 0) { return _out; }
try {
diff --git a/org/meta_conf_make_meta.org b/org/meta_conf_make_meta.org
index f9c13ef..55b7705 100644
--- a/org/meta_conf_make_meta.org
+++ b/org/meta_conf_make_meta.org
@@ -632,6 +632,7 @@ template contentYAMLtoSpineStruct() {
import sisudoc.ocda.meta.defaults;
import sisudoc.ocda.meta.rgx_yaml;
import sisudoc.ocda.meta.rgx;
+ import sisudoc.ocda.meta.topic_register;
ConfComposite _struct_composite;
@system ConfComposite contentYAMLtoSpineStruct(C,Y,M,O,Cfg)(
C _struct_composite,
@@ -644,6 +645,7 @@ template contentYAMLtoSpineStruct() {
mixin spineRgxIn;
static auto rgx = RgxI();
mixin spineRgxYamlTags;
+ mixin spineTopicRegister;
static auto rgx_y = RgxYaml();
<<meta_check_input_markup>>
<<meta_extract_yaml>>
@@ -1277,32 +1279,13 @@ if ("classify" in _yaml
.replaceAll(wrds, "\"$1\"")
.replaceAll(mkp_delim, ":$1$2");
}
- string[] main_topics_ = _struct_composite.meta.classify_topic_register.strip.split(rgx.topic_register_main_terms_split);
- string[] topics;
- string topics_tmp;
- string[] multiple_sub_terms;
- foreach (mt; main_topics_) {
- topics_tmp = mt.replaceAll(rgx.topic_register_main_term_plus_rest_split, mkup.sep);
- if (auto m = topics_tmp.match(rgx.topic_register_multiple_sub_terms_split)) {
- multiple_sub_terms = m.captures[1].split(rgx.topic_register_sub_terms_split);
- foreach (subterm; multiple_sub_terms) {
- topics ~= m.captures.pre ~ mkup.sep ~ subterm;
- }
- } else {
- topics ~= topics_tmp;
- }
- }
- _struct_composite.meta.classify_topic_register_arr = topics;
- string[] topics_expanded;
- if (_struct_composite.meta.classify_topic_register_arr.length > 0) {
- foreach (i, topic; _struct_composite.meta.classify_topic_register_arr) {
- string[] subject_tree = topic.split(mkup.sep);
- if (topic.length > 0) {
- topics_expanded ~= subject_tree.join(", ");
- }
- }
+ /+ ↓ one rule, shared with a document loaded from an artefact,
+ whose header carries the string but not the arrays +/
+ {
+ auto _tr = topicRegisterArrays(_struct_composite.meta.classify_topic_register);
+ _struct_composite.meta.classify_topic_register_arr = _tr.arr;
+ _struct_composite.meta.classify_topic_register_expanded_arr = _tr.expanded;
}
- _struct_composite.meta.classify_topic_register_expanded_arr = topics_expanded;
// writeln("\n------\n", _struct_composite.meta.title_full);
// writeln(_struct_composite.meta.classify_topic_register);
// writeln(_struct_composite.meta.classify_topic_register_expanded_arr.sort.join("\n"));
diff --git a/org/ocda.org b/org/ocda.org
index db24139..ee490f4 100644
--- a/org/ocda.org
+++ b/org/ocda.org
@@ -97,6 +97,7 @@ public import sisudoc.ocda.abstraction.ssp_in; // spineAbstractionRead (.ssp
public import sisudoc.ocda.abstraction.db_in; // spineAbstractionDbRead (.db back in)
public import sisudoc.ocda.abstraction.load; // spineAbstractionLoad (one way in)
public import sisudoc.ocda.abstraction.doc_has; // spineDocHasFromAbstraction (ST_DocHas from a loaded abstraction)
+public import sisudoc.ocda.abstraction.doc_from_artefact; // spineDocFromArtefact (a doc, from a .ssp or a .ocda.db)
#+END_SRC
** _module template_ :module:metadoc_from_src:
diff --git a/org/ocda_get.org b/org/ocda_get.org
index 64fff57..40effc5 100644
--- a/org/ocda_get.org
+++ b/org/ocda_get.org
@@ -165,22 +165,6 @@ template spineDocHasFromAbstraction() {
_tag_assoc[obj.tags.segment_anchor_tag_epub]["seg_lv1to4"]
= obj.tags.segment_anchor_tag_epub;
}
- /+ ↓ the body's objects are what a citation or a cross reference
- points at, so each one's identifier maps to the segment it is
- in. seg_lv4 is only set where it is not already, since an
- anchor of the same name set above names a segment rather than
- sits in one
- +/
- if (_section == "body" && obj.metainfo.identifier.length > 0) {
- if (!((obj.metainfo.identifier in _tag_assoc)
- && ("seg_lv4" in _tag_assoc[obj.metainfo.identifier]))
- ) {
- _tag_assoc[obj.metainfo.identifier]["seg_lv4"]
- = obj.tags.html_segment_anchor_tag_is;
- }
- _tag_assoc[obj.metainfo.identifier]["seg_lv1to4"]
- = obj.tags.epub_segment_anchor_tag_is;
- }
/+ ↓ a heading's own anchor tags, and the anchors declared inline in
an object's text, both point at the segment the object is in +/
/+ ↓ a heading above level 4 opens no html segment of its own, so a
@@ -237,6 +221,33 @@ template spineDocHasFromAbstraction() {
foreach (_section; tail_sections) {
if (_has_section(_abst, _section)) { _out.segnames_lv4 ~= _section; }
}
+ /+ ↓ the body's objects, in a pass of their own after every anchor above
+ has been recorded, because the parser does it in that order and the
+ order is what decides two of these entries.
+ .
+ each body object's identifier maps to the segment it is in. seg_lv4
+ is set only where it is not already, an anchor of the same name
+ having said it *names* a segment rather than sits in one; seg_lv1to4
+ is set always. A heading's own anchor can be a bare number taken
+ from its text ("2. Shorter Terms" gives the anchor "2"), which then
+ collides with the ocn of an unrelated object, and this is the order
+ that resolves the collision the way the parser resolves it: in
+ free_culture, ocn 2 keeps seg_lv4 "them" from the anchor and takes
+ seg_lv1to4 "_part_1" from itself.
+ +/
+ if (_has_section(_abst, "body")) {
+ foreach (obj; _abst["body"]) {
+ if (obj.metainfo.identifier.length == 0) { continue; }
+ if (!((obj.metainfo.identifier in _tag_assoc)
+ && ("seg_lv4" in _tag_assoc[obj.metainfo.identifier]))
+ ) {
+ _tag_assoc[obj.metainfo.identifier]["seg_lv4"]
+ = obj.tags.html_segment_anchor_tag_is;
+ }
+ _tag_assoc[obj.metainfo.identifier]["seg_lv1to4"]
+ = obj.tags.epub_segment_anchor_tag_is;
+ }
+ }
_out.imagelist = _images.sort.uniq.array;
_out.tag_associations = _tag_assoc;
_out.section_keys_sequenced = sectionKeysSequenced(_abst, _opt_action);
diff --git a/org/ocda_obj_setter.org b/org/ocda_obj_setter.org
index e0e7363..cd90f36 100644
--- a/org/ocda_obj_setter.org
+++ b/org/ocda_obj_setter.org
@@ -505,6 +505,70 @@ static auto eN() {
}
#+END_SRC
+* topic register
+
+#+HEADER: :tangle "../src/sisudoc/ocda/meta/topic_register.d"
+#+HEADER: :noweb yes
+#+BEGIN_SRC d
+<<doc_header_including_copyright_and_license>>
+module sisudoc.ocda.meta.topic_register;
+@safe:
+/+ ↓ the topic register, as the two arrays the curate writers walk
+ .
+ classify.topic_register is authored as one string, and the topic index
+ needs it as a list of paths and as a list of readable labels. The rule
+ is not a plain split: a main term is separated from the rest, sub terms
+ divided by "|" become one entry each, and the parts are joined with the
+ internal separator that the writers then split on again.
+ .
+ It lives here because two callers need the same answer from the same
+ string: the yaml reader, which has just read it from a document header,
+ and a document loaded from a .ssp or a .ocda.db, whose header carries
+ the string but not the arrays. Deriving it twice is how two answers to
+ one question get into a program.
++/
+template spineTopicRegister() {
+ import std.algorithm : sort, uniq;
+ import std.array;
+ import std.regex;
+ import std.string;
+ import sisudoc.ocda.meta.rgx;
+ import sisudoc.ocda.meta.defaults;
+ mixin spineRgxIn;
+ mixin InternalMarkup;
+ struct ST_TopicRegister {
+ string[] arr; // topic paths, joined with the internal separator
+ string[] expanded; // the same, as readable ", " separated labels
+ }
+ ST_TopicRegister topicRegisterArrays(string _topic_register) {
+ static auto rgx = RgxI();
+ auto mkup = InlineMarkup();
+ ST_TopicRegister _out;
+ if (_topic_register.length == 0) { return _out; }
+ string[] _main_topics = _topic_register.strip.split(rgx.topic_register_main_terms_split);
+ string[] _topics;
+ string _topics_tmp;
+ string[] _multiple_sub_terms;
+ foreach (_mt; _main_topics) {
+ _topics_tmp = _mt.replaceAll(rgx.topic_register_main_term_plus_rest_split, mkup.sep);
+ if (auto m = _topics_tmp.match(rgx.topic_register_multiple_sub_terms_split)) {
+ _multiple_sub_terms = m.captures[1].split(rgx.topic_register_sub_terms_split);
+ foreach (_subterm; _multiple_sub_terms) {
+ _topics ~= m.captures.pre ~ mkup.sep ~ _subterm;
+ }
+ } else {
+ _topics ~= _topics_tmp;
+ }
+ }
+ _out.arr = _topics;
+ foreach (_topic; _out.arr) {
+ if (_topic.length > 0) { _out.expanded ~= _topic.split(mkup.sep).join(", "); }
+ }
+ return _out;
+ }
+}
+#+END_SRC
+
* org includes
** project version
diff --git a/org/spine.org b/org/spine.org
index 3ff3c05..d3a5276 100644
--- a/org/spine.org
+++ b/org/spine.org
@@ -98,6 +98,54 @@ string program_name = "spine";
}
}
}
+ /+ ↓ documents given as an abstraction artefact rather than as markup.
+ .ssp and .ocda.db are read, not parsed, and what comes back is the
+ doc the output writers already take, so this loop is the markup one
+ with its first step replaced. It is serial: the artefacts are read
+ one at a time and there is nothing here that parallelising would
+ help with, the reading being the cheap half.
+ --source and --pod2 are refused rather than half done: no artefact
+ carries the markup, so a pod cannot be rebuilt from one.
+ +/
+ if (_artefact_args.length > 0 && _opt_action.abstraction) {
+ import sisudoc.ocda.abstraction.doc_from_artefact;
+ if (!(_opt_action.skip_output)) {
+ outputHubInitialize!()(_opt_action, program_info);
+ }
+ if (_opt_action.source_or_pod) {
+ writeln("WARNING: --source and --pod2 are not available from an abstraction",
+ " source: no artefact carries the markup. Skipped for: ",
+ _artefact_args.join(", "));
+ }
+ foreach (_artefact; _artefact_args) {
+ auto doc = spineDocFromArtefact!()(
+ _artefact, program_info, _opt_action, _env, _make_and_meta_struct,
+ );
+ if (!doc.loaded) {
+ stderr.writeln("ERROR: could not load ", _artefact,
+ (doc.note.length > 0) ? ": " ~ doc.note : "");
+ continue;
+ }
+ if (_opt_action.vox_gt_1) {
+ writeln("-- ~ document from ", doc.source_name,
+ " ~ ------------------------------------");
+ writeln(" ", _artefact);
+ }
+ if (doc.matters.opt.action.curate) {
+ auto _hvst = spineMetaDocCurate!()(doc.matters, hvst);
+ if (_hvst.title.length > 0 && _hvst.author_surname_fn.length > 0) {
+ hvst.curates ~= _hvst;
+ }
+ }
+ if (!(doc.matters.opt.action.skip_output)) {
+ doc.outputHub!();
+ }
+ if (_opt_action.vox_gt_1) {
+ writeln("processed artefact: ", _artefact.baseName,
+ " [", doc.matters.src.language, "]");
+ }
+ }
+ }
if (hvst.curates.length > 0) {
if (_opt_action.curate_topics) {
spineMetaDocCuratesTopics!()(hvst, _make_and_meta_struct, _opt_action);
@@ -1218,6 +1266,7 @@ auto _env = [
#+BEGIN_SRC d
auto _manifested = PathMatters!()(_opt_action, _env, "");
auto _manifests = [ _manifested ];
+string[] _artefact_args; /+ .ssp and .ocda.db given on the command line +/
auto _conf_file_details = configFilePaths!()(_manifested, _env, _opt_action.config_path_set);
/+ ↓ track extracted zip pod temp directories for cleanup +/
mixin spineExtractZipPod;
@@ -1613,6 +1662,11 @@ foreach(arg; _resolved_args) {
}
}
}
+ } else if (arg.endsWith(".ssp") || arg.endsWith(".ocda.db")) {
+ /+ ↓ an abstraction artefact: the document is read rather than
+ parsed, and joins the run as a document like any other
+ +/
+ _artefact_args ~= arg;
} else { // anything remaining, unused
arg_unrecognized ~= " " ~ arg;
}