Sfoglia il codice sorgente

mkpstm: expand legacy statum.js references into the four client modules

A <script pstm-res-src="statum.js"> on a composed page now expands at build time into the four module tags in canonical load order (core, document, markdown, forms), each with a plain final src attribute so the generic pstm-res rewrite and strip_pstm leave them alone. Explicit pstm-res-src module references the expansion covers are subsumed (each module appears exactly once), later duplicate statum.js references are dropped, and plain src= references are never touched. One build warning per page names the page, the expansion, and any dedupe performed. Direct module-only pages stay silent. example/main.html is deliberately unmodified: it is the backwards-compat proof.
saddle-ClankerSaddle 3 settimane fa
parent
commit
5c027e388d
1 ha cambiato i file con 136 aggiunte e 0 eliminazioni
  1. 136 0
      tools/statum-mkpstm/statum-mkpstm.vala

+ 136 - 0
tools/statum-mkpstm/statum-mkpstm.vala

@@ -26,6 +26,16 @@ namespace Statum.Tools {
         private Dictionary<string, Xml.Doc*> templates_by_name = new Dictionary<string, Xml.Doc*>();
         private Dictionary<string, Xml.Doc*> fragments_by_name = new Dictionary<string, Xml.Doc*>();
 
+        /**
+         * The Statum client ships as four modules with a strict load order
+         * (core first: every later module registers into the seams core
+         * installs). A legacy single-file `pstm-res-src="statum.js"` reference
+         * expands at build time into exactly these four tags.
+         */
+        private const string[] CLIENT_MODULE_ORDER = {
+            "statum-core.js", "statum-document.js", "statum-markdown.js", "statum-forms.js"
+        };
+
         private const OptionEntry[] options = {
             { "output", 'o', 0, OptionArg.FILENAME, ref output_file, "Output Vala file name", "FILE" },
             { "ns", '\0', 0, OptionArg.STRING, ref namespace_name, "Namespace for the generated class", "NAMESPACE" },
@@ -85,6 +95,7 @@ namespace Statum.Tools {
 
             doc = compose_templates(doc);
             inline_fragments(doc);
+            expand_legacy_client(doc, page_file);
             rewrite_resources(doc);
 
             var variant = build_variant(doc);
@@ -398,6 +409,124 @@ namespace Statum.Tools {
             }
         }
 
+        /**
+         * Expands a legacy single-file client reference into the four ordered
+         * module tags.
+         *
+         * `<script pstm-res-src="statum.js">` (the pre-split client, no longer
+         * an embedded resource) becomes one `<script src="/_statum/resource/
+         * statum-<mod>.js">` tag per module, in canonical load order. Later
+         * duplicate `statum.js` references on the same page are dropped, and
+         * explicit `pstm-res-src="statum-<mod>.js"` references the expansion
+         * already covers are removed (subsumed) so every module appears exactly
+         * once. Pages referencing only module files (or only plain `src="…"`
+         * scripts, which are already final) are left entirely alone.
+         *
+         * Runs on the composed document before {@link rewrite_resources}: the
+         * inserted tags use plain `src` attributes, so the generic rewrite and
+         * `strip_pstm` both leave them alone.
+         */
+        private void expand_legacy_client(Xml.Doc* doc, string page_file) {
+            var root = doc->get_root_element();
+            if (root == null) {
+                return;
+            }
+
+            Xml.Node* legacy = null;
+            var refs = new ClientRefs();
+            collect_client_refs(root, refs);
+            legacy = refs.legacy;
+            if (legacy == null) {
+                return;
+            }
+
+            var parent = legacy->parent;
+            if (parent == null) {
+                return;
+            }
+
+            foreach (var module in CLIENT_MODULE_ORDER) {
+                var tag = ((!)parent)->new_child(null, "script", null);
+                tag->unlink();
+                // Script tags carrying pstm-res-src carry no other attributes
+                // today; copy any extras onto all four for good measure.
+                copy_non_pstm_attrs(legacy, tag);
+                tag->set_prop("src", @"/_statum/resource/$module");
+                legacy->add_prev_sibling(tag);
+            }
+
+            string[] subsumed = new string[0];
+            foreach (var node in refs.modules) {
+                var name = node->get_prop("pstm-res-src");
+                if (name != null) {
+                    subsumed += (!)name;
+                }
+                node->unlink();
+            }
+            legacy->unlink();
+
+            // Exactly one build warning per page, naming the page and the
+            // expansion.
+            var page = page_name(page_file);
+            stderr.printf("warning: %s: expanding legacy pstm-res-src=\"statum.js\" into %s",
+                page, string.joinv(", ", CLIENT_MODULE_ORDER));
+            if (subsumed.length > 0) {
+                stderr.printf("; subsumed explicit module tag(s): %s", string.joinv(", ", subsumed));
+            }
+            if (refs.duplicates > 0) {
+                stderr.printf("; dropped %d duplicate statum.js reference(s)", refs.duplicates);
+            }
+            stderr.printf("\n");
+        }
+
+        /**
+         * Collects the client script references of a composed document: the
+         * first `<script pstm-res-src="statum.js">` (if any), every explicit
+         * module `pstm-res-src="statum-<mod>.js"` reference, and the count of
+         * further `statum.js` duplicates.
+         */
+        private void collect_client_refs(Xml.Node* node, ClientRefs refs) {
+            if (!is_element(node)) {
+                return;
+            }
+            if ((string) node->name == "script") {
+                var res = node->get_prop("pstm-res-src");
+                if (res != null) {
+                    if ((!)res == "statum.js") {
+                        if (refs.legacy == null) {
+                            refs.legacy = node;
+                        } else {
+                            refs.duplicates++;
+                        }
+                    } else if (is_client_module((!)res)) {
+                        refs.modules += node;
+                    }
+                }
+            }
+            for (var child = node->children; child != null; child = child->next) {
+                collect_client_refs(child, refs);
+            }
+        }
+
+        /** The page's short name, as warnings name it (e.g. `home`). */
+        private static string page_name(string page_file) {
+            var page = Path.get_basename(page_file);
+            var dot = page.last_index_of(".");
+            if (dot > 0) {
+                page = page.substring(0, dot);
+            }
+            return page;
+        }
+
+        private static bool is_client_module(string name) {
+            foreach (var module in CLIENT_MODULE_ORDER) {
+                if (name == module) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
         private void rewrite_resources_in(Xml.Node* node) {
             if (!is_element(node)) {
                 return;
@@ -838,6 +967,13 @@ namespace Statum.Tools {
         // Model
         // ------------------------------------------------------------------
 
+        /** Scratch state for {@link collect_client_refs}. */
+        private class ClientRefs : Object {
+            public Xml.Node* legacy = null;
+            public Xml.Node*[] modules = new Xml.Node*[0];
+            public int duplicates = 0;
+        }
+
         private class VariantData : Object {
             public uint8[] identity;
             public uint8[]? gzip;