diff options
| -rw-r--r-- | org/in_abstraction_artefacts.org | 71 | ||||
| -rw-r--r-- | org/spine.org | 102 | ||||
| -rw-r--r-- | src/sisudoc/ocda/abstraction/db_in.d | 30 | ||||
| -rw-r--r-- | src/sisudoc/ocda/abstraction/doc_from_artefact.d | 31 | ||||
| -rw-r--r-- | src/sisudoc/ocda/abstraction/load.d | 10 | ||||
| -rw-r--r-- | src/sisudoc/spine.d | 102 |
6 files changed, 278 insertions, 68 deletions
diff --git a/org/in_abstraction_artefacts.org b/org/in_abstraction_artefacts.org index c2b4c7a..6eeaf8a 100644 --- a/org/in_abstraction_artefacts.org +++ b/org/in_abstraction_artefacts.org @@ -115,13 +115,17 @@ template spineAbstractionLoad() { } /+ ↓ load what can be loaded from the path alone. - .ssp and .ocda.db come back filled. the three source forms come back with loaded = false and a note saying where they are handled, because reading them needs the manifest, environment and configuration that spine.d builds, not a file path. + . + _lang names which language of a .ocda.db to take, a database holding + every language of its document. With none named it takes the only one + there is, and where there is a choice it says so rather than choosing. + A .ssp is one language by construction and ignores it. +/ - LoadedAbstraction abstractionLoad(string _path) { + LoadedAbstraction abstractionLoad(string _path, string _lang = "") { LoadedAbstraction _out; _out.path = _path; _out.source = abstractionSourceOf(_path); @@ -136,7 +140,7 @@ template spineAbstractionLoad() { if (!_out.loaded) { _out.note = "no object sections found"; } break; case AbstractionSource.ocda_db: - _out.doc = dbReadFile(_path); + _out.doc = dbReadFile(_path, _lang); _out.loaded = (_out.doc.section_order.length > 0); if (!_out.loaded) { _out.note = "no object sections found"; } break; @@ -590,6 +594,36 @@ module sisudoc.ocda.abstraction.db_in; writer's own record definition, and require the result to equal the .ssp written from the same document. +/ +/+ ↓ the languages a database holds, in the order they were written. + a caller that wants every language of a document has to be told what + they are, and this is the only place that knows: the file is named for + the document and says nothing about its languages. + . + an unreadable or unexpected file gives an empty list rather than an + exception, and the caller then treats the artefact as it would any other + it could not read. + . + its own template, and not part of spineAbstractionDbRead: asking a file + what languages it holds should not instantiate the whole reader, which + brings the object setter and the markup regexes with it. ++/ +template spineDbLanguages() { + @trusted string[] spineDbLanguages(string db_file) { + import std.file : exists; + import d2sqlite3; + string[] _out; + if (!db_file.exists) { return _out; } + try { + auto db = Database(db_file, SQLITE_OPEN_READONLY); + foreach (row; db.execute("SELECT lang FROM documents ORDER BY id")) { + _out ~= row["lang"].as!string; + } + } catch (Exception ex) { + return string[].init; + } + return _out; + } +} template spineAbstractionDbRead() { import std.conv : to; import std.file; @@ -930,6 +964,34 @@ module sisudoc.ocda.abstraction.doc_from_artefact; so a pod cannot be rebuilt from one, and the empty list is the truthful answer rather than a gap. +/ +/+ ↓ which languages of an artefact there are to build, and in what order. + a .ssp is one language and answers [""], meaning "the one it holds". + a .ocda.db holds every language of its document, so each one named in it + is a document to build, filtered by --lang where that was given. --lang + naming a language the database does not hold leaves an empty list, and + the caller says so rather than silently building nothing. + . + Its own template rather than a function inside spineDocFromArtefact: + that one is eponymous, so nothing beside the function of the same name + can be reached from outside it. The listing itself is db_in's + spineDbLanguages, which is separate from the reader for the same reason: + asking what languages a file holds should not instantiate the reader. ++/ +template spineArtefactLanguages() { + string[] spineArtefactLanguages(string _artefact, string[] _langs_wanted) { + import std.algorithm : endsWith; + import sisudoc.ocda.abstraction.db_in : spineDbLanguages; + if (!_artefact.endsWith(".db")) { return [""]; } + string[] _held = spineDbLanguages!()(_artefact); + if (_held.length == 0) { return [""]; } // let the read report why + if (_langs_wanted.length == 0 || _langs_wanted[0] == "all") { return _held; } + string[] _out; + foreach (_l; _held) { + foreach (_w; _langs_wanted) { if (_l == _w) { _out ~= _l; break; } } + } + return _out; + } +} template spineDocFromArtefact() { import std.algorithm : endsWith; import std.array; @@ -1188,8 +1250,9 @@ template spineDocFromArtefact() { O _opt_action, E _env, ConfComposite _site_conf, + string _lang = "", ) { - auto _loaded = abstractionLoad(_artefact); + auto _loaded = abstractionLoad(_artefact, _lang); auto _ssp = _loaded.doc; auto _pths = artefactPaths(_artefact, _ssp); /+ ↓ the images. diff --git a/org/spine.org b/org/spine.org index 5a54d31..3158191 100644 --- a/org/spine.org +++ b/org/spine.org @@ -118,38 +118,54 @@ string program_name = "spine"; _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 : ""); + /+ ↓ a .ocda.db holds every language of its document, so one artefact can + be several documents to build. A .ssp is one, and comes back as a + single unnamed language. + +/ + string[] _artefact_langs + = spineArtefactLanguages!()(_artefact, _opt_action.languages_set); + if (_artefact_langs.length == 0) { + stderr.writeln("WARNING: ", _artefact.baseName, + " holds no document in the language(s) asked for: ", + _opt_action.languages_set.join(" ")); 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; + foreach (_artefact_lang; _artefact_langs) { + auto doc = spineDocFromArtefact!()( + _artefact, program_info, _opt_action, _env, _make_and_meta_struct, + _artefact_lang, + ); + if (!doc.loaded) { + stderr.writeln("ERROR: could not load ", _artefact, + (_artefact_lang.length > 0) ? " [" ~ _artefact_lang ~ "]" : "", + (doc.note.length > 0) ? ": " ~ doc.note : ""); + continue; } - } - if (!(doc.matters.opt.action.skip_output)) { - doc.outputHub!(); - } - if (_opt_action.vox_gt_1) { - writeln("processed artefact: ", _artefact.baseName, - " [", doc.matters.src.language, "]"); - } - /+ ↓ the images a database carried, written out for this run only +/ - if (doc.images_tmp.length > 0) { - try { - if (doc.images_tmp.exists) { doc.images_tmp.rmdirRecurse; } - } catch (Exception ex) { - stderr.writeln("WARNING: could not remove ", doc.images_tmp, ": ", ex.msg); + 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, "]"); + } + /+ ↓ the images a database carried, written out for this run only +/ + if (doc.images_tmp.length > 0) { + try { + if (doc.images_tmp.exists) { doc.images_tmp.rmdirRecurse; } + } catch (Exception ex) { + stderr.writeln("WARNING: could not remove ", doc.images_tmp, ": ", ex.msg); + } } } } @@ -695,7 +711,26 @@ if (settings["db-round-trip"].length > 0) { import sisudoc.ocda.abstraction.db_in; mixin spineAbstractionDbRead; mixin sspObjectRecord; - auto _doc = dbReadFile(settings["db-round-trip"]); + /+ ↓ a database now holds every language of its document, so a language has to + be named. --lang says which; with none named dbReadFile if there is only + one, takes the only document there is, and where there is a choice + reports the languages and stops rather than picking one. A .ssp emitted + for a language nobody asked for would match nothing, and this is what the + round trip test compares byte for byte. + +/ + string _rt_lang = ""; + { + auto _langs = settings["lang"].split(","); + if (_langs.length > 0 && _langs[0] != "all") { + _rt_lang = _langs[0]; + } + } + auto _doc = dbReadFile(settings["db-round-trip"], _rt_lang); + if (_doc.format.length == 0 && _doc.section_order.length == 0) { + import core.stdc.stdlib : exit; + stdout.flush; + exit(1); + } string[] _out; if (_doc.format.length > 0) { _out ~= _doc.format; } if (_doc.source.length > 0) { _out ~= "% Source: " ~ _doc.source; } @@ -1151,8 +1186,15 @@ struct OptActions { } else if ( /+ ↓ these cannot run in parallel however asked: --curate aggregates across documents, and the shared sqlite db has the one writer + . + --ocda-db joins them because one database now holds every + language of a document, written a language at a time. Several + languages at once against one file, one of them clearing it and + each opening its own transaction, is a race, and the losing + language would be missing from the artefact. +/ sqlite_shared_db_action + || ocda_db || source_or_pod ) { _is = false; diff --git a/src/sisudoc/ocda/abstraction/db_in.d b/src/sisudoc/ocda/abstraction/db_in.d index a8a216c..5535870 100644 --- a/src/sisudoc/ocda/abstraction/db_in.d +++ b/src/sisudoc/ocda/abstraction/db_in.d @@ -60,6 +60,36 @@ module sisudoc.ocda.abstraction.db_in; writer's own record definition, and require the result to equal the .ssp written from the same document. +/ +/+ ↓ the languages a database holds, in the order they were written. + a caller that wants every language of a document has to be told what + they are, and this is the only place that knows: the file is named for + the document and says nothing about its languages. + . + an unreadable or unexpected file gives an empty list rather than an + exception, and the caller then treats the artefact as it would any other + it could not read. + . + its own template, and not part of spineAbstractionDbRead: asking a file + what languages it holds should not instantiate the whole reader, which + brings the object setter and the markup regexes with it. ++/ +template spineDbLanguages() { + @trusted string[] spineDbLanguages(string db_file) { + import std.file : exists; + import d2sqlite3; + string[] _out; + if (!db_file.exists) { return _out; } + try { + auto db = Database(db_file, SQLITE_OPEN_READONLY); + foreach (row; db.execute("SELECT lang FROM documents ORDER BY id")) { + _out ~= row["lang"].as!string; + } + } catch (Exception ex) { + return string[].init; + } + return _out; + } +} template spineAbstractionDbRead() { import std.conv : to; import std.file; diff --git a/src/sisudoc/ocda/abstraction/doc_from_artefact.d b/src/sisudoc/ocda/abstraction/doc_from_artefact.d index 7cbc73a..fa620f8 100644 --- a/src/sisudoc/ocda/abstraction/doc_from_artefact.d +++ b/src/sisudoc/ocda/abstraction/doc_from_artefact.d @@ -74,6 +74,34 @@ module sisudoc.ocda.abstraction.doc_from_artefact; so a pod cannot be rebuilt from one, and the empty list is the truthful answer rather than a gap. +/ +/+ ↓ which languages of an artefact there are to build, and in what order. + a .ssp is one language and answers [""], meaning "the one it holds". + a .ocda.db holds every language of its document, so each one named in it + is a document to build, filtered by --lang where that was given. --lang + naming a language the database does not hold leaves an empty list, and + the caller says so rather than silently building nothing. + . + Its own template rather than a function inside spineDocFromArtefact: + that one is eponymous, so nothing beside the function of the same name + can be reached from outside it. The listing itself is db_in's + spineDbLanguages, which is separate from the reader for the same reason: + asking what languages a file holds should not instantiate the reader. ++/ +template spineArtefactLanguages() { + string[] spineArtefactLanguages(string _artefact, string[] _langs_wanted) { + import std.algorithm : endsWith; + import sisudoc.ocda.abstraction.db_in : spineDbLanguages; + if (!_artefact.endsWith(".db")) { return [""]; } + string[] _held = spineDbLanguages!()(_artefact); + if (_held.length == 0) { return [""]; } // let the read report why + if (_langs_wanted.length == 0 || _langs_wanted[0] == "all") { return _held; } + string[] _out; + foreach (_l; _held) { + foreach (_w; _langs_wanted) { if (_l == _w) { _out ~= _l; break; } } + } + return _out; + } +} template spineDocFromArtefact() { import std.algorithm : endsWith; import std.array; @@ -332,8 +360,9 @@ template spineDocFromArtefact() { O _opt_action, E _env, ConfComposite _site_conf, + string _lang = "", ) { - auto _loaded = abstractionLoad(_artefact); + auto _loaded = abstractionLoad(_artefact, _lang); auto _ssp = _loaded.doc; auto _pths = artefactPaths(_artefact, _ssp); /+ ↓ the images. diff --git a/src/sisudoc/ocda/abstraction/load.d b/src/sisudoc/ocda/abstraction/load.d index 2072aae..ae6c673 100644 --- a/src/sisudoc/ocda/abstraction/load.d +++ b/src/sisudoc/ocda/abstraction/load.d @@ -139,13 +139,17 @@ template spineAbstractionLoad() { } /+ ↓ load what can be loaded from the path alone. - .ssp and .ocda.db come back filled. the three source forms come back with loaded = false and a note saying where they are handled, because reading them needs the manifest, environment and configuration that spine.d builds, not a file path. + . + _lang names which language of a .ocda.db to take, a database holding + every language of its document. With none named it takes the only one + there is, and where there is a choice it says so rather than choosing. + A .ssp is one language by construction and ignores it. +/ - LoadedAbstraction abstractionLoad(string _path) { + LoadedAbstraction abstractionLoad(string _path, string _lang = "") { LoadedAbstraction _out; _out.path = _path; _out.source = abstractionSourceOf(_path); @@ -160,7 +164,7 @@ template spineAbstractionLoad() { if (!_out.loaded) { _out.note = "no object sections found"; } break; case AbstractionSource.ocda_db: - _out.doc = dbReadFile(_path); + _out.doc = dbReadFile(_path, _lang); _out.loaded = (_out.doc.section_order.length > 0); if (!_out.loaded) { _out.note = "no object sections found"; } break; diff --git a/src/sisudoc/spine.d b/src/sisudoc/spine.d index 9424e02..ab4eaa3 100644 --- a/src/sisudoc/spine.d +++ b/src/sisudoc/spine.d @@ -431,7 +431,26 @@ string program_name = "spine"; import sisudoc.ocda.abstraction.db_in; mixin spineAbstractionDbRead; mixin sspObjectRecord; - auto _doc = dbReadFile(settings["db-round-trip"]); + /+ ↓ a database now holds every language of its document, so a language has to + be named. --lang says which; with none named dbReadFile if there is only + one, takes the only document there is, and where there is a choice + reports the languages and stops rather than picking one. A .ssp emitted + for a language nobody asked for would match nothing, and this is what the + round trip test compares byte for byte. + +/ + string _rt_lang = ""; + { + auto _langs = settings["lang"].split(","); + if (_langs.length > 0 && _langs[0] != "all") { + _rt_lang = _langs[0]; + } + } + auto _doc = dbReadFile(settings["db-round-trip"], _rt_lang); + if (_doc.format.length == 0 && _doc.section_order.length == 0) { + import core.stdc.stdlib : exit; + stdout.flush; + exit(1); + } string[] _out; if (_doc.format.length > 0) { _out ~= _doc.format; } if (_doc.source.length > 0) { _out ~= "% Source: " ~ _doc.source; } @@ -880,8 +899,15 @@ string program_name = "spine"; } else if ( /+ ↓ these cannot run in parallel however asked: --curate aggregates across documents, and the shared sqlite db has the one writer + . + --ocda-db joins them because one database now holds every + language of a document, written a language at a time. Several + languages at once against one file, one of them clearing it and + each opening its own transaction, is a race, and the losing + language would be missing from the artefact. +/ sqlite_shared_db_action + || ocda_db || source_or_pod ) { _is = false; @@ -1690,38 +1716,54 @@ string program_name = "spine"; _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 : ""); + /+ ↓ a .ocda.db holds every language of its document, so one artefact can + be several documents to build. A .ssp is one, and comes back as a + single unnamed language. + +/ + string[] _artefact_langs + = spineArtefactLanguages!()(_artefact, _opt_action.languages_set); + if (_artefact_langs.length == 0) { + stderr.writeln("WARNING: ", _artefact.baseName, + " holds no document in the language(s) asked for: ", + _opt_action.languages_set.join(" ")); 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; + foreach (_artefact_lang; _artefact_langs) { + auto doc = spineDocFromArtefact!()( + _artefact, program_info, _opt_action, _env, _make_and_meta_struct, + _artefact_lang, + ); + if (!doc.loaded) { + stderr.writeln("ERROR: could not load ", _artefact, + (_artefact_lang.length > 0) ? " [" ~ _artefact_lang ~ "]" : "", + (doc.note.length > 0) ? ": " ~ doc.note : ""); + continue; } - } - if (!(doc.matters.opt.action.skip_output)) { - doc.outputHub!(); - } - if (_opt_action.vox_gt_1) { - writeln("processed artefact: ", _artefact.baseName, - " [", doc.matters.src.language, "]"); - } - /+ ↓ the images a database carried, written out for this run only +/ - if (doc.images_tmp.length > 0) { - try { - if (doc.images_tmp.exists) { doc.images_tmp.rmdirRecurse; } - } catch (Exception ex) { - stderr.writeln("WARNING: could not remove ", doc.images_tmp, ": ", ex.msg); + 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, "]"); + } + /+ ↓ the images a database carried, written out for this run only +/ + if (doc.images_tmp.length > 0) { + try { + if (doc.images_tmp.exists) { doc.images_tmp.rmdirRecurse; } + } catch (Exception ex) { + stderr.writeln("WARNING: could not remove ", doc.images_tmp, ": ", ex.msg); + } } } } |
