aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--org/in_abstraction_artefacts.org71
-rw-r--r--org/spine.org102
-rw-r--r--src/sisudoc/ocda/abstraction/db_in.d30
-rw-r--r--src/sisudoc/ocda/abstraction/doc_from_artefact.d31
-rw-r--r--src/sisudoc/ocda/abstraction/load.d10
-rw-r--r--src/sisudoc/spine.d102
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);
+ }
}
}
}