@weborigami/origami 0.6.16 → 0.6.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@weborigami/origami",
3
- "version": "0.6.16",
3
+ "version": "0.6.17",
4
4
  "description": "Web Origami language, CLI, framework, and server",
5
5
  "type": "module",
6
6
  "repository": {
@@ -18,9 +18,9 @@
18
18
  },
19
19
  "dependencies": {
20
20
  "@hpcc-js/wasm-graphviz": "^1.21.0",
21
- "@weborigami/async-tree": "0.6.16",
21
+ "@weborigami/async-tree": "0.6.17",
22
22
  "@weborigami/json-feed-to-rss": "1.0.1",
23
- "@weborigami/language": "0.6.16",
23
+ "@weborigami/language": "0.6.17",
24
24
  "css-tree": "3.1.0",
25
25
  "highlight.js": "11.11.1",
26
26
  "jsdom": "28.1.0",
@@ -0,0 +1,26 @@
1
+ import { toString } from "@weborigami/async-tree";
2
+ import { createHash } from "node:crypto";
3
+
4
+ /**
5
+ * Given data, return a 256-bit hash of that data. The data can be a string or a
6
+ * Uint8Array.
7
+ *
8
+ * @typedef {import("@weborigami/async-tree").Stringlike} Stringlike
9
+ *
10
+ * @param {Uint8Array|Stringlike} data
11
+ */
12
+ export default function hashBytes(data) {
13
+ let bytes;
14
+ if (data instanceof Uint8Array) {
15
+ bytes = data;
16
+ } else {
17
+ const text = toString(data);
18
+ if (!text) {
19
+ throw new TypeError("Data must be a string or Uint8Array");
20
+ }
21
+ bytes = new TextEncoder().encode(text);
22
+ }
23
+
24
+ const hash = createHash("sha256").update(bytes).digest();
25
+ return hash;
26
+ }
@@ -0,0 +1,38 @@
1
+ import { args, isStringlike, toString, Tree } from "@weborigami/async-tree";
2
+
3
+ /**
4
+ * Given an old tree and a new tree, return a tree of changes indicated
5
+ * by the values: "added", "changed", or "deleted".
6
+ *
7
+ * @typedef {import("@weborigami/async-tree").Maplike} Maplike
8
+ *
9
+ * @param {Maplike} oldMaplike
10
+ * @param {Maplike} newMaplike
11
+ */
12
+ export default async function changes(oldMaplike, newMaplike) {
13
+ const oldTree = await args.map(oldMaplike, "Dev.changes", {
14
+ deep: true,
15
+ position: 1,
16
+ });
17
+ const newTree = await args.map(newMaplike, "Dev.changes", {
18
+ deep: true,
19
+ position: 2,
20
+ });
21
+
22
+ const combination = await Tree.combine(oldTree, newTree, compare);
23
+ return combination;
24
+ }
25
+
26
+ function compare(oldValue, newValue) {
27
+ if (oldValue !== undefined && newValue === undefined) {
28
+ return "deleted";
29
+ } else if (oldValue === undefined && newValue !== undefined) {
30
+ return "added";
31
+ } else if (isStringlike(oldValue) && isStringlike(newValue)) {
32
+ const oldText = toString(oldValue);
33
+ const newText = toString(newValue);
34
+ return oldText === newText ? undefined : "changed";
35
+ } else {
36
+ return oldValue === newValue ? undefined : "changed";
37
+ }
38
+ }
package/src/dev/dev.js CHANGED
@@ -6,6 +6,7 @@ export { default as indexPage } from "../origami/indexPage.js";
6
6
  export { default as yaml } from "../origami/yaml.js";
7
7
  export { default as breakpoint } from "./breakpoint.js";
8
8
  export { default as changes } from "./changes.js";
9
+ export { default as changes2 } from "./changes2.js";
9
10
  export { default as code } from "./code.js";
10
11
  export { default as copy } from "./copy.js";
11
12
  export { default as audit } from "./crawler/audit.js";
package/src/dev/help.yaml CHANGED
@@ -68,9 +68,15 @@ Origami:
68
68
  fetch:
69
69
  args: (url, options)
70
70
  description: Fetch a resource from a URL with support for extensions
71
+ hash:
72
+ args: (data)
73
+ description: A hex string hash of the data
71
74
  htmlEscape:
72
75
  args: (text)
73
76
  description: Escape HTML entities in the text
77
+ htmlParse:
78
+ args: (html)
79
+ description: Parse HTML into a plain JavaScript object
74
80
  image:
75
81
  description: Collection of functions for working with images
76
82
  indexPage:
@@ -79,9 +85,6 @@ Origami:
79
85
  inline:
80
86
  args: (text)
81
87
  description: Inline Origami expressions found in the text
82
- htmlParse:
83
- args: (html)
84
- description: Parse HTML into a plain JavaScript object
85
88
  json:
86
89
  args: (obj)
87
90
  description: Render the object in JSON format
@@ -110,6 +113,12 @@ Origami:
110
113
  description: POST the given data to the URL
111
114
  projectRoot:
112
115
  description: The root folder for the current Origami project
116
+ randomFrom:
117
+ args: (data)
118
+ description: Returns a random value based on the data
119
+ randomsFrom:
120
+ args: (data)
121
+ description: Returns a function to produce random values based on the data
113
122
  redirect:
114
123
  args: (url, options)
115
124
  description: Redirect to the given URL
@@ -193,6 +202,9 @@ Tree:
193
202
  clear:
194
203
  args: (map)
195
204
  description: Remove all values from the map
205
+ combine:
206
+ args: (tree1, tree2, combineFn)
207
+ description: Pairwise application of combineFn to the trees' values
196
208
  concat:
197
209
  args: (...trees)
198
210
  description: Merge trees, renumbering numeric keys
@@ -0,0 +1,17 @@
1
+ import hashBytes from "../common/hashBytes.js";
2
+
3
+ /**
4
+ * Given string or Uint8Array data, return a hex-encoded hash of that data.
5
+ *
6
+ * @typedef {import("@weborigami/async-tree").Stringlike} Stringlike
7
+ *
8
+ * @param {Uint8Array|Stringlike} data
9
+ */
10
+ export default function hash(data) {
11
+ const bytes = hashBytes(data);
12
+ const text = Array.from(bytes)
13
+ .slice(0, 20) // Limit to first 20 bytes (160 bits) for a shorter hash string
14
+ .map((byte) => byte.toString(16).padStart(2, "0"))
15
+ .join("");
16
+ return text;
17
+ }
@@ -55,4 +55,5 @@ export default async function mdHtml(input) {
55
55
 
56
56
  mdHtml.key = (sourceValue, sourceKey) =>
57
57
  extension.replace(sourceKey, ".md", ".html");
58
+ mdHtml.key.needsSourceValue = false;
58
59
  mdHtml.inverseKey = (resultKey) => extension.replace(resultKey, ".html", ".md");
@@ -4,6 +4,7 @@ export { default as basename } from "./basename.js";
4
4
  export { default as csv } from "./csv.js";
5
5
  export { default as document } from "./document.js";
6
6
  export { default as fetch } from "./fetch.js";
7
+ export { default as hash } from "./hash.js";
7
8
  export { default as htmlEscape } from "./htmlEscape.js";
8
9
  export { default as htmlParse } from "./htmlParse.js";
9
10
  export { default as format } from "./image/format.js";
@@ -23,6 +24,8 @@ export { default as pack } from "./pack.js";
23
24
  export { default as post } from "./post.js";
24
25
  export { default as project } from "./project.js";
25
26
  export { default as projectRoot } from "./projectRoot.js";
27
+ export { default as randomFrom } from "./randomFrom.js";
28
+ export { default as randomsFrom } from "./randomsFrom.js";
26
29
  export { default as redirect } from "./redirect.js";
27
30
  export { default as repeat } from "./repeat.js";
28
31
  export { default as rss } from "./rss.js";
@@ -0,0 +1,15 @@
1
+ import hashBytes from "../common/hashBytes.js";
2
+
3
+ /**
4
+ * Given a block of seed data, derive a pseudo-random 32-bit integer.
5
+ *
6
+ * @typedef {import("@weborigami/async-tree").Stringlike} Stringlike
7
+ *
8
+ * @param {Uint8Array|Stringlike} data
9
+ * @return {number}
10
+ */
11
+ export default function randomFrom(data) {
12
+ // Extract the first 32-bit integer from the hash to use as the random number
13
+ const hash = hashBytes(data);
14
+ return hash.readUInt32LE(0);
15
+ }
@@ -0,0 +1,65 @@
1
+ import hashBytes from "../common/hashBytes.js";
2
+
3
+ /**
4
+ * Given a block of seed data, return a function that produces a sequence of
5
+ * pseudo-random 32-bit integers.
6
+ *
7
+ * @typedef {import("@weborigami/async-tree").Stringlike} Stringlike
8
+ *
9
+ * @param {Uint8Array|Stringlike} data
10
+ * @return {function(): number}
11
+ */
12
+ export default function randomsFrom(data) {
13
+ const hash = hashBytes(data);
14
+
15
+ // Extract four 32-bit integers from the hash to use as the initial state of
16
+ // the pseudo-random number generator
17
+ const a = hash.readUInt32LE(0);
18
+ const b = hash.readUInt32LE(4);
19
+ const c = hash.readUInt32LE(8);
20
+ const d = hash.readUInt32LE(12);
21
+
22
+ const prng = xoshiro128ss(a, b, c, d);
23
+ return prng;
24
+ }
25
+
26
+ // Rotate left (circular left shift) for 32-bit integers
27
+ function rotl(x, k) {
28
+ return ((x << k) | (x >>> (32 - k))) >>> 0;
29
+ }
30
+
31
+ /**
32
+ * Pseudo-random number generator based on the xoshiro128** algorithm. See
33
+ * the 256-bit variant: https://en.wikipedia.org/wiki/Xorshift#xoshiro256**
34
+ *
35
+ * Unlike a typical implementation, this directly returns a 32-bit integer
36
+ * instead of a float.
37
+ *
38
+ * @param {number} a
39
+ * @param {number} b
40
+ * @param {number} c
41
+ * @param {number} d
42
+ * @returns {function(): number}
43
+ */
44
+ function xoshiro128ss(a, b, c, d) {
45
+ let s0 = a >>> 0;
46
+ let s1 = b >>> 0;
47
+ let s2 = c >>> 0;
48
+ let s3 = d >>> 0;
49
+
50
+ return function () {
51
+ const result = (rotl(Math.imul(s1, 5), 7) * 9) >>> 0;
52
+
53
+ const t = (s1 << 9) >>> 0;
54
+
55
+ s2 ^= s0;
56
+ s3 ^= s1;
57
+ s1 ^= s2;
58
+ s0 ^= s3;
59
+
60
+ s2 ^= t;
61
+ s3 = rotl(s3, 11);
62
+
63
+ return result;
64
+ };
65
+ }