diff options
| author | Ralph Amissah <ralph.amissah@gmail.com> | 2026-09-11 10:23:25 -0400 |
|---|---|---|
| committer | Ralph Amissah <ralph.amissah@gmail.com> | 2026-09-12 12:14:39 -0400 |
| commit | b6b2ff06054edb20c880aff26b8b02d8f093fcc2 (patch) | |
| tree | 6cc5abb9323808e28227a1791a8717ef4e00485f /org | |
| parent | ocda: .ssp should have all sections of abstraction (diff) | |
ocda: the abstraction carries the document metadata output reads
.ssp format v0.1 -> v1.1, and the .ocda.db with it: they are two
serialisations of one format and now move together.
The gap this closes, measured against the writers' read set: of the 59
conf_make_meta fields outputs/io_out/ reads, 29 were in neither
artefact. Five are conf.* and stay out, being site and run scoped (urls,
papersize, the search db filename): the same abstraction published to
two sites must take each site's. Of the rest, three are never assigned
anywhere (title_short, publisher, original_publisher; read only by
sqlite.d, always empty) and one is a copy of a field already carried
(title_sub = title_subtitle), so 17 properties actually had to travel
and now do:
make breaks, footer, home_button_text
meta title.edition, date.added_to_site, language.document_char,
original.{title,source,language,language_char},
rights.copyright_{text,translation,illustrations,
photographs,cover,audio,video}
make splits by when it acts, which is worth keeping in mind: these three
are read in outputs/io_out/ and must travel, while italics, bold,
emphasis, substitute and headings are read at parse time and their
effect is already in the objects.
New @source block, and source.* rows in the database, so a reader can
say which markup an abstraction came from rather than working from
stale content in silence:
language the document's own
languages the pod's list, which is what the inter-language links
in html need and neither artefact carried
digest sha256 of the .sst; equals its digests.txt entry
The database adds source.ssp_digest, the sha256 of the .ssp it was built
from, since a file cannot hold its own hash. So the chain
.sst -> .ssp -> .ocda.db is checkable end to end.
The database's metadata table is now filled from the header blocks the
.ssp gives back, not from a second list read off doc_matters. One list,
in ssp.d: a property added there arrives in the database with nothing
else changed, and one that is not in the .ssp cannot be in the database
at all. That was the last place the two could drift; the objects stopped
being able to on 2026-09-07.
Version is checked on load. The major part must match, a newer minor is
accepted (a minor bump only adds properties, and an unknown property
line is ignored). A v0.1 artefact is now refused with a message saying
to regenerate it, rather than loading half populated.
test-abstraction-db.sh now compares the two artefacts' header blocks
property by property, 36 per document on the wealth of networks, where
it previously only checked that a schema.version row existed. Verified
non-vacuous: dropping one property is reported.
Reference regenerated, and the whole diff is this change and nothing
else: 320 lines added, 35 removed over 35 files, being 35 format lines
changed, 35 @source blocks (4 lines each), 35 language.document_char, 35
home_button_text (it has a default), 18 breaks, 16 footer,
4 date.added_to_site, 1 title.edition, 1 original.source.
Existing outputs unaffected.
(assisted by Claude-Code)
Diffstat (limited to 'org')
| -rw-r--r-- | org/in_source_files.org | 94 | ||||
| -rw-r--r-- | org/out_src_abstraction_ocda_peg_ssp.org | 88 | ||||
| -rw-r--r-- | org/out_src_abstraction_sqlite_db.org | 94 | ||||
| -rw-r--r-- | org/spine.org | 4 | ||||
| -rw-r--r-- | org/tests_for_document_abstraction_shell_scripts.org | 37 |
5 files changed, 243 insertions, 74 deletions
diff --git a/org/in_source_files.org b/org/in_source_files.org index e8b5272..6b645b6 100644 --- a/org/in_source_files.org +++ b/org/in_source_files.org @@ -813,8 +813,11 @@ template spineAbstractionRead() { object sections in the order the file gives them +/ struct SSPdocument { - string format; // "% SiSU Document Abstraction v0.1" + string format; // "% SiSU Document Abstraction v1.1" string source; // "% Source: ..." + string[string] source_info; // the @source block + string[] source_info_order; + string ssp_digest; // sha256 of the .ssp read, hex string[string] meta; string[] meta_order; string[string] make; @@ -889,7 +892,7 @@ template spineAbstractionRead() { _closeObject(); string _name = line[1 .. $-2]; switch (_name) { - case "meta": case "make": case "doc_has": + case "source": case "meta": case "make": case "doc_has": block = _name; section = ""; break; default: block = ""; section = _name; @@ -904,6 +907,10 @@ template spineAbstractionRead() { auto _kv = line.stripLeft.findSplit(": "); // leading only: a value may end in a space if (_kv[1].length > 0) { final switch (block) { + case "source": + doc.source_info[_kv[0].to!string] = _kv[2].to!string; + doc.source_info_order ~= _kv[0].to!string; + break; case "meta": doc.meta[_kv[0].to!string] = _kv[2].to!string; doc.meta_order ~= _kv[0].to!string; @@ -1069,25 +1076,75 @@ template spineAbstractionRead() { } return doc; } - /+ ↓ the abstraction as it survives a trip through the .ssp text: emitted - as lines by the writer, read straight back. what a consumer built - from this cannot see, the .ssp does not carry, which is what makes a - second artefact built from it unable to drift + /+ ↓ the whole document as it survives a trip through the .ssp text: + emitted as lines by the writer, read straight back. what a consumer + built from this cannot see, the .ssp does not carry, which is what + makes a second artefact built from it unable to drift. the header + blocks come back with it, so the database is filled from these and + not from a second list read off doc_matters +/ - auto sspRoundTripAbstraction(D)(D doc) { + SSPdocument sspRoundTripDocument(D)(D doc) { import sisudoc.ocda.abstraction.ssp; mixin spineAbstractionTxt; - return sspRead(sspDocumentLines(doc)).abstraction; + string[] _lines = sspDocumentLines(doc); + auto _out = sspRead(_lines); + /+ ↓ the writer emits one line per entry, each with its newline, so this + is the digest of the file that is written beside the pod +/ + _out.ssp_digest = sspDigestOfText(_lines.join("\n") ~ "\n"); + return _out; + } + /+ ↓ sha256 of a .ssp, upper case hex, as digests.txt gives it +/ + string sspDigestOfText(string _text) { + import std.digest : toHexString; + import std.digest.sha : sha256Of; + return _text.sha256Of.toHexString.to!string; + } + auto sspRoundTripAbstraction(D)(D doc) { + return sspRoundTripDocument(doc).abstraction; + } + /+ ↓ the version a reader will accept. + + the major part must match: a format whose properties have changed + meaning is not readable by guesswork, and loading it half populated in + silence is the failure this check exists to stop. a newer minor + version is accepted, since a minor bump only adds properties and an + unknown property line is ignored. + +/ + bool sspFormatVersionOk(string _format_line, string _what) { + import sisudoc.ocda.abstraction.ssp : ssp_format_version, ssp_format_version_major; + enum _prefix = "% SiSU Document Abstraction v"; + if (_format_line.length == 0) { + writeln("ERROR: ", _what, ": no format line, not a .ssp"); + return false; + } + if (!_format_line.startsWith(_prefix)) { + writeln("ERROR: ", _what, ": unrecognised format line: ", _format_line); + return false; + } + string _ver = _format_line[_prefix.length .. $].strip; + string _major = _ver.findSplit(".")[0]; + if (_major != ssp_format_version_major) { + writeln("ERROR: ", _what, ": .ssp format v", _ver, + ", this spine reads v", ssp_format_version_major, ".x (v", + ssp_format_version, "). Regenerate the abstraction from its markup."); + return false; + } + return true; } SSPdocument sspReadFile(string file_path) { + string _text; string[] _lines; try { - _lines = (cast(char[]) file_path.read).to!string.split("\n"); + _text = (cast(char[]) file_path.read).to!string; + _lines = _text.split("\n"); } catch (Exception ex) { writeln("ERROR: could not read ", file_path, ": ", ex.msg); return SSPdocument(); } - return sspRead(_lines); + auto _doc = sspRead(_lines); + if (!sspFormatVersionOk(_doc.format, file_path)) { return SSPdocument(); } + _doc.ssp_digest = sspDigestOfText(_text); + return _doc; } } #+END_SRC @@ -1188,7 +1245,7 @@ template spineAbstractionDbRead() { return doc; } auto db = Database(db_file, SQLITE_OPEN_READONLY); - /+ ↓ the three header blocks, from the metadata table. the key prefix + /+ ↓ the four header blocks, from the metadata table. the key prefix says which block a row belongs to, and insertion order is kept +/ foreach (row; db.execute("SELECT key, value FROM metadata ORDER BY rowid")) { string _k = row["key"].as!string; @@ -1202,16 +1259,25 @@ template spineAbstractionDbRead() { doc.doc_has[_kk] = _v; doc.doc_has_order ~= _kk; } else if (_k.startsWith("schema.")) { - if (_k == "schema.version") { doc.format = "% SiSU Document Abstraction v0.1"; } + if (_k == "schema.version") { + doc.format = "% SiSU Document Abstraction v" ~ _v; + } } else if (_k == "source.filename") { doc.source = _v; - } else if (_k == "source.language") { - // carried in the path, not in the .ssp header + } else if (_k == "source.ssp_digest") { + /+ ↓ this database's own note of the .ssp it was built from. it is + not part of the .ssp, so it does not go back into the block +/ + doc.ssp_digest = _v; + } else if (_k.startsWith("source.")) { + string _kk = _k["source.".length .. $]; + doc.source_info[_kk] = _v; + doc.source_info_order ~= _kk; } else { doc.meta[_k] = _v; doc.meta_order ~= _k; } } + if (!sspFormatVersionOk(doc.format, db_file)) { return SSPdocument(); } /+ ↓ the objects, section by section, in the order they were written +/ string[] _sections; foreach (row; db.execute( diff --git a/org/out_src_abstraction_ocda_peg_ssp.org b/org/out_src_abstraction_ocda_peg_ssp.org index 95efb93..5e26a4f 100644 --- a/org/out_src_abstraction_ocda_peg_ssp.org +++ b/org/out_src_abstraction_ocda_peg_ssp.org @@ -32,6 +32,21 @@ rename source_abstraction_peg_txt.d module sisudoc.ocda.abstraction.ssp; @safe: /+ ↓ write document abstraction as human-readable .ssp text file +/ +/+ ↓ the format version, carried by both serialisations: the .ssp says it in + its "% SiSU Document Abstraction v" header line and the .ocda.db in its + metadata schema.version row, so that one bump moves both. + + a reader checks the major part and refuses what it does not know, rather + than loading an older artefact half populated in silence. bump the minor + part when a property is added (a reader of the newer format still reads + the older one), the major part when one changes meaning or goes away. + + 1.1 2026-09-10 @source block; the document metadata the output writers + read carried in full (14 meta and 3 make properties + added); was 0.1 ++/ +enum ssp_format_version = "1.1"; +enum ssp_format_version_major = "1"; /+ ↓ escaping for values that are folded onto one line a .ssp property value runs to the end of its line, and @@ -413,9 +428,22 @@ template spineAbstractionTxt() { auto doc_matters = doc.matters; string[] output; /+ ↓ header comment +/ - output ~= "% SiSU Document Abstraction v0.1"; + output ~= "% SiSU Document Abstraction v" ~ ssp_format_version; output ~= "% Source: " ~ doc_matters.src.filename; output ~= ""; + /+ ↓ @source block: what this abstraction was made from, so a reader can + say whether it is looking at the right images and whether the + markup has moved on since. the filename is in the % Source: line + above and is not repeated here + +/ + output ~= "@source {"; + output ~= " language: " ~ doc_matters.src.language; + if (doc_matters.pod.manifest_list_of_languages.length > 0) { + output ~= " languages: " ~ doc_matters.pod.manifest_list_of_languages.join(" "); + } + output ~= " digest: " ~ doc_matters.doc_digest.markup_doc.toHexString.to!string; + output ~= "}"; + output ~= ""; /+ ↓ @meta block +/ output ~= "@meta {"; auto meta = doc_matters.conf_make_meta.meta; @@ -431,6 +459,9 @@ template spineAbstractionTxt() { if (meta.title_language.length > 0) { output ~= " title.language: " ~ meta.title_language; } + if (meta.title_edition.length > 0) { + output ~= " title.edition: " ~ meta.title_edition; + } if (meta.creator_author.length > 0) { output ~= " creator.author: " ~ meta.creator_author; } @@ -467,9 +498,33 @@ template spineAbstractionTxt() { if (meta.date_valid.length > 0) { output ~= " date.valid: " ~ meta.date_valid; } + if (meta.date_added_to_site.length > 0) { + output ~= " date.added_to_site: " ~ meta.date_added_to_site; + } if (meta.rights_copyright.length > 0) { output ~= " rights.copyright: " ~ meta.rights_copyright; } + if (meta.rights_copyright_text.length > 0) { + output ~= " rights.copyright_text: " ~ meta.rights_copyright_text; + } + if (meta.rights_copyright_translation.length > 0) { + output ~= " rights.copyright_translation: " ~ meta.rights_copyright_translation; + } + if (meta.rights_copyright_illustrations.length > 0) { + output ~= " rights.copyright_illustrations: " ~ meta.rights_copyright_illustrations; + } + if (meta.rights_copyright_photographs.length > 0) { + output ~= " rights.copyright_photographs: " ~ meta.rights_copyright_photographs; + } + if (meta.rights_copyright_cover.length > 0) { + output ~= " rights.copyright_cover: " ~ meta.rights_copyright_cover; + } + if (meta.rights_copyright_audio.length > 0) { + output ~= " rights.copyright_audio: " ~ meta.rights_copyright_audio; + } + if (meta.rights_copyright_video.length > 0) { + output ~= " rights.copyright_video: " ~ meta.rights_copyright_video; + } if (meta.rights_license.length > 0) { output ~= " rights.license: " ~ meta.rights_license; } @@ -497,6 +552,21 @@ template spineAbstractionTxt() { if (meta.language_document.length > 0) { output ~= " language.document: " ~ meta.language_document; } + if (meta.language_document_char.length > 0) { + output ~= " language.document_char: " ~ meta.language_document_char; + } + if (meta.original_title.length > 0) { + output ~= " original.title: " ~ meta.original_title; + } + if (meta.original_source.length > 0) { + output ~= " original.source: " ~ meta.original_source; + } + if (meta.original_language.length > 0) { + output ~= " original.language: " ~ meta.original_language; + } + if (meta.original_language_char.length > 0) { + output ~= " original.language_char: " ~ meta.original_language_char; + } if (meta.notes_abstract.length > 0) { output ~= " notes.abstract: " ~ meta.notes_abstract; } @@ -519,6 +589,22 @@ template spineAbstractionTxt() { } // check output ~= " auto_num_top_lv: " ~ make.auto_num_top_lv.to!string; output ~= " auto_num_depth: " ~ make.auto_num_depth.to!string; + /+ ↓ the make settings the output writers read. the rest of make + (italics, bold, emphasis, substitute, headings) is read at parse + time and its effect is already in the objects, so it does not + travel; these three are read in outputs/io_out/ and must + +/ + if (make.breaks.length > 0) { + output ~= " breaks: " ~ make.breaks; + } + if (make.footer.length > 0) { // a list: joined, each slot escaped + string[] _ft; + foreach (_f; make.footer) { _ft ~= sspEscapeValue(_f); } + output ~= " footer: " ~ _ft.join("|"); + } + if (make.home_button_text.length > 0) { + output ~= " home_button_text: " ~ make.home_button_text; + } output ~= "}"; output ~= ""; /+ ↓ @doc_has block +/ diff --git a/org/out_src_abstraction_sqlite_db.org b/org/out_src_abstraction_sqlite_db.org index ad719c7..dab8291 100644 --- a/org/out_src_abstraction_sqlite_db.org +++ b/org/out_src_abstraction_sqlite_db.org @@ -43,13 +43,17 @@ template spineAbstractionDb() { import d2sqlite3; import sisudoc.outputs.io_out.paths_output; import sisudoc.ocda.io_in.paths_source; - /+ ↓ the abstraction is passed in rather than taken from doc, because the + import sisudoc.ocda.abstraction.ssp : ssp_format_version; + /+ ↓ the document is passed in rather than taken from doc, because the caller hands over one that has been through the .ssp: written as text and read back. so the database is a function of the .ssp and cannot - carry anything the .ssp does not + carry anything the .ssp does not, header blocks included: the metadata + table below is filled from what came back, not from a second list read + off doc_matters, which is how the two drifted apart before +/ - void spineAbstractionDb(D,A)(D doc, A doc_abstraction) { + void spineAbstractionDb(D,S)(D doc, S ssp_doc) { auto doc_matters = doc.matters; + auto doc_abstraction = ssp_doc.abstraction; /+ ↓ output path: beside the pod's zip and digests, not under <lang>/abstraction/. the database carries its own images, so it is a distributable artefact in its own right and belongs with the others: @@ -241,13 +245,18 @@ template spineAbstractionDb() { SELECT role, name, bytes, sha256, width, height FROM files ORDER BY role, name; "); - /+ ↓ populate metadata +/ + /+ ↓ populate metadata, from the header blocks the .ssp gave back. + one list, in ssp.d, not two: a property added to the .ssp header + arrives here without anything being changed, and one that is not in + the .ssp cannot be here at all. the keys are the .ssp's own + (title.main, make.doc_type, doc_has.images), prefixed by their block + where the block name is not already part of the key. + +/ db.run("BEGIN TRANSACTION"); auto meta_stmt = db.prepare( "INSERT INTO metadata (key, value) VALUES (:key, :value)" ); - auto meta = doc_matters.conf_make_meta.meta; void insertMeta(string key, string value) { if (value.length > 0) { @@ -257,62 +266,31 @@ template spineAbstractionDb() { meta_stmt.reset(); } } + void insertBlock(string prefix, string[] keys_in_order, string[string] kv) { + foreach (k; keys_in_order) { + if (k in kv) { insertMeta(prefix ~ k, kv[k]); } + } + } - /+ ↓ what this file is, so a reader can tell before it starts +/ + /+ ↓ what this file is, so a reader can tell before it starts. the + version is the .ssp's own: the two serialisations are one format and + move together + +/ insertMeta("schema.name", "sisu-abstraction-db"); - insertMeta("schema.version", "1"); - insertMeta("source.filename", doc_matters.src.filename); - insertMeta("source.language", doc_matters.src.language); - - insertMeta("title.main", meta.title_main); - insertMeta("title.subtitle", meta.title_subtitle); - insertMeta("title.full", meta.title_full); - insertMeta("title.language", meta.title_language); - insertMeta("creator.author", meta.creator_author); - insertMeta("creator.author_surname", meta.creator_author_surname); - insertMeta("creator.author_surname_fn", meta.creator_author_surname_fn); - insertMeta("creator.author_email", meta.creator_author_email); - insertMeta("creator.illustrator", meta.creator_illustrator); - insertMeta("creator.translator", meta.creator_translator); - insertMeta("date.published", meta.date_published); - insertMeta("date.created", meta.date_created); - insertMeta("date.issued", meta.date_issued); - insertMeta("date.available", meta.date_available); - insertMeta("date.modified", meta.date_modified); - insertMeta("date.valid", meta.date_valid); - insertMeta("rights.copyright", meta.rights_copyright); - insertMeta("rights.license", meta.rights_license); - insertMeta("classify.topic_register", meta.classify_topic_register); - insertMeta("classify.subject", meta.classify_subject); - insertMeta("classify.keywords", meta.classify_keywords); - insertMeta("classify.loc", meta.classify_loc); - insertMeta("classify.dewey", meta.classify_dewey); - insertMeta("identifier.isbn", meta.identifier_isbn); - insertMeta("identifier.oclc", meta.identifier_oclc); - insertMeta("language.document", meta.language_document); - insertMeta("notes.abstract", meta.notes_abstract); - insertMeta("notes.description", meta.notes_description); - insertMeta("notes.summary", meta.notes_summary); - - /+ ↓ make settings +/ - auto make = doc_matters.conf_make_meta.make; - insertMeta("make.doc_type", make.doc_type); - insertMeta("make.auto_num_top_at_level", make.auto_num_top_at_level); - insertMeta("make.auto_num_top_lv", make.auto_num_top_lv.to!string); - insertMeta("make.auto_num_depth", make.auto_num_depth.to!string); - - /+ ↓ doc_has counts +/ - insertMeta("doc_has.inline_links", doc_matters.has.inline_links.to!string); - insertMeta("doc_has.inline_notes_reg", doc_matters.has.inline_notes_reg.to!string); - insertMeta("doc_has.inline_notes_star", doc_matters.has.inline_notes_star.to!string); - insertMeta("doc_has.tables", doc_matters.has.tables.to!string); - insertMeta("doc_has.codeblocks", doc_matters.has.codeblocks.to!string); - insertMeta("doc_has.images", doc_matters.has.images.to!string); - insertMeta("doc_has.poems", doc_matters.has.poems.to!string); - insertMeta("doc_has.groups", doc_matters.has.groups.to!string); - insertMeta("doc_has.blocks", doc_matters.has.blocks.to!string); - insertMeta("doc_has.quotes", doc_matters.has.quotes.to!string); + insertMeta("schema.version", ssp_format_version); + insertMeta("source.filename", ssp_doc.source); + insertBlock("source.", ssp_doc.source_info_order, ssp_doc.source_info); + /+ ↓ the digest of the .ssp this database was built from. a file cannot + hold its own hash, so this one is not in the .ssp and is taken here: + source.digest says which markup the abstraction came from, + source.ssp_digest says which abstraction this database came from, + and the chain .sst -> .ssp -> .ocda.db is checkable end to end + +/ + insertMeta("source.ssp_digest", ssp_doc.ssp_digest); + insertBlock("", ssp_doc.meta_order, ssp_doc.meta); + insertBlock("make.", ssp_doc.make_order, ssp_doc.make); + insertBlock("doc_has.", ssp_doc.doc_has_order, ssp_doc.doc_has); meta_stmt.finalize(); /+ ↓ populate objects +/ diff --git a/org/spine.org b/org/spine.org index c26944c..50b9ce2 100644 --- a/org/spine.org +++ b/org/spine.org @@ -583,6 +583,7 @@ if (settings["ssp-round-trip"].length > 0) { _out ~= "}"; _out ~= ""; } + _block("source", _doc.source_info_order, _doc.source_info); _block("meta", _doc.meta_order, _doc.meta); _block("make", _doc.make_order, _doc.make); _block("doc_has", _doc.doc_has_order, _doc.doc_has); @@ -631,6 +632,7 @@ if (settings["db-round-trip"].length > 0) { _out ~= "}"; _out ~= ""; } + _blockdb("source", _doc.source_info_order, _doc.source_info); _blockdb("meta", _doc.meta_order, _doc.meta); _blockdb("make", _doc.make_order, _doc.make); _blockdb("doc_has", _doc.doc_has_order, _doc.doc_has); @@ -1763,7 +1765,7 @@ if (doc.matters.opt.action.ocda_db) { import sisudoc.ocda.abstraction.ssp_in; import sisudoc.outputs.io_out.create_ocda_db; mixin spineAbstractionRead; - spineAbstractionDb!()(doc, sspRoundTripAbstraction(doc)); + spineAbstractionDb!()(doc, sspRoundTripDocument(doc)); } #+END_SRC diff --git a/org/tests_for_document_abstraction_shell_scripts.org b/org/tests_for_document_abstraction_shell_scripts.org index 7228170..5f355c8 100644 --- a/org/tests_for_document_abstraction_shell_scripts.org +++ b/org/tests_for_document_abstraction_shell_scripts.org @@ -621,11 +621,48 @@ for ssp in $(find "$OUT_DIR/pod" -name "*.ssp" 2>/dev/null | sort); do rm -f "$OUT_DIR/.dbtxt.$$" "$OUT_DIR/.ssptxt.$$" done + # the header blocks: every @source/@meta/@make/@doc_has property in the .ssp + # must be in the database under its own key, with the same value. this is + # what catches a header property that reaches one artefact and not the + # other, which a count of objects cannot see. + awk ' + /^@(source|meta|make|doc_has) \{$/ { blk=substr($1,2); next } + /^\}$/ { blk=""; next } + blk != "" && /^ [a-z_.]+: / { + k=$1; sub(/:$/,"",k) + v=substr($0, index($0,": ")+2) + pfx = (blk=="meta") ? "" : blk "." + print pfx k "\t" v + } + ' "$ssp" | sort > "$OUT_DIR/.sspmeta.$$" + sqlite3 -separator "$(printf '\t')" "$db" \ + "select key, value from metadata where key not in + ('schema.name','schema.version','source.filename','source.ssp_digest');" \ + 2>/dev/null | sort > "$OUT_DIR/.dbmeta.$$" + if ! diff -q "$OUT_DIR/.sspmeta.$$" "$OUT_DIR/.dbmeta.$$" > /dev/null 2>&1; then + [ $doc_fail -eq 0 ] && echo "$base" + echo " MISMATCH header blocks (.ssp against metadata table):" + diff "$OUT_DIR/.sspmeta.$$" "$OUT_DIR/.dbmeta.$$" | head -8 | sed 's/^/ /' + doc_fail=1 + fi + rm -f "$OUT_DIR/.sspmeta.$$" "$OUT_DIR/.dbmeta.$$" + + # the source filename, and the version the two share + sf=$(sqlite3 "$db" "select value from metadata where key='source.filename';" 2>/dev/null || echo '') + sf_ssp=$(sed -n 's/^% Source: //p' "$ssp" | head -1) + if [ "$sf" != "$sf_ssp" ]; then + [ $doc_fail -eq 0 ] && echo "$base" + echo " MISMATCH source.filename: db '$sf' .ssp '$sf_ssp'"; doc_fail=1 + fi + sv_ssp=$(sed -n 's/^% SiSU Document Abstraction v//p' "$ssp" | head -1) # the file says what it is sv=$(sqlite3 "$db" "select value from metadata where key='schema.version';" 2>/dev/null || echo '') if [ -z "$sv" ]; then [ $doc_fail -eq 0 ] && echo "$base" echo " MISSING metadata schema.version"; doc_fail=1 + elif [ "$sv" != "$sv_ssp" ]; then + [ $doc_fail -eq 0 ] && echo "$base" + echo " MISMATCH format version: db '$sv' .ssp '$sv_ssp'"; doc_fail=1 fi FAILURES=$((FAILURES + doc_fail)) |
