diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/sisudoc/io_in/paths_source.d | 14 | ||||
| -rw-r--r-- | src/sisudoc/io_in/read_zip_pod.d | 122 | ||||
| -rw-r--r-- | src/sisudoc/io_out/create_abstraction_db.d | 355 | ||||
| -rw-r--r-- | src/sisudoc/io_out/create_abstraction_txt.d | 430 | ||||
| -rw-r--r-- | src/sisudoc/io_out/source_pod.d | 45 | ||||
| -rw-r--r-- | src/sisudoc/meta/metadoc_from_src.d | 19 | ||||
| -rw-r--r-- | src/sisudoc/meta/metadoc_object_setter.d | 1 | ||||
| -rwxr-xr-x | src/sisudoc/spine.d | 92 |
8 files changed, 1064 insertions, 14 deletions
diff --git a/src/sisudoc/io_in/paths_source.d b/src/sisudoc/io_in/paths_source.d index 9f8ae63..41353ed 100644 --- a/src/sisudoc/io_in/paths_source.d +++ b/src/sisudoc/io_in/paths_source.d @@ -821,6 +821,20 @@ template spinePathsPods() { } return _pods(); } + auto abstraction_root(string fn_src) { + auto pod_root_ = pod_root(fn_src); + auto pth_1_ = ((media_root(fn_src).zpod.chainPath("abstraction")).asNormalizedPath).array; + auto pth_2_ = ((media_root(fn_src).filesystem_open_zpod.chainPath("abstraction")).asNormalizedPath).array; + struct _pods { + auto zpod() { + return pth_1_; + } + auto filesystem_open_zpod() { + return pth_2_; + } + } + return _pods(); + } auto image_root(string fn_src) { auto pod_root_ = pod_root(fn_src); auto pth_1_ = ((media_root(fn_src).zpod.chainPath("image")).asNormalizedPath).array; diff --git a/src/sisudoc/io_in/read_zip_pod.d b/src/sisudoc/io_in/read_zip_pod.d index 38480cd..d228f4e 100644 --- a/src/sisudoc/io_in/read_zip_pod.d +++ b/src/sisudoc/io_in/read_zip_pod.d @@ -265,6 +265,128 @@ template spineExtractZipPod() { return ""; } + /+ ↓ download a zip pod from a URL to a temp file +/ + enum size_t MAX_DOWNLOAD_SIZE = 200 * 1024 * 1024; /+ 200 MB download limit +/ + enum int DOWNLOAD_TIMEOUT = 120; /+ seconds +/ + + static auto rgx_url_zip = ctRegex!(`^https?://[a-zA-Z0-9._:/-]+[.]zip$`); + + struct DownloadResult { + string local_path; /+ path to downloaded temp file +/ + bool ok; + string error_msg; + } + + bool isUrl(string arg) { + return arg.length > 8 + && (arg[0..8] == "https://" || arg[0..7] == "http://"); + } + + @trusted DownloadResult downloadZipUrl(string url) { + import std.process : execute, environment; + DownloadResult result; + result.ok = false; + /+ ↓ validate URL scheme +/ + if (url.length < 8 || (url[0..8] != "https://" && url[0..7] != "http://")) { + result.error_msg = "only http/https URLs are supported: " ~ url; + return result; + } + if (url[0..7] == "http://" && url[0..8] != "https://") { + stderr.writeln("WARNING: downloading over insecure http: ", url); + } + /+ ↓ validate URL format +/ + if (!(url.matchFirst(rgx_url_zip))) { + result.error_msg = "URL does not match expected zip URL pattern: " ~ url; + return result; + } + /+ ↓ reject URLs that could target internal services +/ + { + import std.uni : toLower; + string url_lower = url.toLower; + /+ strip scheme to get host portion +/ + string after_scheme = (url_lower[0..8] == "https://") + ? url_lower[8..$] + : url_lower[7..$]; + /+ extract host (up to first / or :) +/ + string host; + foreach (i, c; after_scheme) { + if (c == '/' || c == ':') { + host = after_scheme[0..i]; + break; + } + } + if (host.length == 0) host = after_scheme; + if (host == "localhost" + || host == "127.0.0.1" + || host == "::1" + || host == "[::1]" + || host == "0.0.0.0" + || host.canFind("169.254.") + || host.canFind("10.") + || host.canFind("192.168.") + ) { + result.error_msg = "URL targets a local/private address: " ~ url; + return result; + } + } + /+ ↓ derive filename from URL +/ + string url_basename = url.baseName; + if (url_basename.length == 0 || url_basename.indexOf('.') < 0) { + result.error_msg = "cannot determine filename from URL: " ~ url; + return result; + } + /+ ↓ create temp directory for download +/ + string tmp_base = tempDir.buildPath("spine-zip-pod"); + try { + if (!exists(tmp_base)) + mkdirRecurse(tmp_base); + } catch (FileException ex) { + result.error_msg = "failed to create temp directory: " ~ ex.msg; + return result; + } + string tmp_file = tmp_base.buildPath(url_basename); + /+ ↓ download using curl +/ + auto curl_result = execute([ + "curl", + "--silent", "--show-error", + "--fail", /+ fail on HTTP errors +/ + "--location", /+ follow redirects +/ + "--max-redirs", "5", /+ limit redirects +/ + "--max-time", DOWNLOAD_TIMEOUT.to!string, + "--max-filesize", MAX_DOWNLOAD_SIZE.to!string, + "--proto", "=https,http", /+ restrict protocols +/ + "--output", tmp_file, + url + ]); + if (curl_result.status != 0) { + result.error_msg = "download failed: " ~ url; + if (curl_result.output.length > 0) + result.error_msg ~= " - " ~ curl_result.output; + /+ clean up partial download +/ + try { if (exists(tmp_file)) remove(tmp_file); } catch (FileException) {} + return result; + } + if (!exists(tmp_file) || !tmp_file.isFile) { + result.error_msg = "download produced no file: " ~ url; + return result; + } + result.local_path = tmp_file; + result.ok = true; + return result; + } + + /+ ↓ clean up a downloaded temp file +/ + void cleanupDownload(ref DownloadResult dlr) { + if (dlr.local_path.length > 0 && exists(dlr.local_path)) { + try { + remove(dlr.local_path); + } catch (FileException ex) { + stderr.writeln("WARNING: failed to clean up downloaded file: ", dlr.local_path); + } + } + dlr.ok = false; + } + /+ ↓ clean up extracted temp directory +/ void cleanupZipPod(ref ZipPodResult zpr) { if (zpr.pod_dir.length > 0 && exists(zpr.pod_dir)) { diff --git a/src/sisudoc/io_out/create_abstraction_db.d b/src/sisudoc/io_out/create_abstraction_db.d new file mode 100644 index 0000000..20ca074 --- /dev/null +++ b/src/sisudoc/io_out/create_abstraction_db.d @@ -0,0 +1,355 @@ +/+ +- Name: SisuDoc Spine, Doc Reform [a part of] + - Description: documents, structuring, processing, publishing, search + - static content generator + + - Author: Ralph Amissah + [ralph.amissah@gmail.com] + + - Copyright: (C) 2015 (continuously updated, current 2026) Ralph Amissah, All Rights Reserved. + + - License: AGPL 3 or later: + + Spine (SiSU), a framework for document structuring, publishing and + search + + Copyright (C) Ralph Amissah + + This program is free software: you can redistribute it and/or modify it + under the terms of the GNU AFERO General Public License as published by the + Free Software Foundation, either version 3 of the License, or (at your + option) any later version. + + This program is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + more details. + + You should have received a copy of the GNU General Public License along with + this program. If not, see [https://www.gnu.org/licenses/]. + + If you have Internet connection, the latest version of the AGPL should be + available at these locations: + [https://www.fsf.org/licensing/licenses/agpl.html] + [https://www.gnu.org/licenses/agpl.html] + + - Spine (by Doc Reform, related to SiSU) uses standard: + - docReform markup syntax + - standard SiSU markup syntax with modified headers and minor modifications + - docReform object numbering + - standard SiSU object citation numbering & system + + - Homepages: + [https://www.sisudoc.org] + [https://www.doc-reform.org] + + - Git + [https://git.sisudoc.org/] + ++/ +module sisudoc.io_out.create_abstraction_db; + +/+ ↓ write document abstraction as per-document sqlite3 database +/ +template spineAbstractionDb() { + import std.conv : to; + import std.file; + import std.path; + import std.stdio; + import std.string; + import std.array; + import d2sqlite3; + import sisudoc.io_out.paths_output; + + void spineAbstractionDb(D)(D doc) { + auto doc_abstraction = doc.abstraction; + auto doc_matters = doc.matters; + + /+ ↓ determine output path +/ + auto out_pth = spineOutPaths!()(doc_matters.output_path, doc_matters.src.language); + string base_dir = "abstraction"; + string base_pth = ((out_pth.output_base.chainPath(base_dir)).asNormalizedPath).array; + try { + if (!exists(base_pth)) { + base_pth.mkdirRecurse; + } + } catch (Exception ex) { + } + string db_file = ((base_pth.chainPath( + doc_matters.src.doc_uid_out ~ ".abstraction.db")).asNormalizedPath).array; + + /+ ↓ remove existing file to start fresh +/ + try { + if (exists(db_file)) { + remove(db_file); + } + } catch (Exception ex) { + } + + if (doc_matters.opt.action.vox_gt_1) { + writeln(" ", db_file); + } + + /+ ↓ open database and create schema +/ + auto db = Database(db_file); + db.run("PRAGMA journal_mode=WAL"); + db.run("PRAGMA synchronous=NORMAL"); + + db.run(" + CREATE TABLE metadata ( + key TEXT PRIMARY KEY, + value TEXT NOT NULL + ); + + CREATE TABLE objects ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + section TEXT NOT NULL, + seq INTEGER NOT NULL, + ocn INTEGER DEFAULT 0, + is_a TEXT NOT NULL, + is_of_part TEXT, + is_of_type TEXT, + heading_level INTEGER, + identifier TEXT, + parent_ocn INTEGER DEFAULT 0, + last_descendant_ocn INTEGER DEFAULT 0, + ancestors TEXT, + dummy_heading INTEGER DEFAULT 0, + object_number_off INTEGER DEFAULT 0, + indent_base INTEGER DEFAULT 0, + indent_hang INTEGER DEFAULT 0, + bullet INTEGER DEFAULT 0, + lang TEXT, + has_links INTEGER DEFAULT 0, + has_notes_reg INTEGER DEFAULT 0, + has_notes_star INTEGER DEFAULT 0, + has_images INTEGER DEFAULT 0, + segment TEXT, + segment_prev TEXT, + segment_next TEXT, + anchor TEXT, + table_cols INTEGER, + table_widths TEXT, + table_header INTEGER, + code_syntax TEXT, + code_linenumbers INTEGER DEFAULT 0, + text TEXT + ); + + CREATE INDEX idx_objects_section ON objects(section); + CREATE INDEX idx_objects_ocn ON objects(ocn); + CREATE INDEX idx_objects_parent ON objects(parent_ocn); + CREATE INDEX idx_objects_is_a ON objects(is_a); + CREATE INDEX idx_objects_heading ON objects(heading_level) + WHERE heading_level IS NOT NULL; + "); + + /+ ↓ populate metadata +/ + 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) { + meta_stmt.bind(":key", key); + meta_stmt.bind(":value", value); + meta_stmt.execute(); + meta_stmt.reset(); + } + } + + 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); + + meta_stmt.finalize(); + + /+ ↓ populate objects +/ + auto obj_stmt = db.prepare( + "INSERT INTO objects (" + ~ "section, seq, ocn, is_a, is_of_part, is_of_type," + ~ "heading_level, identifier, parent_ocn, last_descendant_ocn," + ~ "ancestors, dummy_heading, object_number_off," + ~ "indent_base, indent_hang, bullet, lang," + ~ "has_links, has_notes_reg, has_notes_star, has_images," + ~ "segment, segment_prev, segment_next, anchor," + ~ "table_cols, table_widths, table_header," + ~ "code_syntax, code_linenumbers, text" + ~ ") VALUES (" + ~ ":section, :seq, :ocn, :is_a, :is_of_part, :is_of_type," + ~ ":heading_level, :identifier, :parent_ocn, :last_descendant_ocn," + ~ ":ancestors, :dummy_heading, :object_number_off," + ~ ":indent_base, :indent_hang, :bullet, :lang," + ~ ":has_links, :has_notes_reg, :has_notes_star, :has_images," + ~ ":segment, :segment_prev, :segment_next, :anchor," + ~ ":table_cols, :table_widths, :table_header," + ~ ":code_syntax, :code_linenumbers, :text" + ~ ")" + ); + + string[] section_order = ["head", "toc", "body", "endnotes", + "glossary", "bibliography", "bookindex", "blurb"]; + + foreach (section; section_order) { + if (section !in doc_abstraction) continue; + auto section_objs = doc_abstraction[section]; + if (section_objs.length == 0) continue; + + foreach (seq, obj; section_objs) { + obj_stmt.bind(":section", section); + obj_stmt.bind(":seq", cast(int) seq); + obj_stmt.bind(":ocn", obj.metainfo.ocn); + obj_stmt.bind(":is_a", obj.metainfo.is_a); + + /+ ↓ nullable string fields +/ + void bindStr(string param, string val) { + import std.typecons : Nullable; + if (val.length > 0) { + obj_stmt.bind(param, val); + } else { + obj_stmt.bind(param, Nullable!string()); + } + } + + bindStr(":is_of_part", obj.metainfo.is_of_part); + bindStr(":is_of_type", obj.metainfo.is_of_type); + + /+ ↓ heading level +/ + { + import std.typecons : Nullable; + if (obj.metainfo.is_a == "heading" && obj.metainfo.heading_lev_markup < 9) { + obj_stmt.bind(":heading_level", obj.metainfo.heading_lev_markup); + } else { + obj_stmt.bind(":heading_level", Nullable!int()); + } + } + + bindStr(":identifier", obj.metainfo.identifier); + obj_stmt.bind(":parent_ocn", obj.metainfo.parent_ocn); + obj_stmt.bind(":last_descendant_ocn", obj.metainfo.last_descendant_ocn); + + /+ ↓ ancestors as space-separated integers +/ + { + bool has_ancestors = false; + foreach (a; obj.metainfo.markedup_ancestors) { + if (a != 0) { has_ancestors = true; break; } + } + if (has_ancestors) { + string anc; + foreach (i, a; obj.metainfo.markedup_ancestors) { + if (i > 0) anc ~= " "; + anc ~= a.to!string; + } + obj_stmt.bind(":ancestors", anc); + } else { + import std.typecons : Nullable; + obj_stmt.bind(":ancestors", Nullable!string()); + } + } + + obj_stmt.bind(":dummy_heading", obj.metainfo.dummy_heading ? 1 : 0); + obj_stmt.bind(":object_number_off", obj.metainfo.object_number_off ? 1 : 0); + obj_stmt.bind(":indent_base", obj.attrib.indent_base); + obj_stmt.bind(":indent_hang", obj.attrib.indent_hang); + obj_stmt.bind(":bullet", obj.attrib.bullet ? 1 : 0); + bindStr(":lang", obj.attrib.language); + obj_stmt.bind(":has_links", obj.has.inline_links ? 1 : 0); + obj_stmt.bind(":has_notes_reg", obj.has.inline_notes_reg ? 1 : 0); + obj_stmt.bind(":has_notes_star", obj.has.inline_notes_star ? 1 : 0); + obj_stmt.bind(":has_images", obj.has.images ? 1 : 0); + bindStr(":segment", obj.tags.in_segment_html); + bindStr(":segment_prev", obj.tags.segname_prev); + bindStr(":segment_next", obj.tags.segname_next); + bindStr(":anchor", obj.tags.anchor_tag_html); + + /+ ↓ table properties +/ + { + import std.typecons : Nullable; + if (obj.metainfo.is_a == "table" && obj.table.number_of_columns > 0) { + obj_stmt.bind(":table_cols", obj.table.number_of_columns); + if (obj.table.column_widths.length > 0) { + string[] ws; + foreach (w; obj.table.column_widths) ws ~= w.to!string; + obj_stmt.bind(":table_widths", ws.join(" ")); + } else { + obj_stmt.bind(":table_widths", Nullable!string()); + } + obj_stmt.bind(":table_header", obj.table.heading ? 1 : 0); + } else { + obj_stmt.bind(":table_cols", Nullable!int()); + obj_stmt.bind(":table_widths", Nullable!string()); + obj_stmt.bind(":table_header", Nullable!int()); + } + } + + /+ ↓ code block properties +/ + { + import std.typecons : Nullable; + if (obj.metainfo.is_a == "code") { + bindStr(":code_syntax", obj.code_block.syntax); + obj_stmt.bind(":code_linenumbers", obj.code_block.linenumbers ? 1 : 0); + } else { + obj_stmt.bind(":code_syntax", Nullable!string()); + obj_stmt.bind(":code_linenumbers", 0); + } + } + + /+ ↓ text content +/ + bindStr(":text", obj.text); + + obj_stmt.execute(); + obj_stmt.reset(); + } + } + + obj_stmt.finalize(); + db.run("COMMIT TRANSACTION"); + } +} diff --git a/src/sisudoc/io_out/create_abstraction_txt.d b/src/sisudoc/io_out/create_abstraction_txt.d new file mode 100644 index 0000000..af98f61 --- /dev/null +++ b/src/sisudoc/io_out/create_abstraction_txt.d @@ -0,0 +1,430 @@ +/+ +- Name: SisuDoc Spine, Doc Reform [a part of] + - Description: documents, structuring, processing, publishing, search + - static content generator + + - Author: Ralph Amissah + [ralph.amissah@gmail.com] + + - Copyright: (C) 2015 (continuously updated, current 2026) Ralph Amissah, All Rights Reserved. + + - License: AGPL 3 or later: + + Spine (SiSU), a framework for document structuring, publishing and + search + + Copyright (C) Ralph Amissah + + This program is free software: you can redistribute it and/or modify it + under the terms of the GNU AFERO General Public License as published by the + Free Software Foundation, either version 3 of the License, or (at your + option) any later version. + + This program is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + more details. + + You should have received a copy of the GNU General Public License along with + this program. If not, see [https://www.gnu.org/licenses/]. + + If you have Internet connection, the latest version of the AGPL should be + available at these locations: + [https://www.fsf.org/licensing/licenses/agpl.html] + [https://www.gnu.org/licenses/agpl.html] + + - Spine (by Doc Reform, related to SiSU) uses standard: + - docReform markup syntax + - standard SiSU markup syntax with modified headers and minor modifications + - docReform object numbering + - standard SiSU object citation numbering & system + + - Homepages: + [https://www.sisudoc.org] + [https://www.doc-reform.org] + + - Git + [https://git.sisudoc.org/] + ++/ +module sisudoc.io_out.create_abstraction_txt; +@safe: + +/+ ↓ write document abstraction as human-readable .ssp text file +/ +template spineAbstractionTxt() { + import std.conv : to; + import std.digest : toHexString; + import std.file; + import std.path; + import std.stdio; + import std.string; + import std.array; + import sisudoc.io_out.paths_output; + + void spineAbstractionTxt(D)(D doc) { + auto doc_abstraction = doc.abstraction; + auto doc_matters = doc.matters; + string[] output; + + /+ ↓ header comment +/ + output ~= "% SiSU Document Abstraction v0.1"; + output ~= "% Source: " ~ doc_matters.src.filename; + output ~= ""; + + /+ ↓ @meta block +/ + output ~= "@meta {"; + auto meta = doc_matters.conf_make_meta.meta; + if (meta.title_main.length > 0) + output ~= " title.main: " ~ meta.title_main; + if (meta.title_subtitle.length > 0) + output ~= " title.subtitle: " ~ meta.title_subtitle; + if (meta.title_full.length > 0) + output ~= " title.full: " ~ meta.title_full; + if (meta.title_language.length > 0) + output ~= " title.language: " ~ meta.title_language; + if (meta.creator_author.length > 0) + output ~= " creator.author: " ~ meta.creator_author; + if (meta.creator_author_surname.length > 0) + output ~= " creator.author_surname: " ~ meta.creator_author_surname; + if (meta.creator_author_surname_fn.length > 0) + output ~= " creator.author_surname_fn: " ~ meta.creator_author_surname_fn; + if (meta.creator_author_email.length > 0) + output ~= " creator.author_email: " ~ meta.creator_author_email; + if (meta.creator_illustrator.length > 0) + output ~= " creator.illustrator: " ~ meta.creator_illustrator; + if (meta.creator_translator.length > 0) + output ~= " creator.translator: " ~ meta.creator_translator; + if (meta.date_published.length > 0) + output ~= " date.published: " ~ meta.date_published; + if (meta.date_created.length > 0) + output ~= " date.created: " ~ meta.date_created; + if (meta.date_issued.length > 0) + output ~= " date.issued: " ~ meta.date_issued; + if (meta.date_available.length > 0) + output ~= " date.available: " ~ meta.date_available; + if (meta.date_modified.length > 0) + output ~= " date.modified: " ~ meta.date_modified; + if (meta.date_valid.length > 0) + output ~= " date.valid: " ~ meta.date_valid; + if (meta.rights_copyright.length > 0) + output ~= " rights.copyright: " ~ meta.rights_copyright; + if (meta.rights_license.length > 0) + output ~= " rights.license: " ~ meta.rights_license; + if (meta.classify_topic_register.length > 0) + output ~= " classify.topic_register: " ~ meta.classify_topic_register; + if (meta.classify_subject.length > 0) + output ~= " classify.subject: " ~ meta.classify_subject; + if (meta.classify_keywords.length > 0) + output ~= " classify.keywords: " ~ meta.classify_keywords; + if (meta.classify_loc.length > 0) + output ~= " classify.loc: " ~ meta.classify_loc; + if (meta.classify_dewey.length > 0) + output ~= " classify.dewey: " ~ meta.classify_dewey; + if (meta.identifier_isbn.length > 0) + output ~= " identifier.isbn: " ~ meta.identifier_isbn; + if (meta.identifier_oclc.length > 0) + output ~= " identifier.oclc: " ~ meta.identifier_oclc; + if (meta.language_document.length > 0) + output ~= " language.document: " ~ meta.language_document; + if (meta.notes_abstract.length > 0) + output ~= " notes.abstract: " ~ meta.notes_abstract; + if (meta.notes_description.length > 0) + output ~= " notes.description: " ~ meta.notes_description; + if (meta.notes_summary.length > 0) + output ~= " notes.summary: " ~ meta.notes_summary; + output ~= "}"; + output ~= ""; + + /+ ↓ @make block +/ + output ~= "@make {"; + auto make = doc_matters.conf_make_meta.make; + if (make.doc_type.length > 0) + output ~= " doc_type: " ~ make.doc_type; + if (make.auto_num_top_at_level.length > 0) + output ~= " auto_num_top_at_level: " ~ make.auto_num_top_at_level; + output ~= " auto_num_top_lv: " ~ make.auto_num_top_lv.to!string; + output ~= " auto_num_depth: " ~ make.auto_num_depth.to!string; + output ~= "}"; + output ~= ""; + + /+ ↓ @doc_has block +/ + output ~= "@doc_has {"; + output ~= " inline_links: " ~ doc_matters.has.inline_links.to!string; + output ~= " inline_notes_reg: " ~ doc_matters.has.inline_notes_reg.to!string; + output ~= " inline_notes_star: " ~ doc_matters.has.inline_notes_star.to!string; + output ~= " tables: " ~ doc_matters.has.tables.to!string; + output ~= " codeblocks: " ~ doc_matters.has.codeblocks.to!string; + output ~= " images: " ~ doc_matters.has.images.to!string; + output ~= " poems: " ~ doc_matters.has.poems.to!string; + output ~= " groups: " ~ doc_matters.has.groups.to!string; + output ~= " blocks: " ~ doc_matters.has.blocks.to!string; + output ~= " quotes: " ~ doc_matters.has.quotes.to!string; + output ~= "}"; + output ~= ""; + + /+ ↓ document sections +/ + string[] section_order = ["head", "toc", "body", "endnotes", + "glossary", "bibliography", "bookindex", "blurb"]; + + foreach (section; section_order) { + if (section !in doc_abstraction) continue; + auto section_objs = doc_abstraction[section]; + if (section_objs.length == 0) continue; + + output ~= "@" ~ section ~ " {"; + output ~= ""; + + foreach (obj; section_objs) { + /+ ↓ object declaration line +/ + string obj_decl = "[" ~ obj.metainfo.ocn.to!string ~ "] "; + + if (obj.metainfo.is_a == "heading") { + string lev = obj.metainfo.marked_up_level; + obj_decl ~= "heading :" ~ lev; + if (obj.metainfo.identifier.length > 0 + && obj.metainfo.identifier != obj.metainfo.ocn.to!string) { + obj_decl ~= " " ~ obj.metainfo.identifier; + } + } else { + obj_decl ~= obj.metainfo.is_a; + } + output ~= obj_decl; + + /+ ↓ properties (only non-default values) +/ + if (obj.metainfo.is_of_part.length > 0) + output ~= ".part: " ~ obj.metainfo.is_of_part; + if (obj.metainfo.is_of_section.length > 0 + && obj.metainfo.is_of_section != section) + output ~= ".section: " ~ obj.metainfo.is_of_section; + if (obj.metainfo.parent_ocn != 0) + output ~= ".parent: " ~ obj.metainfo.parent_ocn.to!string; + if (obj.metainfo.last_descendant_ocn != 0) + output ~= ".last_descendant: " ~ obj.metainfo.last_descendant_ocn.to!string; + + /+ ↓ child headings (from pre-computed map) +/ + if (obj.metainfo.children_headings.length > 0) { + string[] ch; + foreach (c; obj.metainfo.children_headings) { + ch ~= c.to!string; + } + output ~= ".children: " ~ ch.join(" "); + } + + /+ ↓ ancestors (only if non-zero) +/ + { + bool has_anc = false; + foreach (a; obj.metainfo.markedup_ancestors) { + if (a != 0) { has_anc = true; break; } + } + if (has_anc) { + string anc; + foreach (i, a; obj.metainfo.markedup_ancestors) { + if (i > 0) anc ~= " "; + anc ~= a.to!string; + } + output ~= ".ancestors: " ~ anc; + } + } + /+ ↓ collapsed ancestors (only if non-zero) +/ + { + bool has_anc_c = false; + foreach (a; obj.metainfo.collapsed_ancestors) { + if (a != 0) { has_anc_c = true; break; } + } + if (has_anc_c) { + string anc; + foreach (i, a; obj.metainfo.collapsed_ancestors) { + if (i > 0) anc ~= " "; + anc ~= a.to!string; + } + output ~= ".ancestors_collapsed: " ~ anc; + } + } + /+ ↓ dom structure status (only if non-zero) +/ + { + bool has_dom = false; + foreach (d; obj.metainfo.dom_structure_markedup_tags_status) { + if (d != 0) { has_dom = true; break; } + } + if (has_dom) { + string ds; + foreach (i, d; obj.metainfo.dom_structure_markedup_tags_status) { + if (i > 0) ds ~= " "; + ds ~= d.to!string; + } + output ~= ".dom_status: " ~ ds; + } + } + { + bool has_dom_c = false; + foreach (d; obj.metainfo.dom_structure_collapsed_tags_status) { + if (d != 0) { has_dom_c = true; break; } + } + if (has_dom_c) { + string ds; + foreach (i, d; obj.metainfo.dom_structure_collapsed_tags_status) { + if (i > 0) ds ~= " "; + ds ~= d.to!string; + } + output ~= ".dom_status_collapsed: " ~ ds; + } + } + + if (obj.metainfo.heading_lev_collapsed < 9) + output ~= ".heading_lev_collapsed: " ~ obj.metainfo.heading_lev_collapsed.to!string; + if (obj.metainfo.parent_lev_markup != 0) + output ~= ".parent_lev: " ~ obj.metainfo.parent_lev_markup.to!string; + if (obj.metainfo.dummy_heading) + output ~= ".dummy: true"; + if (obj.metainfo.object_number_off) + output ~= ".ocn_off: true"; + if (obj.metainfo.o_n_type != 0) + output ~= ".o_n_type: " ~ obj.metainfo.o_n_type.to!string; + if (obj.metainfo.is_of_type.length > 0) + output ~= ".is_of_type: " ~ obj.metainfo.is_of_type; + if (obj.metainfo.attrib.length > 0) + output ~= ".attrib: " ~ obj.metainfo.attrib; + if (obj.metainfo.lang.length > 0) + output ~= ".meta_lang: " ~ obj.metainfo.lang; + if (obj.metainfo.syntax.length > 0) + output ~= ".meta_syntax: " ~ obj.metainfo.syntax; + + /+ ↓ sha256 digest +/ + { + bool has_sha = false; + foreach (b; obj.metainfo.sha256) { + if (b != 0) { has_sha = true; break; } + } + if (has_sha) { + output ~= ".sha256: " ~ obj.metainfo.sha256.toHexString.to!string; + } + } + + /+ ↓ text attributes +/ + if (obj.attrib.indent_base != 0 || obj.attrib.indent_hang != 0) + output ~= ".indent: " ~ obj.attrib.indent_base.to!string + ~ " " ~ obj.attrib.indent_hang.to!string; + if (obj.attrib.bullet) + output ~= ".bullet: true"; + if (obj.attrib.language.length > 0) + output ~= ".lang: " ~ obj.attrib.language; + + /+ ↓ has flags +/ + { + string[] has_flags; + if (obj.has.inline_links) has_flags ~= "links"; + if (obj.has.inline_notes_reg) has_flags ~= "notes_reg"; + if (obj.has.inline_notes_star) has_flags ~= "notes_star"; + if (obj.has.images) has_flags ~= "images"; + if (obj.has.image_without_dimensions) has_flags ~= "images_no_dim"; + if (has_flags.length > 0) + output ~= ".has: " ~ has_flags.join(" "); + } + + /+ ↓ table properties +/ + if (obj.metainfo.is_a == "table" && obj.table.number_of_columns > 0) { + output ~= ".table_cols: " ~ obj.table.number_of_columns.to!string; + if (obj.table.column_widths.length > 0) { + string[] ws; + foreach (w; obj.table.column_widths) ws ~= w.to!string; + output ~= ".table_widths: " ~ ws.join(" "); + } + if (obj.table.column_aligns.length > 0) { + output ~= ".table_aligns: " ~ obj.table.column_aligns.join(" "); + } + if (obj.table.heading) + output ~= ".table_header: true"; + if (obj.table.walls) + output ~= ".table_walls: true"; + } + + /+ ↓ code block properties +/ + if (obj.metainfo.is_a == "code") { + if (obj.code_block.syntax.length > 0) + output ~= ".code_syntax: " ~ obj.code_block.syntax; + if (obj.code_block.linenumbers) + output ~= ".code_linenumbers: true"; + } + + /+ ↓ stow (extracted links) +/ + if (obj.stow.link.length > 0) { + foreach (lnk; obj.stow.link) { + if (lnk.length > 0) + output ~= ".stow_link: " ~ lnk; + } + } + + /+ ↓ tag properties +/ + if (obj.tags.in_segment_html.length > 0) + output ~= ".segment: " ~ obj.tags.in_segment_html; + if (obj.tags.anchor_tag_html.length > 0 + && obj.tags.anchor_tag_html != obj.tags.in_segment_html) + output ~= ".anchor: " ~ obj.tags.anchor_tag_html; + if (obj.tags.segname_prev.length > 0) + output ~= ".segment_prev: " ~ obj.tags.segname_prev; + if (obj.tags.segname_next.length > 0) + output ~= ".segment_next: " ~ obj.tags.segname_next; + if (obj.tags.heading_lev_anchor_tag.length > 0) + output ~= ".heading_lev_anchor: " ~ obj.tags.heading_lev_anchor_tag; + if (obj.tags.segment_anchor_tag_epub.length > 0) + output ~= ".segment_epub: " ~ obj.tags.segment_anchor_tag_epub; + /+ ↓ heading ancestors text +/ + { + bool has_hat = false; + foreach (h; obj.tags.heading_ancestors_text) { + if (h.length > 0) { has_hat = true; break; } + } + if (has_hat) { + output ~= ".heading_ancestors_text: " ~ obj.tags.heading_ancestors_text.join("|"); + } + } + /+ ↓ lev4 subtoc +/ + if (obj.tags.lev4_subtoc.length > 0) { + foreach (st; obj.tags.lev4_subtoc) { + if (st.length > 0) + output ~= ".lev4_subtoc: " ~ st; + } + } + /+ ↓ anchor tags +/ + if (obj.tags.anchor_tags.length > 0) { + foreach (at; obj.tags.anchor_tags) { + if (at.length > 0) + output ~= ".anchor_tag: " ~ at; + } + } + + /+ ↓ text content +/ + if (obj.text.length > 0) { + foreach (line; obj.text.split("\n")) { + output ~= "| " ~ line; + } + } + + output ~= ""; + } + + output ~= "}"; + output ~= ""; + } + + /+ ↓ write to file +/ + auto out_pth = spineOutPaths!()(doc_matters.output_path, doc_matters.src.language); + string base_dir = "abstraction"; + string base_pth = ((out_pth.output_base.chainPath(base_dir)).asNormalizedPath).array; + try { + if (!exists(base_pth)) { + base_pth.mkdirRecurse; + } + } catch (Exception ex) { + } + string out_file = ((base_pth.chainPath( + doc_matters.src.doc_uid_out ~ ".ssp")).asNormalizedPath).array; + if (doc_matters.opt.action.vox_gt_1) { + writeln(" ", out_file); + } + auto f = File(out_file, "w"); + foreach (line; output) { + f.writeln(line); + } + } +} diff --git a/src/sisudoc/io_out/source_pod.d b/src/sisudoc/io_out/source_pod.d index fa9794b..138f105 100644 --- a/src/sisudoc/io_out/source_pod.d +++ b/src/sisudoc/io_out/source_pod.d @@ -72,6 +72,15 @@ template spinePod() { assert (doc_matters.src.filename.match(rgx_files.src_fn)); if (doc_matters.opt.action.source_or_pod) { try { + /+ ↓ clean slate: remove per-document pod directory before regeneration, + but only on the first language of a multi-language document, + so that subsequent languages' files are not wiped +/ + if (doc_matters.src.language == doc_matters.pod.manifest_list_of_languages[0]) { + string doc_pod_dir = pths_pod.base_filesystem_(doc_matters.src.filename); + if (exists(doc_pod_dir) && doc_pod_dir.isDir) { + doc_pod_dir.rmdirRecurse; + } + } { podArchive_directory_tree(doc_matters, pths_pod); } @@ -114,6 +123,11 @@ template spinePod() { if (!exists(pths_pod.css(doc_matters.src.filename).filesystem_open_zpod)) { pths_pod.css(doc_matters.src.filename).filesystem_open_zpod.mkdirRecurse; } + if (doc_matters.opt.action.pod2) { + if (!exists(pths_pod.abstraction_root(doc_matters.src.filename).filesystem_open_zpod)) { + pths_pod.abstraction_root(doc_matters.src.filename).filesystem_open_zpod.mkdirRecurse; + } + } if (!exists(pths_pod.image_root(doc_matters.src.filename).filesystem_open_zpod)) { pths_pod.image_root(doc_matters.src.filename).filesystem_open_zpod.mkdirRecurse; } @@ -179,6 +193,34 @@ template spinePod() { } } } + } { // bundle abstraction .ssp file (only for --pod2) + if (doc_matters.opt.action.pod2) { + import sisudoc.io_out.paths_output; + auto out_pth = spineOutPaths!()(doc_matters.output_path, doc_matters.src.language); + string abstraction_dir = ((out_pth.output_base.chainPath("abstraction")).asNormalizedPath).array; + string ssp_filename = doc_matters.src.doc_uid_out ~ ".ssp"; + string fn_src_in = ((abstraction_dir.chainPath(ssp_filename)).asNormalizedPath).array.to!string; + auto fn_src_out_pod_zip_base + = pths_pod.abstraction_root(doc_matters.src.filename).zpod.to!string + ~ "/" ~ ssp_filename; + auto fn_src_out_filesystem + = pths_pod.abstraction_root(doc_matters.src.filename).filesystem_open_zpod.to!string + ~ "/" ~ ssp_filename; + if (exists(fn_src_in)) { + debug(io) { writeln("(io debug) src out found: ", fn_src_in); } + { // take DIGEST write to pod file digests.txt + auto data = (cast(byte[]) (fn_src_in).read); + _digests[doc_matters.src.language]["ssp"] ~= data.sha256Of.toHexString + ~ "::" ~ data.length.to!string ~ " - " ~ ssp_filename ~ "\n"; + } + fn_src_in.copy(fn_src_out_filesystem); + zip = podArchive("file_path_text", fn_src_in, fn_src_out_pod_zip_base, zip); + } else { + if (doc_matters.opt.action.debug_do_pod && doc_matters.opt.action.vox_gt_2) { + writeln("WARNING (io) src out NOT found (abstraction): ", fn_src_in); + } + } + } } { // bundle dr_document_make auto fn_src_in = ((doc_matters.src.is_pod) ? doc_matters.src.conf_dir_path @@ -475,6 +517,9 @@ template spinePod() { // if (doc_matters.opt.action.vox_gt_2) { writeln(_digests[_lang]["ssi"]); } f.writeln(_digests[_lang]["ssi"]); } + if (("ssp" in _digests[_lang]) && (_digests[_lang]["ssp"].length > 0)) { + f.writeln(_digests[_lang]["ssp"]); + } } } if ("shared" in _digests) { diff --git a/src/sisudoc/meta/metadoc_from_src.d b/src/sisudoc/meta/metadoc_from_src.d index a4388fb..4967c1f 100644 --- a/src/sisudoc/meta/metadoc_from_src.d +++ b/src/sisudoc/meta/metadoc_from_src.d @@ -1331,6 +1331,25 @@ template docAbstraction() { } } } + /+ ↓ compute children_headings for heading objects +/ + { + int[][int] heading_children; + foreach (obj; the_document_body_section) { + if (obj.metainfo.is_a == "heading" && obj.metainfo.parent_ocn != 0) { + heading_children[obj.metainfo.parent_ocn] ~= obj.metainfo.ocn; + } + } + foreach (ref obj; the_document_head_section) { + if (obj.metainfo.is_a == "heading" && obj.metainfo.ocn in heading_children) { + obj.metainfo.children_headings = heading_children[obj.metainfo.ocn]; + } + } + foreach (ref obj; the_document_body_section) { + if (obj.metainfo.is_a == "heading" && obj.metainfo.ocn in heading_children) { + obj.metainfo.children_headings = heading_children[obj.metainfo.ocn]; + } + } + } // TODO // - note create/insert heading object sole purpose eof close all open tags // sort out: diff --git a/src/sisudoc/meta/metadoc_object_setter.d b/src/sisudoc/meta/metadoc_object_setter.d index bd4f7c8..018c51b 100644 --- a/src/sisudoc/meta/metadoc_object_setter.d +++ b/src/sisudoc/meta/metadoc_object_setter.d @@ -173,6 +173,7 @@ template ObjectSetter() { int parent_lev_markup = 0; int parent_ocn = 0; int last_descendant_ocn = 0; + int[] children_headings; ubyte[32] sha256; } struct ObjGenericComposite { diff --git a/src/sisudoc/spine.d b/src/sisudoc/spine.d index ee3bcef..e710281 100755 --- a/src/sisudoc/spine.d +++ b/src/sisudoc/spine.d @@ -165,7 +165,10 @@ string program_name = "spine"; "pdf-color-links" : false, "pdf-init" : false, "pod" : false, + "pod2" : false, "serial" : false, + "show-abstraction" : false, + "show-abstraction-db" : false, "show-config" : false, "show-curate" : false, "show-curate-authors" : false, @@ -276,6 +279,7 @@ string program_name = "spine"; "pdf-color-links", "mono or color links for pdfs", &opts["pdf-color-links"], "pdf-init", "initialise latex shared files (see latex-header-sty)", &opts["pdf-init"], "pod", "spine (doc reform) pod source content bundled", &opts["pod"], + "pod2", "pod with document abstraction (.ssp) bundled", &opts["pod2"], "quiet|q", "output to terminal", &opts["vox_is1"], "section-backmatter", "document backmatter (default)" , &opts["backmatter"], "section-biblio", "document biblio (default)", &opts["section_biblio"], @@ -287,6 +291,8 @@ string program_name = "spine"; "section-toc", "table of contents (default)", &opts["section_toc"], "serial", "serial processing", &opts["serial"], "skip-output", "skip output", &opts["skip-output"], + "show-abstraction", "show document abstraction (write .ssp file)", &opts["show-abstraction"], + "show-abstraction-db", "show document abstraction (write .db sqlite file)", &opts["show-abstraction-db"], "show-config", "show config", &opts["show-config"], "show-curate", "show curate", &opts["show-curate"], "show-curate-authors", "show curate authors", &opts["show-curate-authors"], @@ -493,11 +499,20 @@ string program_name = "spine"; return ((opts["ocn-off"]) || (opts["no-ocn"])) ? true : false; } @trusted bool pod() { - return opts["pod"]; + return (opts["pod"] || opts["pod2"]) ? true : false; + } + @trusted bool pod2() { + return opts["pod2"]; } @trusted bool show_config() { return opts["show-config"]; } + @trusted bool show_abstraction() { + return (opts["show-abstraction"] || pod2) ? true : false; + } + @trusted bool show_abstraction_db() { + return opts["show-abstraction-db"]; + } @trusted bool show_curate() { return opts["show-curate"]; } @@ -538,7 +553,7 @@ string program_name = "spine"; return opts["source"]; } @trusted bool source_or_pod() { - return (opts["pod"] || opts["source"]) ? true : false; + return (opts["pod"] || opts["pod2"] || opts["source"]) ? true : false; } @trusted bool sqlite_discrete() { return opts["sqlite-discrete"]; @@ -731,21 +746,23 @@ string program_name = "spine"; } auto output_task_scheduler() { int[] schedule; - if (source_or_pod) { schedule ~= outTask.source_or_pod; } - if (sqlite_discrete) { schedule ~= outTask.sqlite; } - if (epub) { schedule ~= outTask.epub; } - if (html_scroll) { schedule ~= outTask.html_scroll; } - if (html_seg) { schedule ~= outTask.html_seg; } - if (html_stuff) { schedule ~= outTask.html_stuff; } - if (odt) { schedule ~= outTask.odt; } - if (latex) { schedule ~= outTask.latex; } - if (text) { schedule ~= outTask.text; } - if (skel) { schedule ~= outTask.skel; } + if (source_or_pod) schedule ~= outTask.source_or_pod; + if (sqlite_discrete) schedule ~= outTask.sqlite; + if (epub) schedule ~= outTask.epub; + if (html_scroll) schedule ~= outTask.html_scroll; + if (html_seg) schedule ~= outTask.html_seg; + if (html_stuff) schedule ~= outTask.html_stuff; + if (odt) schedule ~= outTask.odt; + if (latex) schedule ~= outTask.latex; + if (text) schedule ~= outTask.text; + if (skel) schedule ~= outTask.skel; return schedule.sort().uniq; } @trusted bool abstraction() { return ( opts["abstraction"] + || show_abstraction + || show_abstraction_db || concordance || source_or_pod || curate @@ -772,6 +789,8 @@ string program_name = "spine"; || latex || odt || manifest + || show_abstraction + || show_abstraction_db || show_make || show_metadata || show_summary @@ -786,6 +805,8 @@ string program_name = "spine"; @trusted bool meta_processing_general() { return ( opts["abstraction"] + || show_abstraction + || show_abstraction_db || curate || html || epub @@ -860,12 +881,31 @@ string program_name = "spine"; /+ ↓ track extracted zip pod temp directories for cleanup +/ mixin spineExtractZipPod; ZipPodResult[] _zip_pod_extractions; + DownloadResult[] _url_downloads; + /+ ↓ pre-process args: resolve URL arguments to local temp files +/ + string[] _resolved_args; + foreach (arg; args[1..$]) { + if (isUrl(arg)) { + auto _dlr = downloadZipUrl(arg); + if (_dlr.ok) { + _url_downloads ~= _dlr; + _resolved_args ~= _dlr.local_path; + if (_opt_action.vox_gt_1) { + writeln("downloaded: ", arg, " -> ", _dlr.local_path); + } + } else { + writeln("ERROR >> Download failed: ", arg, " - ", _dlr.error_msg); + } + } else { + _resolved_args ~= arg; + } + } ConfComposite _siteConfig; if ( _opt_action.require_processing_files && _opt_action.config_path_set.empty ) { - foreach(arg; args[1..$]) { + foreach(arg; _resolved_args) { if (!(arg.match(rgx.flag_action))) { /+ cli markup source path +/ // get first input markup source file names for processing string _config_arg = arg; /+ ↓ if first non-flag arg is a zip, extract for config discovery +/ @@ -910,7 +950,7 @@ string program_name = "spine"; } ConfComposite _make_and_meta_struct = _siteConfig; destroy(_siteConfig); - foreach(arg; args[1..$]) { + foreach(arg; _resolved_args) { if (arg.match(rgx.flag_action)) { /+ cli instruction, flag do +/ flag_action ~= " " ~ arg; // flags not taken by getopt } else if (_opt_action.require_processing_files) { /+ cli, assumed to be path to source files +/ @@ -1284,6 +1324,16 @@ string program_name = "spine"; import sisudoc.meta.metadoc_show_config; spineShowConfig!()(doc.matters); } + /+ ↓ document abstraction text representation +/ + if (doc.matters.opt.action.show_abstraction) { + import sisudoc.io_out.create_abstraction_txt; + spineAbstractionTxt!()(doc); + } + /+ ↓ document abstraction sqlite database +/ + if (doc.matters.opt.action.show_abstraction_db) { + import sisudoc.io_out.create_abstraction_db; + spineAbstractionDb!()(doc); + } if (doc.matters.opt.action.curate) { auto _hvst = spineMetaDocCurate!()(doc.matters, hvst); if ( @@ -1383,6 +1433,16 @@ string program_name = "spine"; import sisudoc.meta.metadoc_show_config; spineShowConfig!()(doc.matters); } + /+ ↓ document abstraction text representation +/ + if (doc.matters.opt.action.show_abstraction) { + import sisudoc.io_out.create_abstraction_txt; + spineAbstractionTxt!()(doc); + } + /+ ↓ document abstraction sqlite database +/ + if (doc.matters.opt.action.show_abstraction_db) { + import sisudoc.io_out.create_abstraction_db; + spineAbstractionDb!()(doc); + } if (doc.matters.opt.action.curate) { auto _hvst = spineMetaDocCurate!()(doc.matters, hvst); if ( @@ -1453,4 +1513,8 @@ string program_name = "spine"; foreach (ref _zpr; _zip_pod_extractions) { cleanupZipPod(_zpr); } + /+ ↓ clean up any downloaded temp files +/ + foreach (ref _dlr; _url_downloads) { + cleanupDownload(_dlr); + } } |
