From 28b27ec45b9a592be3187d995b4355f483e53cc1 Mon Sep 17 00:00:00 2001 From: Ralph Amissah Date: Mon, 7 Sep 2026 11:18:52 -0400 Subject: ocda: a reader for .ssp, and a round trip check sisudoc.ocda.abstraction.ssp_in reads a .ssp file back into ObjGenericComposite[][string], the same value the parser produces, so anything that consumes the abstraction can be fed from a .ssp instead of from markup. The three header blocks come back as key/value with their order preserved. --ssp-round-trip= loads a file and emits it again on stdout, using sspObjectRecord, the writer's own definition of a record. So the check is against the writer, not against a second description of the format: ./bin/spine-ldc --ssp-round-trip=test/reference/abstraction/.ssp \ | diff test/reference/abstraction/.ssp - BUG as yet to FIX 27 of the 35 reference documents round trip byte identically. The other 8 fail on two defects in the *writer* that the round trip found, and which are left for a decision: 1. .heading_ancestors_text and .lev4_subtoc can carry a raw newline, because a heading's text may contain a line break. The value then spans two physical lines and the format's rule that a value runs to the end of the line is broken. 194 and 22 occurrences, in the seven live-manual translations. 2. .heading_ancestors_text joins its eight slots with "|" while the text in them may itself contain "|". 22 occurrences in revisiting_the_autonomous_contract. Both need an escape (or normalisation at source) and both change the .ssp, so require a decision and another reference regeneration. ocda: export the .ssp reader from the abstraction package package.d is the re-export surface for consumers that want to reach the abstraction without depending on the directory layout; the reader belongs there beside the writer. (assisted by Claude-Code) --- src/sisudoc/spine.d | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) (limited to 'src/sisudoc/spine.d') diff --git a/src/sisudoc/spine.d b/src/sisudoc/spine.d index 17950d0..9ceb069 100644 --- a/src/sisudoc/spine.d +++ b/src/sisudoc/spine.d @@ -213,6 +213,7 @@ string program_name = "spine"; ]; string[string] settings = [ "output" : "", + "ssp-round-trip" : "", "www-http" : "", "www-host" : "", "www-host-doc-root" : "", @@ -273,6 +274,7 @@ string program_name = "spine"; "odf", "open document format text (--odt)", &opts["odf"], "odt", "open document format text", &opts["odt"], "output", "=/path/to/output/dir specify where to place output", &settings["output"], + "ssp-round-trip", "=/path/to/file.ssp read a .ssp back and re-emit it on stdout", &settings["ssp-round-trip"], "parallel", "parallelisation", &opts["parallel"], "parallel-subprocesses", "nested parallelisation", &opts["parallel-subprocesses"], "pdf", "latex output for pdfs", &opts["pdf"], @@ -352,6 +354,49 @@ string program_name = "spine"; if (helpInfo.helpWanted) { defaultGetoptPrinter("Some information about the program.", helpInfo.options); } + /+ ↓ read a .ssp back into the abstraction and emit it again, on stdout. + the two should be byte identical: that is the check that the reader is + faithful, and it is made against the writer's own record definition, + not against a second description of the format + +/ + if (settings["ssp-round-trip"].length > 0) { + import sisudoc.ocda.abstraction.ssp; + import sisudoc.ocda.abstraction.ssp_in; + mixin spineAbstractionRead; + mixin sspObjectRecord; + auto _doc = sspReadFile(settings["ssp-round-trip"]); + string[] _out; + if (_doc.format.length > 0) { _out ~= _doc.format; } + if (_doc.source.length > 0) { _out ~= "% Source: " ~ _doc.source; } + _out ~= ""; + void _block(string _name, string[] _keys, string[string] _kv) { + _out ~= "@" ~ _name ~ " {"; + foreach (k; _keys) { + if (k in _kv) { _out ~= " " ~ k ~ ": " ~ _kv[k]; } + } + _out ~= "}"; + _out ~= ""; + } + _block("meta", _doc.meta_order, _doc.meta); + _block("make", _doc.make_order, _doc.make); + _block("doc_has", _doc.doc_has_order, _doc.doc_has); + foreach (section; _doc.section_order) { + _out ~= "@" ~ section ~ " {"; + _out ~= ""; + foreach (obj; _doc.abstraction[section]) { + _out ~= sspObjectRecord(obj, section); + } + _out ~= "}"; + _out ~= ""; + } + foreach (line; _out) { writeln(line); } + /+ ↓ the document goes to stdout, so the run-complete banner that + scope(success) prints would be part of it; leave without it + +/ + import core.stdc.stdlib : exit; + stdout.flush; + exit(0); + } enum outTask { source_or_pod, sqlite, sqlite_multi, latex, odt, epub, html_scroll, html_seg, html_stuff, text, skel } struct OptActions { @trusted bool allow_downloads() { -- cgit v1.2.3