| 1 | // strings_node drives the string lookup one script carries, |
| 2 | // fiestaPickLang and fiestaWord, in plain Node against a stub page. |
| 3 | // Both datetime.js and select.js carry a copy (each enhancement is one |
| 4 | // script tag), so the script's file name is the one argument: |
| 5 | // |
| 6 | // echo '{"registry":["en","pt-PT"],"tags":["pt-BR",""]}' | |
| 7 | // node node/strings_node.mjs datetime.js |
| 8 | // |
| 9 | // check/strings_test.go is the only caller. It reads back one JSON |
| 10 | // object: "pick", fiestaPickLang's answer for each tag on stdin plus |
| 11 | // one for undefined under "<undefined>", against a registry holding |
| 12 | // an (empty) table per tag named; and "word", fiestaWord's answer in |
| 13 | // five scenarios on a stub element, four in a lang="es" document whose |
| 14 | // table says "Tabla": "both" (attribute "Atributo" and the table), |
| 15 | // "empty" (an empty attribute and the table), "table" (no attribute) |
| 16 | // and "neither" (no attribute, no table, so the fallback "English"); |
| 17 | // and "notable", a lang="de" document with only that es table and no |
| 18 | // attribute, the fallback again. The word scenarios build their own |
| 19 | // tables, so stdin's registry is for "pick" alone. |
| 20 | // |
| 21 | // The page is the least DOM either script touches on load: a document |
| 22 | // that is still loading, with a root carrying lang, and a self holding |
| 23 | // the registry. The script's page half registers its listeners and |
| 24 | // waits for DOMContentLoaded, which never comes; the lookup is read |
| 25 | // off module.exports, which both scripts fill behind their guard. |
| 26 | import { readFileSync } from "node:fs"; |
| 27 | import vm from "node:vm"; |
| 28 | |
| 29 | const name = process.argv[2]; |
| 30 | if (!name) { |
| 31 | process.stderr.write("usage: strings_node.mjs <datetime.js | select.js>\n"); |
| 32 | process.exit(2); |
| 33 | } |
| 34 | const src = readFileSync(new URL("../../js/" + name, import.meta.url), "utf8"); |
| 35 | |
| 36 | const chunks = []; |
| 37 | for await (const chunk of process.stdin) chunks.push(chunk); |
| 38 | // Go encodes a nil slice as null, which a default would not fill. |
| 39 | const stdin = JSON.parse(Buffer.concat(chunks).toString("utf8") || "{}"); |
| 40 | const registry = stdin.registry || []; |
| 41 | const tags = stdin.tags || []; |
| 42 | |
| 43 | // load evaluates the script in a fresh context whose document says |
| 44 | // lang and whose self.fiestaStrings is strings, and returns what it |
| 45 | // exported. |
| 46 | function load(lang, strings) { |
| 47 | const root = { lang, getAttribute: (k) => (k === "lang" ? lang : null) }; |
| 48 | const document = { |
| 49 | readyState: "loading", |
| 50 | documentElement: root, |
| 51 | addEventListener() {}, |
| 52 | querySelectorAll: () => [], |
| 53 | }; |
| 54 | const self = { fiestaStrings: strings }; |
| 55 | const module = { exports: {} }; |
| 56 | const context = { document, self, window: self, module, exports: module.exports, setTimeout, clearTimeout }; |
| 57 | vm.runInNewContext(src, context, { filename: name }); |
| 58 | return module.exports; |
| 59 | } |
| 60 | |
| 61 | // An element with attributes and nothing else. |
| 62 | function element(attrs) { |
| 63 | return { getAttribute: (k) => (Object.prototype.hasOwnProperty.call(attrs, k) ? attrs[k] : null) }; |
| 64 | } |
| 65 | |
| 66 | const out = { pick: {}, word: {} }; |
| 67 | |
| 68 | { |
| 69 | const tables = {}; |
| 70 | for (const tag of registry) tables[tag] = {}; |
| 71 | const { fiestaPickLang } = load("en", tables); |
| 72 | if (typeof fiestaPickLang !== "function") { |
| 73 | process.stderr.write(name + " does not export fiestaPickLang\n"); |
| 74 | process.exit(1); |
| 75 | } |
| 76 | for (const tag of tags) out.pick[tag] = fiestaPickLang(tag, tables); |
| 77 | out.pick["<undefined>"] = fiestaPickLang(undefined, tables); |
| 78 | } |
| 79 | |
| 80 | { |
| 81 | const table = { es: { "date-set": "Tabla" } }; |
| 82 | const withTable = load("es", table).fiestaWord; |
| 83 | const without = load("es", {}).fiestaWord; |
| 84 | if (typeof withTable !== "function") { |
| 85 | process.stderr.write(name + " does not export fiestaWord\n"); |
| 86 | process.exit(1); |
| 87 | } |
| 88 | out.word.both = withTable(element({ "data-rst-date-set": "Atributo" }), "date-set", "English"); |
| 89 | out.word.empty = withTable(element({ "data-rst-date-set": "" }), "date-set", "English"); |
| 90 | out.word.table = withTable(element({}), "date-set", "English"); |
| 91 | out.word.neither = without(element({}), "date-set", "English"); |
| 92 | out.word.notable = load("de", table).fiestaWord(element({}), "date-set", "English"); |
| 93 | } |
| 94 | |
| 95 | process.stdout.write(JSON.stringify(out)); |
| 96 | |