rastrillo / fiesta Public

Clone
git clone https://amadan.net/rastrillo/fiesta

Plain git — no account needed to clone.

Download

Download this file

1// vocab_node drives datetime.js's vocabulary resolution in plain Node
2// against a stub page: a document whose lang is en, no
3// self.fiestaStrings, and a field with no attributes, so the only
4// vocabulary left is the script's own English. It reads back one JSON
5// object: "words", the vocabulary the field resolved to; "parsed",
6// each phrase on stdin to the ISO instant the parser read it as (null
7// for no reading), at a fixed now of 2026-10-28 10:30 local; and
8// "malformed", the vocabulary a field with an attribute that is not
9// JSON resolves to on a Spanish page with the Spanish table loaded.
10//
11// echo '{"phrases":["tomorrow","next fri 9am","in 2 weeks"]}' |
12// node node/vocab_node.mjs
13//
14// check/datetime_test.go is the only caller. It holds the words to
15// i18n/en.json's date-words and wants every phrase read, which is what
16// the README promises of an English page that loads no table.
17import { readFileSync } from "node:fs";
18import vm from "node:vm";
19
20const src = readFileSync(new URL("../../js/datetime.js", import.meta.url), "utf8");
21
22const chunks = [];
23for await (const chunk of process.stdin) chunks.push(chunk);
24const { phrases = [] } = JSON.parse(Buffer.concat(chunks).toString("utf8") || "{}");
25
26// load evaluates the script in a fresh context: the least DOM it
27// touches on load, a loading document whose root carries lang, and a
28// self holding strings as its registry. It returns what the script
29// exported.
30function load(lang, strings) {
31 const root = { lang, getAttribute: (k) => (k === "lang" ? lang : null) };
32 const document = {
33 readyState: "loading",
34 documentElement: root,
35 addEventListener() {},
36 querySelectorAll: () => [],
37 };
38 const self = { fiestaStrings: strings };
39 const module = { exports: {} };
40 vm.runInNewContext(src, { document, self, window: self, module, exports: module.exports, setTimeout, clearTimeout }, { filename: "datetime.js" });
41 return module.exports;
42}
43
44// An element with attributes and nothing else.
45const element = (attrs) => ({ getAttribute: (k) => (Object.prototype.hasOwnProperty.call(attrs, k) ? attrs[k] : null) });
46
47const { vocabulary, parse, tables } = load("en", {});
48if (typeof vocabulary !== "function") {
49 process.stderr.write("datetime.js does not export vocabulary\n");
50 process.exit(1);
51}
52// A field with no attributes at all, on an English page with no table.
53const words = vocabulary(element({}));
54
55// A Spanish page whose table is the shipped i18n/es.json, and a field
56// whose attribute is not JSON: the attribute is as good as absent, so
57// the vocabulary must be the table's, not English.
58const es = JSON.parse(readFileSync(new URL("../../i18n/es.json", import.meta.url), "utf8"));
59const malformed = load("es", { es }).vocabulary(element({ "data-rst-date-words": "{not json" }));
60const now = new Date(2026, 9, 28, 10, 30);
61const tbl = tables("en");
62const parsed = {};
63for (const phrase of phrases) {
64 let read = null;
65 try {
66 read = parse(phrase, words, tbl, now, {});
67 } catch (e) {
68 read = null;
69 }
70 parsed[phrase] = read && read.date ? read.date.toISOString() : null;
71}
72process.stdout.write(JSON.stringify({ words, parsed, malformed }));
73