run npm install to generate a package lock
This commit is contained in:
21
node_modules/@weborigami/origami/LICENSE
generated
vendored
Normal file
21
node_modules/@weborigami/origami/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 Jan Miksovsky and other contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
3
node_modules/@weborigami/origami/ReadMe.md
generated
vendored
Normal file
3
node_modules/@weborigami/origami/ReadMe.md
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
# Origami package
|
||||
|
||||
This folder contains the files published as the high-level `@weborigami/origami` package. It contains common types of useful trees, the `ori` CLI, a templating system for creating text output, and more.
|
||||
22
node_modules/@weborigami/origami/index.ts
generated
vendored
Normal file
22
node_modules/@weborigami/origami/index.ts
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* Tree Origami is a JavaScript project, but we use TypeScript as an internal
|
||||
* tool to confirm our code is type safe.
|
||||
*/
|
||||
|
||||
import { Treelike, Unpackable } from "@weborigami/async-tree";
|
||||
|
||||
/**
|
||||
* A class constructor is an object with a `new` method that returns an
|
||||
* instance of the indicated type.
|
||||
*/
|
||||
export type Constructor<T> = new (...args: any[]) => T;
|
||||
|
||||
export type Invocable = Function | Unpackable<Function|Treelike> | Treelike;
|
||||
|
||||
export interface JsonObject {
|
||||
[key: string]: JsonValue;
|
||||
}
|
||||
|
||||
export type JsonValue = boolean | number | string | Date | JsonObject | JsonValue[] | null;
|
||||
|
||||
export type TypedArray = Int8Array | Uint8Array | Uint8ClampedArray | Int16Array | Uint16Array | Int32Array | Uint32Array | Float32Array | Float64Array | BigInt64Array | BigUint64Array;
|
||||
12
node_modules/@weborigami/origami/main.js
generated
vendored
Normal file
12
node_modules/@weborigami/origami/main.js
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
export * from "./src/calc/calc.js";
|
||||
export { default as documentObject } from "./src/common/documentObject.js";
|
||||
export { toString } from "./src/common/utilities.js";
|
||||
export * from "./src/dev/dev.js";
|
||||
export * from "./src/handlers/handlerExports.js";
|
||||
export * from "./src/handlers/handlers.js";
|
||||
export * from "./src/image/image.js";
|
||||
export { builtinsTree } from "./src/internal.js";
|
||||
export * from "./src/origami/origami.js";
|
||||
export * from "./src/site/site.js";
|
||||
export * from "./src/text/text.js";
|
||||
export * from "./src/tree/tree.js";
|
||||
37
node_modules/@weborigami/origami/package.json
generated
vendored
Normal file
37
node_modules/@weborigami/origami/package.json
generated
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"name": "@weborigami/origami",
|
||||
"version": "0.2.1",
|
||||
"description": "Web Origami language, CLI, framework, and server",
|
||||
"type": "module",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/WebOrigami/origami.git"
|
||||
},
|
||||
"bin": {
|
||||
"ori": "src/cli/cli.js"
|
||||
},
|
||||
"main": "./main.js",
|
||||
"types": "./index.ts",
|
||||
"devDependencies": {
|
||||
"@types/node": "22.7.4",
|
||||
"typescript": "5.6.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@weborigami/async-tree": "0.2.1",
|
||||
"@weborigami/language": "0.2.1",
|
||||
"@weborigami/types": "0.2.1",
|
||||
"exif-parser": "0.1.12",
|
||||
"graphviz-wasm": "3.0.2",
|
||||
"highlight.js": "11.10.0",
|
||||
"marked": "14.1.2",
|
||||
"marked-gfm-heading-id": "4.1.0",
|
||||
"marked-highlight": "2.1.4",
|
||||
"marked-smartypants": "1.1.8",
|
||||
"sharp": "0.33.5",
|
||||
"yaml": "2.5.1"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "node --test --test-reporter=spec",
|
||||
"typecheck": "node node_modules/typescript/bin/tsc"
|
||||
}
|
||||
}
|
||||
62
node_modules/@weborigami/origami/src/builtins.js
generated
vendored
Normal file
62
node_modules/@weborigami/origami/src/builtins.js
generated
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
import * as calc from "./calc/calc.js";
|
||||
import deprecated from "./deprecated.js";
|
||||
import * as dev from "./dev/dev.js";
|
||||
import * as handlers from "./handlers/handlers.js";
|
||||
import help from "./help/help.js";
|
||||
import * as image from "./image/image.js";
|
||||
import js from "./js.js";
|
||||
import node from "./node.js";
|
||||
import * as origami from "./origami/origami.js";
|
||||
import explore from "./protocols/explore.js";
|
||||
import files from "./protocols/files.js";
|
||||
import http from "./protocols/http.js";
|
||||
import https from "./protocols/https.js";
|
||||
import httpstree from "./protocols/httpstree.js";
|
||||
import httptree from "./protocols/httptree.js";
|
||||
import inherited from "./protocols/inherited.js";
|
||||
import instantiate from "./protocols/new.js";
|
||||
import packageNamespace from "./protocols/package.js";
|
||||
import scope from "./protocols/scope.js";
|
||||
import * as site from "./site/site.js";
|
||||
import * as text from "./text/text.js";
|
||||
import * as tree from "./tree/tree.js";
|
||||
|
||||
/** @type {any} */
|
||||
export default {
|
||||
"calc:": adjustReservedWords(calc),
|
||||
"dev:": dev,
|
||||
"explore:": explore,
|
||||
"files:": files,
|
||||
"help:": help,
|
||||
"http:": http,
|
||||
"https:": https,
|
||||
"httpstree:": httpstree,
|
||||
"httptree:": httptree,
|
||||
"image:": image,
|
||||
"inherited:": inherited,
|
||||
"js:": js,
|
||||
"new:": instantiate,
|
||||
"node:": node,
|
||||
"origami:": origami,
|
||||
"package:": packageNamespace,
|
||||
"scope:": scope,
|
||||
"site:": adjustReservedWords(site),
|
||||
"text:": text,
|
||||
"tree:": tree,
|
||||
|
||||
// Some builtins need to be exposed at top level
|
||||
...handlers.default,
|
||||
|
||||
// Deprecated builtins
|
||||
...deprecated,
|
||||
};
|
||||
|
||||
// Handle cases where a builtin name conflicts with a JS reserved word
|
||||
function adjustReservedWords(obj) {
|
||||
const result = {};
|
||||
for (const [key, value] of Object.entries(obj)) {
|
||||
const name = value.key ?? key;
|
||||
result[name] = value;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
36
node_modules/@weborigami/origami/src/builtinsTree.js
generated
vendored
Normal file
36
node_modules/@weborigami/origami/src/builtinsTree.js
generated
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
import { trailingSlash } from "@weborigami/async-tree";
|
||||
import builtins from "./builtins.js";
|
||||
|
||||
const expanded = { ...builtins };
|
||||
|
||||
// For all builtins like `tree:keys`, add a shorthand `keys`.
|
||||
for (const [key, value] of Object.entries(expanded)) {
|
||||
const isNamespace = key.endsWith(":");
|
||||
if (isNamespace) {
|
||||
for (const [subKey, subValue] of Object.entries(value)) {
|
||||
// HACK: Skip description keys until we can make them all non-enumerable.
|
||||
if (subKey === "description") {
|
||||
continue;
|
||||
}
|
||||
if (subKey in expanded) {
|
||||
throw new Error(`Internal Origami error: Duplicate key: ${subKey}`);
|
||||
}
|
||||
expanded[subKey] = subValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We create our own tree instead of using ObjectTree, since that binds the
|
||||
// functions would be bound to the object. We want to leave them unbound.
|
||||
class BuiltinsTree {
|
||||
async get(key) {
|
||||
const normalizedKey = trailingSlash.remove(key);
|
||||
return expanded[normalizedKey];
|
||||
}
|
||||
|
||||
async keys() {
|
||||
return Object.keys(expanded);
|
||||
}
|
||||
}
|
||||
|
||||
export default new BuiltinsTree();
|
||||
81
node_modules/@weborigami/origami/src/calc/calc.js
generated
vendored
Normal file
81
node_modules/@weborigami/origami/src/calc/calc.js
generated
vendored
Normal file
@@ -0,0 +1,81 @@
|
||||
import { Tree } from "@weborigami/async-tree";
|
||||
import assertTreeIsDefined from "../common/assertTreeIsDefined.js";
|
||||
|
||||
export function add(...args) {
|
||||
console.warn(`Warning: "add" is deprecated. Use the "+" operator instead.`);
|
||||
const numbers = args.map((arg) => Number(arg));
|
||||
return numbers.reduce((acc, val) => acc + val, 0);
|
||||
}
|
||||
|
||||
export function and(...args) {
|
||||
console.warn(`Warning: "and" is deprecated. Use the "&&" operator instead.`);
|
||||
return args.every((arg) => arg);
|
||||
}
|
||||
|
||||
export function divide(a, b) {
|
||||
console.warn(
|
||||
`Warning: "divide" is deprecated. Use the "/" operator instead.`
|
||||
);
|
||||
return Number(a) / Number(b);
|
||||
}
|
||||
|
||||
export function equals(a, b) {
|
||||
console.warn(
|
||||
`Warning: "equals" is deprecated. Use the "===" operator instead.`
|
||||
);
|
||||
return a === b;
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
||||
*
|
||||
* @this {AsyncTree|null}
|
||||
* @param {any} value
|
||||
* @param {any} trueResult
|
||||
* @param {any} [falseResult]
|
||||
*/
|
||||
export async function ifBuiltin(value, trueResult, falseResult) {
|
||||
console.warn(
|
||||
`Warning: "if" is deprecated. Use the conditional "a ? b : c" operator instead.`
|
||||
);
|
||||
|
||||
assertTreeIsDefined(this, "calc:if");
|
||||
let condition = await value;
|
||||
if (Tree.isAsyncTree(condition)) {
|
||||
const keys = Array.from(await condition.keys());
|
||||
condition = keys.length > 0;
|
||||
}
|
||||
|
||||
// 0 is true, null/undefined/false is false
|
||||
let result = condition || condition === 0 ? trueResult : falseResult;
|
||||
if (typeof result === "function") {
|
||||
result = await result.call(this);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
ifBuiltin.key = "if";
|
||||
|
||||
export function multiply(...args) {
|
||||
console.warn(
|
||||
`Warning: "multiply" is deprecated. Use the "*" operator instead.`
|
||||
);
|
||||
const numbers = args.map((arg) => Number(arg));
|
||||
return numbers.reduce((acc, val) => acc * val, 1);
|
||||
}
|
||||
|
||||
export function not(value) {
|
||||
console.warn(`Warning: "not" is deprecated. Use the "!" operator instead.`);
|
||||
return !value;
|
||||
}
|
||||
|
||||
export function or(...args) {
|
||||
console.warn(`Warning: "or" is deprecated. Use the "||" operator instead.`);
|
||||
return args.find((arg) => arg);
|
||||
}
|
||||
|
||||
export function subtract(a, b) {
|
||||
console.warn(
|
||||
`Warning: "subtract" is deprecated. Use the "-" operator instead.`
|
||||
);
|
||||
return Number(a) - Number(b);
|
||||
}
|
||||
69
node_modules/@weborigami/origami/src/cli/cli.js
generated
vendored
Executable file
69
node_modules/@weborigami/origami/src/cli/cli.js
generated
vendored
Executable file
@@ -0,0 +1,69 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { Tree } from "@weborigami/async-tree";
|
||||
import { formatError } from "@weborigami/language";
|
||||
import path from "node:path";
|
||||
import process, { stdout } from "node:process";
|
||||
import help from "../help/help.js";
|
||||
import ori from "../origami/ori.js";
|
||||
import project from "../origami/project.js";
|
||||
|
||||
const TypedArray = Object.getPrototypeOf(Uint8Array);
|
||||
|
||||
async function main(...args) {
|
||||
const expression = args.join(" ");
|
||||
|
||||
// Find the project root.
|
||||
const projectTree = await project.call(null);
|
||||
|
||||
// If no arguments were passed, show usage.
|
||||
if (!expression) {
|
||||
// HACK: the config is the parent of the project tree.
|
||||
const config = projectTree.parent;
|
||||
const usage = await help.call(config);
|
||||
console.log(usage);
|
||||
return;
|
||||
}
|
||||
|
||||
// Traverse from the project root to the current directory.
|
||||
const currentDirectory = process.cwd();
|
||||
const relative = path.relative(projectTree.path, currentDirectory);
|
||||
const tree = await Tree.traversePath(projectTree, relative);
|
||||
|
||||
const result = await ori.call(tree, expression);
|
||||
|
||||
if (result !== undefined) {
|
||||
const output =
|
||||
result instanceof ArrayBuffer
|
||||
? new Uint8Array(result)
|
||||
: typeof result === "string" || result instanceof TypedArray
|
||||
? result
|
||||
: String(result);
|
||||
await stdout.write(output);
|
||||
|
||||
// If stdout points to the console, and the result didn't end in a newline,
|
||||
// then output a newline.
|
||||
if (stdout.isTTY) {
|
||||
const lastChar = output[output.length - 1];
|
||||
const isNewLine = lastChar === "\n" || lastChar === 10;
|
||||
if (!isNewLine) {
|
||||
await stdout.write("\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Process command line arguments
|
||||
const args = process.argv;
|
||||
args.shift(); // "node"
|
||||
args.shift(); // name of this script file
|
||||
// Not sure why we end up with blank arguments; skip them.
|
||||
while (args[0] === "") {
|
||||
args.shift();
|
||||
}
|
||||
try {
|
||||
await main(...args);
|
||||
} catch (/** @type {any} */ error) {
|
||||
console.error(formatError(error));
|
||||
process.exitCode = 1;
|
||||
}
|
||||
16
node_modules/@weborigami/origami/src/cli/defaultModuleExport.js
generated
vendored
Normal file
16
node_modules/@weborigami/origami/src/cli/defaultModuleExport.js
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
import path from "node:path";
|
||||
import process from "node:process";
|
||||
import { pathToFileURL } from "node:url";
|
||||
|
||||
export default async function defaultModuleExport(...keys) {
|
||||
// On Windows, absolute paths must be valid file:// URLs.
|
||||
const modulePath = path.resolve(process.cwd(), ...keys);
|
||||
const url = pathToFileURL(modulePath);
|
||||
const module = await import(url.href);
|
||||
const result = module.default;
|
||||
if (!result) {
|
||||
console.error(`${modulePath} does not define a default export.`);
|
||||
return;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
18
node_modules/@weborigami/origami/src/common/ConstantTree.js
generated
vendored
Normal file
18
node_modules/@weborigami/origami/src/common/ConstantTree.js
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
||||
* @implements {AsyncTree}
|
||||
*/
|
||||
export default class ConstantTree {
|
||||
constructor(value) {
|
||||
this.value = value;
|
||||
this.parent = null;
|
||||
}
|
||||
|
||||
async get(key) {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
async keys() {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
1
node_modules/@weborigami/origami/src/common/assertTreeIsDefined.d.ts
generated
vendored
Normal file
1
node_modules/@weborigami/origami/src/common/assertTreeIsDefined.d.ts
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export default function assertTreeIsDefined(tree: any, methodName: string): asserts tree is object
|
||||
10
node_modules/@weborigami/origami/src/common/assertTreeIsDefined.js
generated
vendored
Normal file
10
node_modules/@weborigami/origami/src/common/assertTreeIsDefined.js
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
import { Tree } from "@weborigami/async-tree";
|
||||
|
||||
export default function assertTreeIsDefined(tree, methodName) {
|
||||
const isValid = tree === null || Tree.isAsyncTree(tree);
|
||||
if (!isValid) {
|
||||
throw new Error(
|
||||
`${methodName} must be called with a tree target. If you don't want to pass a tree, invoke with: ${methodName}.call(null)`
|
||||
);
|
||||
}
|
||||
}
|
||||
20
node_modules/@weborigami/origami/src/common/constructHref.js
generated
vendored
Normal file
20
node_modules/@weborigami/origami/src/common/constructHref.js
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
import { pathFromKeys } from "@weborigami/async-tree";
|
||||
|
||||
/**
|
||||
* Given a protocol, a host, and a list of keys, construct an href.
|
||||
*
|
||||
* @param {string} protocol
|
||||
* @param {string} host
|
||||
* @param {string[]} keys
|
||||
*/
|
||||
export default function constructHref(protocol, host, ...keys) {
|
||||
const path = pathFromKeys(keys);
|
||||
let href = [host, path].join("/");
|
||||
if (!href.startsWith(protocol)) {
|
||||
if (!href.startsWith("//")) {
|
||||
href = `//${href}`;
|
||||
}
|
||||
href = `${protocol}${href}`;
|
||||
}
|
||||
return href;
|
||||
}
|
||||
34
node_modules/@weborigami/origami/src/common/constructSiteTree.js
generated
vendored
Normal file
34
node_modules/@weborigami/origami/src/common/constructSiteTree.js
generated
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
import { trailingSlash } from "@weborigami/async-tree";
|
||||
import { HandleExtensionsTransform } from "@weborigami/language";
|
||||
import constructHref from "./constructHref.js";
|
||||
|
||||
/**
|
||||
* Given a protocol, a host, and a list of keys, construct an href.
|
||||
*
|
||||
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
||||
*
|
||||
* @param {string} protocol
|
||||
* @param {import("../../index.ts").Constructor<AsyncTree>} treeClass
|
||||
* @param {AsyncTree|null} parent
|
||||
* @param {string} host
|
||||
* @param {string[]} keys
|
||||
*/
|
||||
export default function constructSiteTree(
|
||||
protocol,
|
||||
treeClass,
|
||||
parent,
|
||||
host,
|
||||
...keys
|
||||
) {
|
||||
// If the last key doesn't end in a slash, remove it for now.
|
||||
let lastKey;
|
||||
if (keys.length > 0 && keys.at(-1) && !trailingSlash.has(keys.at(-1))) {
|
||||
lastKey = keys.pop();
|
||||
}
|
||||
|
||||
const href = constructHref(protocol, host, ...keys);
|
||||
let result = new (HandleExtensionsTransform(treeClass))(href);
|
||||
result.parent = parent;
|
||||
|
||||
return lastKey ? result.get(lastKey) : result;
|
||||
}
|
||||
42
node_modules/@weborigami/origami/src/common/documentObject.js
generated
vendored
Normal file
42
node_modules/@weborigami/origami/src/common/documentObject.js
generated
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
import { isPlainObject, isUnpackable, toString } from "@weborigami/async-tree";
|
||||
// import txtHandler from "../builtins/txt.handler.js";
|
||||
|
||||
/**
|
||||
* In Origami, a text document object is any object with a `@text` property and
|
||||
* a pack() method that formats that object as text with YAML front matter. This
|
||||
* function is a helper for constructing such text document objects.
|
||||
*
|
||||
* @typedef {import("@weborigami/async-tree").StringLike} StringLike
|
||||
* @typedef {import("@weborigami/async-tree").PlainObject} PlainObject
|
||||
*
|
||||
* @param {StringLike|PlainObject} input
|
||||
* @param {any} [data]
|
||||
*/
|
||||
export default async function documentObject(input, data) {
|
||||
let text;
|
||||
let inputData;
|
||||
|
||||
if (isUnpackable(input)) {
|
||||
// Unpack the input first, might already be a document object.
|
||||
input = await input.unpack();
|
||||
}
|
||||
|
||||
if (isPlainObject(input)) {
|
||||
text = input["@text"];
|
||||
inputData = input;
|
||||
} else {
|
||||
text = toString(input);
|
||||
inputData = null;
|
||||
}
|
||||
// TODO: Either restore this code, or move responsibility for packing a
|
||||
// document to HandleExtensionsTransform set().
|
||||
// const base = {
|
||||
// pack() {
|
||||
// return txtHandler.pack(this);
|
||||
// },
|
||||
// };
|
||||
// const result = Object.create(base);
|
||||
const result = {};
|
||||
Object.assign(result, inputData, data, { "@text": text });
|
||||
return result;
|
||||
}
|
||||
26
node_modules/@weborigami/origami/src/common/fetchAndHandleExtension.js
generated
vendored
Normal file
26
node_modules/@weborigami/origami/src/common/fetchAndHandleExtension.js
generated
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
import { handleExtension } from "@weborigami/language";
|
||||
|
||||
/**
|
||||
* Fetch the resource at the given href.
|
||||
*
|
||||
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
||||
*
|
||||
* @this {AsyncTree|null}
|
||||
* @param {string} href
|
||||
*/
|
||||
export default async function fetchAndHandleExtension(href) {
|
||||
const response = await fetch(href);
|
||||
if (!response.ok) {
|
||||
return undefined;
|
||||
}
|
||||
let buffer = await response.arrayBuffer();
|
||||
|
||||
// Attach any loader defined for the file type.
|
||||
const url = new URL(href);
|
||||
const filename = url.pathname.split("/").pop();
|
||||
if (this && filename) {
|
||||
buffer = await handleExtension(this, buffer, filename);
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
67
node_modules/@weborigami/origami/src/common/getTreeArgument.js
generated
vendored
Normal file
67
node_modules/@weborigami/origami/src/common/getTreeArgument.js
generated
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
import { Tree, isUnpackable } from "@weborigami/async-tree";
|
||||
import assertTreeIsDefined from "./assertTreeIsDefined.js";
|
||||
|
||||
/**
|
||||
* Many Origami built-in functions accept an optional treelike object as their
|
||||
* first argument. If no tree is supplied, then the current context for the
|
||||
* Origami command is used as the tree.
|
||||
*
|
||||
* So the argument is optional -- but if supplied, it must be defined. The
|
||||
* caller should pass its `arguments` object to this function so that the actual
|
||||
* number of supplied arguments can be checked.
|
||||
*
|
||||
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
||||
* @typedef {import("@weborigami/async-tree").Treelike} Treelike
|
||||
*
|
||||
* @param {AsyncTree|null} parent
|
||||
* @param {IArguments} args
|
||||
* @param {Treelike|undefined} treelike
|
||||
* @param {string} methodName
|
||||
* @param {boolean} [deep]
|
||||
* @returns {Promise<AsyncTree>}
|
||||
*/
|
||||
export default async function getTreeArgument(
|
||||
parent,
|
||||
args,
|
||||
treelike,
|
||||
methodName,
|
||||
deep
|
||||
) {
|
||||
assertTreeIsDefined(parent, methodName);
|
||||
|
||||
if (treelike !== undefined) {
|
||||
if (isUnpackable(treelike)) {
|
||||
treelike = await treelike.unpack();
|
||||
}
|
||||
if (Tree.isTreelike(treelike)) {
|
||||
const options = deep !== undefined ? { deep } : undefined;
|
||||
let tree = Tree.from(treelike, options);
|
||||
// If the tree was created from a treelike object and does not yet have a
|
||||
// parent, make the current tree its parent.
|
||||
if (!tree.parent && parent !== undefined) {
|
||||
if (parent !== null && !Tree.isAsyncTree(parent)) {
|
||||
throw new Error(
|
||||
`The parent argument passed to ${methodName} must be a tree.`
|
||||
);
|
||||
}
|
||||
tree.parent = parent;
|
||||
}
|
||||
return tree;
|
||||
}
|
||||
throw new Error(
|
||||
`The first argument to ${methodName} must be a tree, like an array, object, or files.`
|
||||
);
|
||||
}
|
||||
|
||||
if (args.length === 0) {
|
||||
if (!parent) {
|
||||
// Should never happen because assertTreeIsDefined throws an exception.
|
||||
throw new Error(
|
||||
`${methodName} was called with no tree argument and no parent.`
|
||||
);
|
||||
}
|
||||
return parent;
|
||||
}
|
||||
|
||||
throw new Error(`The first argument to ${methodName} was undefined.`);
|
||||
}
|
||||
38
node_modules/@weborigami/origami/src/common/processUnpackedContent.js
generated
vendored
Normal file
38
node_modules/@weborigami/origami/src/common/processUnpackedContent.js
generated
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
import { symbols, Tree } from "@weborigami/async-tree";
|
||||
import { builtinsTree } from "../internal.js";
|
||||
|
||||
/**
|
||||
* Perform any necessary post-processing on the unpacked content of a file. This
|
||||
* lets treat the contents of various file types consistently.
|
||||
*
|
||||
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
||||
*
|
||||
* @param {any} content
|
||||
* @param {AsyncTree|null} parent
|
||||
* @returns
|
||||
*/
|
||||
export default function processUnpackedContent(content, parent) {
|
||||
if (typeof content === "function") {
|
||||
// Bind the function to the parent as the `this` context.
|
||||
const target = parent ?? builtinsTree;
|
||||
const result = content.bind(target);
|
||||
if (content.code) {
|
||||
result.code = content.code;
|
||||
}
|
||||
return result;
|
||||
} else if (Tree.isAsyncTree(content) && !content.parent) {
|
||||
const result = Object.create(content);
|
||||
result.parent = parent;
|
||||
return result;
|
||||
} else if (Object.isExtensible(content) && !content[symbols.parent]) {
|
||||
Object.defineProperty(content, symbols.parent, {
|
||||
configurable: true,
|
||||
enumerable: false,
|
||||
value: parent,
|
||||
writable: true,
|
||||
});
|
||||
return content;
|
||||
} else {
|
||||
return content;
|
||||
}
|
||||
}
|
||||
7
node_modules/@weborigami/origami/src/common/serialize.d.ts
generated
vendored
Normal file
7
node_modules/@weborigami/origami/src/common/serialize.d.ts
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
import type { AsyncTree } from "@weborigami/types";
|
||||
import type { JsonValue } from "../../index.ts";
|
||||
|
||||
export function evaluateYaml(text: string, parent?: AsyncTree|null): Promise<JsonValue>;
|
||||
export function parseYaml(text: string): JsonValue|AsyncTree;
|
||||
export function toJson(obj: JsonValue | AsyncTree): Promise<string>;
|
||||
export function toYaml(obj: JsonValue | AsyncTree): Promise<string>;
|
||||
58
node_modules/@weborigami/origami/src/common/serialize.js
generated
vendored
Normal file
58
node_modules/@weborigami/origami/src/common/serialize.js
generated
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* @typedef {import("../../index.ts").JsonValue} JsonValue
|
||||
* @typedef {import("@weborigami/async-tree").PlainObject} PlainObject
|
||||
* @typedef {import("@weborigami/async-tree").Treelike} Treelike
|
||||
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
||||
*/
|
||||
|
||||
import { Tree, toPlainValue } from "@weborigami/async-tree";
|
||||
import * as YAMLModule from "yaml";
|
||||
|
||||
// The "yaml" package doesn't seem to provide a default export that the browser can
|
||||
// recognize, so we have to handle two ways to accommodate Node and the browser.
|
||||
// @ts-ignore
|
||||
const YAML = YAMLModule.default ?? YAMLModule.YAML;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} text
|
||||
* @param {AsyncTree|null} [parent]
|
||||
*/
|
||||
export async function evaluateYaml(text, parent) {
|
||||
const data = parseYaml(String(text));
|
||||
if (Tree.isAsyncTree(data)) {
|
||||
data.parent = parent;
|
||||
return Tree.plain(data);
|
||||
} else {
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} text
|
||||
* @returns {JsonValue|AsyncTree}
|
||||
*/
|
||||
export function parseYaml(text) {
|
||||
return YAML.parse(text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes an object as a JSON string.
|
||||
*
|
||||
* @param {any} obj
|
||||
*/
|
||||
export async function toJson(obj) {
|
||||
const serializable = await toPlainValue(obj);
|
||||
return JSON.stringify(serializable, null, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes an object as a JSON string.
|
||||
*
|
||||
* @param {any} obj
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
export async function toYaml(obj) {
|
||||
const serializable = await toPlainValue(obj);
|
||||
return YAML.stringify(serializable);
|
||||
}
|
||||
7
node_modules/@weborigami/origami/src/common/utilities.d.ts
generated
vendored
Normal file
7
node_modules/@weborigami/origami/src/common/utilities.d.ts
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
|
||||
export function getDescriptor(object: any): string;
|
||||
export function hasNonPrintableCharacters(text: string): boolean;
|
||||
export function isTransformApplied(Transform: Function, object: any): boolean;
|
||||
export function toFunction(object: any): Function;
|
||||
export function toString(object: any): string|null;
|
||||
export function transformObject(Transform: Function, object: any): any;
|
||||
144
node_modules/@weborigami/origami/src/common/utilities.js
generated
vendored
Normal file
144
node_modules/@weborigami/origami/src/common/utilities.js
generated
vendored
Normal file
@@ -0,0 +1,144 @@
|
||||
import {
|
||||
Tree,
|
||||
toString as asyncTreeToString,
|
||||
isPlainObject,
|
||||
isUnpackable,
|
||||
trailingSlash,
|
||||
} from "@weborigami/async-tree";
|
||||
import { symbols } from "@weborigami/language";
|
||||
import { basename } from "node:path";
|
||||
|
||||
// For a given tree, return some user-friendly descriptor
|
||||
export function getDescriptor(tree) {
|
||||
if ("path" in tree) {
|
||||
return trailingSlash.add(basename(tree.path));
|
||||
}
|
||||
|
||||
const source = tree[symbols.sourceSymbol];
|
||||
if (source) {
|
||||
// If the source looks like an identifier, use that.
|
||||
// TODO: Use real identifier parsing.
|
||||
const identifierRegex = /^[A-Za-z0-9_\-\.]+\/?$/;
|
||||
if (identifierRegex.test(source)) {
|
||||
return trailingSlash.add(source);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Return true if the text appears to contain non-printable binary characters;
|
||||
// used to infer whether a file is binary or text.
|
||||
export function hasNonPrintableCharacters(text) {
|
||||
// https://stackoverflow.com/a/1677660/76472
|
||||
return /[\x00-\x08\x0E-\x1F]/.test(text);
|
||||
}
|
||||
|
||||
export function isTransformApplied(Transform, obj) {
|
||||
let transformName = Transform.name;
|
||||
if (!transformName) {
|
||||
throw `isTransformApplied was called on an unnamed transform function, but a name is required.`;
|
||||
}
|
||||
if (transformName.endsWith("Transform")) {
|
||||
transformName = transformName.slice(0, -9);
|
||||
}
|
||||
// Walk up prototype chain looking for a constructor with the same name as the
|
||||
// transform. This is not a great test.
|
||||
for (let proto = obj; proto; proto = Object.getPrototypeOf(proto)) {
|
||||
if (proto.constructor.name === transformName) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the given object to a function.
|
||||
*
|
||||
* @typedef {import("../../index.ts").Invocable} Invocable
|
||||
* @param {any} obj
|
||||
* @returns {Function|null}
|
||||
*/
|
||||
export function toFunction(obj) {
|
||||
if (typeof obj === "function") {
|
||||
// Return a function as is.
|
||||
return obj;
|
||||
} else if (isUnpackable(obj)) {
|
||||
// Extract the contents of the object and convert that to a function.
|
||||
let fnPromise;
|
||||
/** @this {any} */
|
||||
return async function (...args) {
|
||||
if (!fnPromise) {
|
||||
// unpack() may return a function or a promise for a function; normalize
|
||||
// to a promise for a function
|
||||
const unpackPromise = Promise.resolve(obj.unpack());
|
||||
fnPromise = unpackPromise.then((content) => toFunction(content));
|
||||
}
|
||||
const fn = await fnPromise;
|
||||
return fn.call(this, ...args);
|
||||
};
|
||||
} else if (Tree.isTreelike(obj)) {
|
||||
// Return a function that invokes the tree's getter.
|
||||
return Tree.toFunction(obj);
|
||||
} else {
|
||||
// Not a function
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extend the async-tree toString method: objects that have a `@text` property
|
||||
* will return the value of that property as a string.
|
||||
*
|
||||
* @param {any} object
|
||||
* @returns {string|null}
|
||||
*/
|
||||
export function toString(object) {
|
||||
if (isPlainObject(object) && "@text" in object) {
|
||||
object = object["@text"];
|
||||
}
|
||||
return asyncTreeToString(object);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply a functional class mixin to an individual object instance.
|
||||
*
|
||||
* This works by create an intermediate class, creating an instance of that, and
|
||||
* then setting the intermediate class's prototype to the given individual
|
||||
* object. The resulting, extended object is then returned.
|
||||
*
|
||||
* This manipulation of the prototype chain is generally sound in JavaScript,
|
||||
* with some caveats. In particular, the original object class cannot make
|
||||
* direct use of private members; JavaScript will complain if the extended
|
||||
* object does anything that requires access to those private members.
|
||||
*
|
||||
* @param {Function} Transform
|
||||
* @param {any} obj
|
||||
*/
|
||||
export function transformObject(Transform, obj) {
|
||||
// Apply the mixin to Object and instantiate that. The Object base class here
|
||||
// is going to be cut out of the prototype chain in a moment; we just use
|
||||
// Object as a convenience because its constructor takes no arguments.
|
||||
const mixed = new (Transform(Object))();
|
||||
|
||||
// Find the highest prototype in the chain that was added by the class mixin.
|
||||
// The mixin may have added multiple prototypes to the chain. Walk up the
|
||||
// prototype chain until we hit Object.
|
||||
let mixinProto = Object.getPrototypeOf(mixed);
|
||||
while (Object.getPrototypeOf(mixinProto) !== Object.prototype) {
|
||||
mixinProto = Object.getPrototypeOf(mixinProto);
|
||||
}
|
||||
|
||||
// Redirect the prototype chain above the mixin to point to the original
|
||||
// object. The mixed object now extends the original object with the mixin.
|
||||
Object.setPrototypeOf(mixinProto, obj);
|
||||
|
||||
// Create a new constructor for this mixed object that reflects its prototype
|
||||
// chain. Because we've already got the instance we want, we won't use this
|
||||
// constructor now, but this can be used later to instantiate other objects
|
||||
// that look like the mixed one.
|
||||
mixed.constructor = Transform(obj.constructor);
|
||||
|
||||
// Return the mixed object.
|
||||
return mixed;
|
||||
}
|
||||
140
node_modules/@weborigami/origami/src/deprecated.js
generated
vendored
Normal file
140
node_modules/@weborigami/origami/src/deprecated.js
generated
vendored
Normal file
@@ -0,0 +1,140 @@
|
||||
import { Tree } from "@weborigami/async-tree";
|
||||
import * as calc from "./calc/calc.js";
|
||||
import * as dev from "./dev/dev.js";
|
||||
import * as image from "./image/image.js";
|
||||
import js from "./js.js";
|
||||
import node from "./node.js";
|
||||
import * as origami from "./origami/origami.js";
|
||||
import files from "./protocols/files.js";
|
||||
import * as site from "./site/site.js";
|
||||
import * as text from "./text/text.js";
|
||||
import * as tree from "./tree/tree.js";
|
||||
|
||||
const warningsDisplayedForKeys = new Set();
|
||||
|
||||
export function command(namespace, newKey, oldKey, fn) {
|
||||
const wrappedFn = function (...args) {
|
||||
const keys = newKey
|
||||
? `"${namespace}${newKey}" or just "${newKey}"`
|
||||
: `"${namespace}"`;
|
||||
if (!warningsDisplayedForKeys.has(oldKey)) {
|
||||
console.warn(
|
||||
`ori: Warning: "${oldKey}" is deprecated. Use ${keys} instead.`
|
||||
);
|
||||
warningsDisplayedForKeys.add(oldKey);
|
||||
}
|
||||
return fn instanceof Function
|
||||
? // @ts-ignore
|
||||
fn.call(this, ...args)
|
||||
: Tree.traverseOrThrow(fn, ...args);
|
||||
};
|
||||
if (fn.key) {
|
||||
wrappedFn.key = fn.key;
|
||||
}
|
||||
if (fn.inverseKey) {
|
||||
wrappedFn.inverseKey = fn.inverseKey;
|
||||
}
|
||||
return wrappedFn;
|
||||
}
|
||||
|
||||
export function commands(namespace, object) {
|
||||
const deprecatedEntries = Object.entries(object).map(([key, fn]) => [
|
||||
`@${fn.key ?? key}`,
|
||||
command(namespace, fn.key ?? key, `@${fn.key ?? key}`, fn),
|
||||
]);
|
||||
return Object.fromEntries(deprecatedEntries);
|
||||
}
|
||||
|
||||
export default {
|
||||
...commands("calc:", calc),
|
||||
...commands("dev:", dev),
|
||||
"@false": command("js:", "false", "@false", js.false),
|
||||
"@fetch": command("js:", "fetch", "@fetch", js.fetch),
|
||||
"@files": command("files:", null, "@files/", files),
|
||||
"@image": command("image:", null, "@image/", image),
|
||||
"@js": command("js:", null, "@js/", js),
|
||||
"@math": command("calc:", null, "@math/", calc),
|
||||
"@mdHtml": command("text:", "mdHtml", "@mdHtml", text.mdHtml),
|
||||
"@node": command("node:", null, "@node/", node),
|
||||
...commands("origami:", origami),
|
||||
...commands("site:", site),
|
||||
...commands("text:", text),
|
||||
...commands("tree:", tree),
|
||||
"@tree": command("tree:", null, "@tree/", Tree),
|
||||
"@true": command("js:", "true", "@true", js.true),
|
||||
|
||||
// Renamed commands
|
||||
"@clean": command("tree:", "clear", "@clean", tree.clear),
|
||||
|
||||
// Deprecated commands
|
||||
"@deepTakeFn": command(
|
||||
"tree:",
|
||||
"deepTake",
|
||||
"@deepTakeFn",
|
||||
(options) =>
|
||||
/** @this {any} */
|
||||
function (treelike) {
|
||||
return tree.deepTake.call(this, treelike, options);
|
||||
}
|
||||
),
|
||||
"@deepMapFn": command(
|
||||
"tree:",
|
||||
"deepMap",
|
||||
"@deepMapFn",
|
||||
(options) =>
|
||||
/** @this {any} */
|
||||
function (treelike) {
|
||||
return tree.deepMap.call(this, treelike, options);
|
||||
}
|
||||
),
|
||||
"@groupFn": command(
|
||||
"tree:",
|
||||
"group",
|
||||
"@groupFn",
|
||||
(options) =>
|
||||
/** @this {any} */
|
||||
function (treelike) {
|
||||
return tree.group.call(this, treelike, options);
|
||||
}
|
||||
),
|
||||
"@mapFn": command(
|
||||
"tree:",
|
||||
"map",
|
||||
"@mapFn",
|
||||
(options) =>
|
||||
/** @this {any} */
|
||||
function (treelike) {
|
||||
return tree.map.call(this, treelike, options);
|
||||
}
|
||||
),
|
||||
"@paginateFn": command(
|
||||
"tree:",
|
||||
"paginate",
|
||||
"@paginateFn",
|
||||
(options) =>
|
||||
/** @this {any} */
|
||||
function (treelike) {
|
||||
return tree.paginate.call(this, treelike, options);
|
||||
}
|
||||
),
|
||||
"@sortFn": command(
|
||||
"tree:",
|
||||
"sort",
|
||||
"@sortFn",
|
||||
(options) =>
|
||||
/** @this {any} */
|
||||
function (treelike) {
|
||||
return tree.sort.call(this, treelike, options);
|
||||
}
|
||||
),
|
||||
"@takeFn": command(
|
||||
"tree:",
|
||||
"take",
|
||||
"@takeFn",
|
||||
(options) =>
|
||||
/** @this {any} */
|
||||
function (treelike) {
|
||||
return tree.take.call(this, treelike, options);
|
||||
}
|
||||
),
|
||||
};
|
||||
5
node_modules/@weborigami/origami/src/dev/ExplorableSiteTransform.d.ts
generated
vendored
Normal file
5
node_modules/@weborigami/origami/src/dev/ExplorableSiteTransform.d.ts
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
import { Mixin } from "@weborigami/language";
|
||||
|
||||
declare const ExplorableSiteTransform: Mixin<{}>;
|
||||
|
||||
export default ExplorableSiteTransform;
|
||||
78
node_modules/@weborigami/origami/src/dev/ExplorableSiteTransform.js
generated
vendored
Normal file
78
node_modules/@weborigami/origami/src/dev/ExplorableSiteTransform.js
generated
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
import { Tree, jsonKeys } from "@weborigami/async-tree";
|
||||
import { isTransformApplied, transformObject } from "../common/utilities.js";
|
||||
import index from "../site/index.js";
|
||||
|
||||
/**
|
||||
* Wraps a tree (typically a SiteTree) to turn a standard site into an
|
||||
* explorable site.
|
||||
*
|
||||
* An explorable site follows three conventions:
|
||||
* 1. if route /foo has any resources beneath it (/foo/bar.jpg), then /foo
|
||||
* redirects to /foo/
|
||||
* 2. /foo/ is a synonym for foo/index.html
|
||||
* 3. /foo/.keys.json returns the public keys below foo/
|
||||
*
|
||||
* The first convention is handled by the Tree Origami server. This transform
|
||||
* handles the second and third conventions.
|
||||
*
|
||||
* As a convenience, this transform also provides a default index.html page if
|
||||
* the tree doesn't define one.
|
||||
*
|
||||
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
||||
* @typedef {import("../../index.ts").Constructor<AsyncTree>} AsyncTreeConstructor
|
||||
* @param {AsyncTreeConstructor} Base
|
||||
*/
|
||||
export default function ExplorableSiteTransform(Base) {
|
||||
return class ExplorableSite extends Base {
|
||||
async get(key) {
|
||||
// Ask the tree if it has the key.
|
||||
let value = await super.get(key);
|
||||
|
||||
if (value === undefined) {
|
||||
// The tree doesn't have the key; try the defaults.
|
||||
if (key === "index.html") {
|
||||
// This tree is both the function call target and the parameter.
|
||||
value = await index.call(this, this);
|
||||
} else if (key === ".keys.json") {
|
||||
value = await jsonKeys.stringify(this);
|
||||
}
|
||||
}
|
||||
|
||||
if (Tree.isAsyncTree(value)) {
|
||||
// Ensure this transform is applied to any tree result so the user
|
||||
// browse into data and trees of classes other than the current class.
|
||||
if (!isTransformApplied(ExplorableSiteTransform, value)) {
|
||||
value = transformObject(ExplorableSiteTransform, value);
|
||||
}
|
||||
} else if (value?.unpack) {
|
||||
// If the value isn't a tree, but has a tree attached via an `unpack`
|
||||
// method, wrap the unpack method to add this transform.
|
||||
const original = value.unpack.bind(value);
|
||||
const parent = this;
|
||||
value.unpack = async () => {
|
||||
const content = await original();
|
||||
// See function notes at @debug
|
||||
if (!Tree.isTraversable(content) || typeof content === "function") {
|
||||
return content;
|
||||
}
|
||||
/** @type {any} */
|
||||
let tree = Tree.from(content);
|
||||
if (!tree.parent) {
|
||||
tree.parent = parent;
|
||||
}
|
||||
if (!isTransformApplied(ExplorableSiteTransform, tree)) {
|
||||
tree = transformObject(ExplorableSiteTransform, tree);
|
||||
}
|
||||
return tree;
|
||||
};
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
// If this value is given to the server, the server will call this pack()
|
||||
// method. We respond with the index page.
|
||||
async pack() {
|
||||
return this.get("index.html");
|
||||
}
|
||||
};
|
||||
}
|
||||
5
node_modules/@weborigami/origami/src/dev/OriCommandTransform.d.ts
generated
vendored
Normal file
5
node_modules/@weborigami/origami/src/dev/OriCommandTransform.d.ts
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
import { Mixin } from "@weborigami/language";
|
||||
|
||||
declare const OriCommandTransform: Mixin<{}>;
|
||||
|
||||
export default OriCommandTransform;
|
||||
34
node_modules/@weborigami/origami/src/dev/OriCommandTransform.js
generated
vendored
Normal file
34
node_modules/@weborigami/origami/src/dev/OriCommandTransform.js
generated
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
/** @typedef {import("@weborigami/types").AsyncTree} AsyncTree */
|
||||
import ori from "../origami/ori.js";
|
||||
|
||||
/**
|
||||
* Add support for commands prefixed with `!`.
|
||||
*
|
||||
* E.g., asking this tree for `!yaml` will invoke the yaml() builtin function
|
||||
* in the context of this tree.
|
||||
*
|
||||
* @typedef {import("../../index.ts").Constructor<AsyncTree>} AsyncTreeConstructor
|
||||
* @param {AsyncTreeConstructor} Base
|
||||
*/
|
||||
export default function OriCommandTransform(Base) {
|
||||
return class OriCommand extends Base {
|
||||
async get(key) {
|
||||
let value = await super.get(key);
|
||||
|
||||
if (value === undefined) {
|
||||
if (
|
||||
key === undefined ||
|
||||
typeof key !== "string" ||
|
||||
!key.startsWith?.("!")
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
// Key is an Origami command; invoke it.
|
||||
const source = key.slice(1).trim();
|
||||
value = await ori.call(this, source, { formatResult: false });
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
};
|
||||
}
|
||||
12
node_modules/@weborigami/origami/src/dev/breakpoint.js
generated
vendored
Normal file
12
node_modules/@weborigami/origami/src/dev/breakpoint.js
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Break into the JavaScript debugger.
|
||||
*
|
||||
* This can be used to pause execution of the JavaScript code and inspect the
|
||||
* function argument and function target (`this`).
|
||||
*
|
||||
* @param {*} arg
|
||||
*/
|
||||
export default function breakpoint(arg) {
|
||||
debugger;
|
||||
return arg;
|
||||
}
|
||||
64
node_modules/@weborigami/origami/src/dev/changes.js
generated
vendored
Normal file
64
node_modules/@weborigami/origami/src/dev/changes.js
generated
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
import { trailingSlash, Tree } from "@weborigami/async-tree";
|
||||
|
||||
/**
|
||||
* Given an old tree and a new tree, return a tree of changes indicated
|
||||
* by the values: "added", "changed", or "deleted".
|
||||
*
|
||||
* @typedef {import("@weborigami/async-tree").Treelike} Treelike
|
||||
*
|
||||
* @this {import("@weborigami/types").AsyncTree|null}
|
||||
* @param {Treelike} oldTreelike
|
||||
* @param {Treelike} newTreelike
|
||||
*/
|
||||
export default async function changes(oldTreelike, newTreelike) {
|
||||
const oldTree = Tree.from(oldTreelike, { deep: true, parent: this });
|
||||
const newTree = Tree.from(newTreelike, { deep: true, parent: this });
|
||||
|
||||
const oldKeys = Array.from(await oldTree.keys());
|
||||
const newKeys = Array.from(await newTree.keys());
|
||||
|
||||
const oldKeysNormalized = oldKeys.map(trailingSlash.remove);
|
||||
const newKeysNormalized = newKeys.map(trailingSlash.remove);
|
||||
|
||||
let result;
|
||||
|
||||
for (const oldKey of oldKeys) {
|
||||
const oldNormalized = trailingSlash.remove(oldKey);
|
||||
if (!newKeysNormalized.includes(oldNormalized)) {
|
||||
result ??= {};
|
||||
result[oldKey] = "deleted";
|
||||
continue;
|
||||
}
|
||||
|
||||
const oldValue = await oldTree.get(oldKey);
|
||||
const newValue = await newTree.get(oldKey);
|
||||
|
||||
if (Tree.isAsyncTree(oldValue) && Tree.isAsyncTree(newValue)) {
|
||||
const treeChanges = await changes.call(this, oldValue, newValue);
|
||||
if (treeChanges && Object.keys(treeChanges).length > 0) {
|
||||
result ??= {};
|
||||
result[oldKey] = treeChanges;
|
||||
}
|
||||
} else if (oldValue?.toString && newValue?.toString) {
|
||||
const oldText = oldValue.toString();
|
||||
const newText = newValue.toString();
|
||||
if (oldText !== newText) {
|
||||
result ??= {};
|
||||
result[oldKey] = "changed";
|
||||
}
|
||||
} else {
|
||||
result ??= {};
|
||||
result[oldKey] = "changed";
|
||||
}
|
||||
}
|
||||
|
||||
for (const newKey of newKeys) {
|
||||
const newNormalized = trailingSlash.remove(newKey);
|
||||
if (!oldKeysNormalized.includes(newNormalized)) {
|
||||
result ??= {};
|
||||
result[newKey] = "added";
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
37
node_modules/@weborigami/origami/src/dev/code.js
generated
vendored
Normal file
37
node_modules/@weborigami/origami/src/dev/code.js
generated
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
import { symbols } from "@weborigami/language";
|
||||
import getTreeArgument from "../common/getTreeArgument.js";
|
||||
|
||||
/**
|
||||
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
||||
*
|
||||
* @this {AsyncTree|null}
|
||||
* @param {any} value
|
||||
*/
|
||||
export default async function code(value) {
|
||||
if (value === undefined) {
|
||||
value = await getTreeArgument(this, arguments, value, "dev:code");
|
||||
}
|
||||
if (value === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
const code = value.code ?? value[symbols.codeSymbol];
|
||||
return code ? functionNames(code) : undefined;
|
||||
}
|
||||
|
||||
function functionNames(code) {
|
||||
if (!Array.isArray(code)) {
|
||||
return code;
|
||||
}
|
||||
let [head, ...tail] = code;
|
||||
if (typeof head === "function") {
|
||||
const text = head.toString();
|
||||
if (text.startsWith("«ops.")) {
|
||||
head = text;
|
||||
} else {
|
||||
head = head.name;
|
||||
}
|
||||
} else {
|
||||
head = functionNames(head);
|
||||
}
|
||||
return [head, ...tail.map(functionNames)];
|
||||
}
|
||||
71
node_modules/@weborigami/origami/src/dev/debug.js
generated
vendored
Normal file
71
node_modules/@weborigami/origami/src/dev/debug.js
generated
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
import { Tree } from "@weborigami/async-tree";
|
||||
import getTreeArgument from "../common/getTreeArgument.js";
|
||||
import { isTransformApplied, transformObject } from "../common/utilities.js";
|
||||
import ExplorableSiteTransform from "./ExplorableSiteTransform.js";
|
||||
import OriCommandTransform from "./OriCommandTransform.js";
|
||||
|
||||
/**
|
||||
* Add debugging features to the indicated tree.
|
||||
*
|
||||
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
||||
* @typedef {import("@weborigami/async-tree").Treelike} Treelike
|
||||
*
|
||||
* @this {AsyncTree|null}
|
||||
* @param {Treelike} [treelike]
|
||||
*/
|
||||
export default async function debug(treelike) {
|
||||
// The debug command leaves the tree's existing scope intact; it does not
|
||||
// apply its own scope to the tree.
|
||||
let tree = await getTreeArgument(this, arguments, treelike, "dev:debug");
|
||||
|
||||
if (!isTransformApplied(DebugTransform, tree)) {
|
||||
tree = transformObject(DebugTransform, tree);
|
||||
}
|
||||
if (!isTransformApplied(ExplorableSiteTransform, tree)) {
|
||||
tree = transformObject(ExplorableSiteTransform, tree);
|
||||
}
|
||||
|
||||
return tree;
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {import("../../index.ts").Constructor<AsyncTree>} AsyncTreeConstructor
|
||||
* @param {AsyncTreeConstructor} Base
|
||||
*/
|
||||
function DebugTransform(Base) {
|
||||
return class Debug extends OriCommandTransform(Base) {
|
||||
async get(key) {
|
||||
let value = await super.get(key);
|
||||
const parent = this;
|
||||
|
||||
// Since this transform is for diagnostic purposes, cast any treelike
|
||||
// result to a tree so we can debug the result too. (Don't do this for
|
||||
// functions, as that can be undesirable, e.g., when writing functions
|
||||
// that handle POST requests.)
|
||||
if (Tree.isTreelike(value) && typeof value !== "function") {
|
||||
value = Tree.from(value, { parent });
|
||||
if (!isTransformApplied(DebugTransform, value)) {
|
||||
value = transformObject(DebugTransform, value);
|
||||
}
|
||||
} else if (value?.unpack) {
|
||||
// If the value isn't a tree, but has a tree attached via an `unpack`
|
||||
// method, wrap the unpack method to provide debug support for it.
|
||||
const original = value.unpack.bind(value);
|
||||
value.unpack = async () => {
|
||||
let content = await original();
|
||||
if (!Tree.isTraversable(content)) {
|
||||
return content;
|
||||
}
|
||||
/** @type {any} */
|
||||
let tree = Tree.from(content, { parent });
|
||||
if (!isTransformApplied(DebugTransform, tree)) {
|
||||
tree = transformObject(DebugTransform, tree);
|
||||
}
|
||||
return tree;
|
||||
};
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
};
|
||||
}
|
||||
9
node_modules/@weborigami/origami/src/dev/dev.js
generated
vendored
Normal file
9
node_modules/@weborigami/origami/src/dev/dev.js
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
export { default as breakpoint } from "./breakpoint.js";
|
||||
export { default as changes } from "./changes.js";
|
||||
export { default as code } from "./code.js";
|
||||
export { default as debug } from "./debug.js";
|
||||
export { default as explore } from "./explore.js";
|
||||
export { default as log } from "./log.js";
|
||||
export { default as serve } from "./serve.js";
|
||||
export { default as svg } from "./svg.js";
|
||||
export { default as watch } from "./watch.js";
|
||||
88
node_modules/@weborigami/origami/src/dev/explore.css
generated
vendored
Normal file
88
node_modules/@weborigami/origami/src/dev/explore.css
generated
vendored
Normal file
@@ -0,0 +1,88 @@
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
background: #333;
|
||||
color: #eee;
|
||||
display: grid;
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
font-size: 13px;
|
||||
grid-template-columns: 200px 1fr;
|
||||
grid-template-rows: minMax(0, 1fr);
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
nav {
|
||||
display: grid;
|
||||
gap: 1em;
|
||||
grid-auto-rows: min-content;
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
overflow: auto;
|
||||
padding: 1em 0.5em;
|
||||
}
|
||||
|
||||
#label {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#scopeToolbar {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, auto);
|
||||
}
|
||||
|
||||
button {
|
||||
background: transparent;
|
||||
border: solid 1px #555;
|
||||
color: inherit;
|
||||
font-size: smaller;
|
||||
font-family: inherit;
|
||||
font-weight: inherit;
|
||||
padding: 0.25em;
|
||||
}
|
||||
button:hover {
|
||||
border-color: #999;
|
||||
}
|
||||
button:active {
|
||||
border-color: #eee;
|
||||
}
|
||||
button[aria-pressed="true"] {
|
||||
background: #555;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
h2 {
|
||||
color: #999;
|
||||
font-size: inherit;
|
||||
margin: 0.25em 0;
|
||||
padding-left: 0.25em;
|
||||
}
|
||||
|
||||
li {
|
||||
padding: 0.25em;
|
||||
padding-left: 1em;
|
||||
text-indent: -0.75em;
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
iframe {
|
||||
background: white;
|
||||
border: none;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
80
node_modules/@weborigami/origami/src/dev/explore.js
generated
vendored
Normal file
80
node_modules/@weborigami/origami/src/dev/explore.js
generated
vendored
Normal file
@@ -0,0 +1,80 @@
|
||||
/** @typedef {import("@weborigami/types").AsyncTree} AsyncTree */
|
||||
import { Tree, scope } from "@weborigami/async-tree";
|
||||
import { OrigamiFiles } from "@weborigami/language";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import assertTreeIsDefined from "../common/assertTreeIsDefined.js";
|
||||
import { getDescriptor } from "../common/utilities.js";
|
||||
import { builtinsTree } from "../internal.js";
|
||||
import debug from "./debug.js";
|
||||
|
||||
let templatePromise;
|
||||
|
||||
/**
|
||||
* @this {AsyncTree|null}
|
||||
*/
|
||||
export default async function explore(...keys) {
|
||||
assertTreeIsDefined(this, "origami:explore");
|
||||
if (!this) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const tree = Tree.from(this);
|
||||
|
||||
/** @type {any} */
|
||||
let result;
|
||||
if (keys.length > 0) {
|
||||
// Traverse the scope using the given keys.
|
||||
const debugTree = await debug.call(tree, this);
|
||||
if (!debugTree) {
|
||||
return undefined;
|
||||
}
|
||||
const debugScope = scope(debugTree);
|
||||
// HACK: reproduce logic of ExplorableSiteTransform that turns a trailing
|
||||
// slash into index.html. Calling `debug` applies that transform and the
|
||||
// transform should handle that logic, but unfortunately the `traverse`
|
||||
// operation has special casing to treat a trailing slash, and never gives
|
||||
// ExplorableSiteTransform a chance.
|
||||
if (keys.at(-1) === "") {
|
||||
keys[keys.length - 1] = "index.html";
|
||||
}
|
||||
result = await Tree.traverse(debugScope, ...keys);
|
||||
} else {
|
||||
// Return the Explore page for the current scope.
|
||||
const data = await getScopeData(scope(tree));
|
||||
templatePromise ??= loadTemplate();
|
||||
const template = await templatePromise;
|
||||
const text = await template(data);
|
||||
|
||||
result = new String(text);
|
||||
result.unpack = () => debug.call(tree, tree);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async function getScopeData(scope) {
|
||||
const trees = scope.trees ?? [scope];
|
||||
const data = [];
|
||||
for (const tree of trees) {
|
||||
if (tree.parent === undefined) {
|
||||
// Skip builtins.
|
||||
continue;
|
||||
}
|
||||
const name = getDescriptor(tree);
|
||||
const treeKeys = Array.from(await tree.keys());
|
||||
// Skip system-ish files that start with a period.
|
||||
const keys = treeKeys.filter((key) => !key.startsWith?.("."));
|
||||
data.push({ name, keys });
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
async function loadTemplate() {
|
||||
const folderPath = path.resolve(fileURLToPath(import.meta.url), "..");
|
||||
const folder = new OrigamiFiles(folderPath);
|
||||
folder.parent = builtinsTree;
|
||||
const templateFile = await folder.get("explore.ori");
|
||||
const template = await templateFile.unpack();
|
||||
return template;
|
||||
}
|
||||
119
node_modules/@weborigami/origami/src/dev/explore.js.inline
generated
vendored
Normal file
119
node_modules/@weborigami/origami/src/dev/explore.js.inline
generated
vendored
Normal file
@@ -0,0 +1,119 @@
|
||||
let defaultPath;
|
||||
let frame;
|
||||
|
||||
const modes = {
|
||||
Content: "",
|
||||
Index: "!index",
|
||||
YAML: "!yaml",
|
||||
SVG: "!svg",
|
||||
};
|
||||
|
||||
// Extract the path from the URL hash.
|
||||
function getPathFromHash() {
|
||||
return window.location.hash.slice(1); // Remove `#`
|
||||
}
|
||||
|
||||
function getModeFromLocation() {
|
||||
const href = document.location.href;
|
||||
const match = /[\/](?<command>\!(?:index|yaml|svg))$/.exec(href);
|
||||
const command = match?.groups?.command ?? "";
|
||||
const mode =
|
||||
Object.keys(modes).find((key) => modes[key] === command) ?? "Content";
|
||||
return mode;
|
||||
}
|
||||
|
||||
function removeDocumentPath(path) {
|
||||
const documentPath = document.location.pathname;
|
||||
if (path.startsWith(documentPath)) {
|
||||
// Remove the document path prefix.
|
||||
path = path.slice(documentPath.length);
|
||||
}
|
||||
if (path.startsWith("/")) {
|
||||
// Remove the leading slash.
|
||||
path = path.slice(1);
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
function selectMode(newMode) {
|
||||
const currentMode = getModeFromLocation();
|
||||
if (newMode !== currentMode) {
|
||||
let newPath = removeDocumentPath(frame.contentDocument.location.pathname);
|
||||
const currentExtension = modes[currentMode];
|
||||
if (currentExtension && newPath.endsWith(currentExtension)) {
|
||||
// Remove the current extension.
|
||||
newPath = newPath.slice(0, -currentExtension.length);
|
||||
}
|
||||
const newExtension = modes[newMode];
|
||||
const separator = newPath.endsWith("/") ? "" : "/";
|
||||
const newFullPath = `${newPath}${separator}${newExtension}`;
|
||||
setPath(newFullPath);
|
||||
}
|
||||
}
|
||||
|
||||
function setPath(path) {
|
||||
// Show the indicated page in the frame.
|
||||
const abbreviatedPath = `/${path}`;
|
||||
const fullPath = `${document.location.pathname}/${path}`;
|
||||
const framePathname = frame.contentDocument.location.pathname;
|
||||
if (framePathname !== abbreviatedPath && framePathname !== fullPath) {
|
||||
// Use `replace` to avoid affecting browser history.
|
||||
frame.contentWindow.location.replace(fullPath);
|
||||
}
|
||||
|
||||
// If the path ends with a file name corresponding to a mode, select
|
||||
// the corresponding mode button.
|
||||
const mode = getModeFromLocation();
|
||||
const selectedButtonId = `button${mode}`;
|
||||
scopeToolbar.querySelectorAll("button").forEach((button) => {
|
||||
const pressed = button.id === selectedButtonId ? "true" : "false";
|
||||
button.setAttribute("aria-pressed", pressed);
|
||||
});
|
||||
}
|
||||
|
||||
// When hash changes, load the indicated page.
|
||||
window.addEventListener("hashchange", () => {
|
||||
const hashPath = getPathFromHash();
|
||||
const newPath = hashPath !== undefined ? hashPath : defaultPath;
|
||||
if (newPath) {
|
||||
setPath(newPath);
|
||||
}
|
||||
});
|
||||
|
||||
// Initialize
|
||||
window.addEventListener("load", () => {
|
||||
// Refresh title on page load.
|
||||
frame = document.getElementById("frame");
|
||||
frame.addEventListener("load", () => {
|
||||
if (frame.contentDocument.location.href !== "about:blank") {
|
||||
document.title = frame.contentDocument.title;
|
||||
const newPath = removeDocumentPath(
|
||||
frame.contentDocument.location.pathname
|
||||
);
|
||||
const hash = `#${newPath}`;
|
||||
if (window.location.hash !== hash) {
|
||||
// Use `replace` to avoid affecting browser history.
|
||||
window.location.replace(hash);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
buttonContent.addEventListener("click", () => {
|
||||
selectMode("Content");
|
||||
});
|
||||
buttonIndex.addEventListener("click", () => {
|
||||
selectMode("Index");
|
||||
});
|
||||
buttonYAML.addEventListener("click", () => {
|
||||
selectMode("YAML");
|
||||
});
|
||||
buttonSVG.addEventListener("click", () => {
|
||||
selectMode("SVG");
|
||||
});
|
||||
|
||||
// Navigate to any path already in the hash.
|
||||
defaultPath = getPathFromHash();
|
||||
if (defaultPath) {
|
||||
setPath(defaultPath);
|
||||
}
|
||||
});
|
||||
33
node_modules/@weborigami/origami/src/dev/explore.ori
generated
vendored
Normal file
33
node_modules/@weborigami/origami/src/dev/explore.ori
generated
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
(scope) => `<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
<title>Web Origami Explorer</title>
|
||||
<style>${ explore.css }</style>
|
||||
<script>${ explore.js.inline }</script>
|
||||
</head>
|
||||
<body>
|
||||
<nav>
|
||||
<div id="label">Web Origami Explorer</div>
|
||||
<div id="scopeToolbar">
|
||||
<button id="buttonContent">Content</button>
|
||||
<button id="buttonIndex">Index</button>
|
||||
<button id="buttonSVG">SVG</button>
|
||||
<button id="buttonYAML">YAML</button>
|
||||
</div>
|
||||
${ map(scope, (scopeTree) => `
|
||||
<ul>
|
||||
<h2>${ scopeTree/name ?? "" }</h2>
|
||||
${ map(scopeTree/keys, (key) => `
|
||||
<li>
|
||||
<a href="./!explore/${ key }" target="frame">${ key }</a>
|
||||
</li>
|
||||
`) }
|
||||
</ul>
|
||||
`) }
|
||||
</nav>
|
||||
<iframe id="frame" name="frame"></iframe>
|
||||
</body>
|
||||
</html>
|
||||
`
|
||||
16
node_modules/@weborigami/origami/src/dev/log.js
generated
vendored
Normal file
16
node_modules/@weborigami/origami/src/dev/log.js
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
import yaml from "../origami/yaml.js";
|
||||
|
||||
/**
|
||||
* Log the first argument to the console as a side effect and return the second
|
||||
* argument. If no second argument is provided, return the first argument.
|
||||
*
|
||||
* @this {import("@weborigami/types").AsyncTree|null}
|
||||
* @param {any} object
|
||||
* @param {any} [result]
|
||||
*/
|
||||
export default async function log(result, object = result) {
|
||||
let text = object !== undefined ? await yaml.call(this, object) : "undefined";
|
||||
text = text?.trim();
|
||||
console.log(text);
|
||||
return result;
|
||||
}
|
||||
68
node_modules/@weborigami/origami/src/dev/serve.js
generated
vendored
Normal file
68
node_modules/@weborigami/origami/src/dev/serve.js
generated
vendored
Normal file
@@ -0,0 +1,68 @@
|
||||
import { Tree } from "@weborigami/async-tree";
|
||||
import http from "node:http";
|
||||
import { createServer } from "node:net";
|
||||
import process from "node:process";
|
||||
import assertTreeIsDefined from "../common/assertTreeIsDefined.js";
|
||||
import { isTransformApplied, transformObject } from "../common/utilities.js";
|
||||
import { requestListener } from "../server/server.js";
|
||||
import debug from "./debug.js";
|
||||
import ExplorableSiteTransform from "./ExplorableSiteTransform.js";
|
||||
import watch from "./watch.js";
|
||||
|
||||
const defaultPort = 5000;
|
||||
|
||||
/**
|
||||
* Start a local web server for the indicated tree.
|
||||
*
|
||||
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
||||
* @typedef {import("@weborigami/async-tree").Treelike} Treelike
|
||||
*
|
||||
* @param {Treelike} treelike
|
||||
* @param {number} [port]
|
||||
* @this {AsyncTree|null}
|
||||
*/
|
||||
export default async function serve(treelike, port) {
|
||||
assertTreeIsDefined(this, "dev:serve");
|
||||
let tree;
|
||||
if (treelike) {
|
||||
tree = Tree.from(treelike, { parent: this });
|
||||
if (!isTransformApplied(ExplorableSiteTransform, tree)) {
|
||||
tree = transformObject(ExplorableSiteTransform, tree);
|
||||
}
|
||||
} else {
|
||||
// By default, watch the default tree and add default pages.
|
||||
const withDefaults = await debug.call(this);
|
||||
tree = await watch.call(this, withDefaults);
|
||||
}
|
||||
|
||||
if (port === undefined) {
|
||||
if (process.env.PORT) {
|
||||
// Use the port specified in the environment.
|
||||
port = parseInt(process.env.PORT);
|
||||
} else {
|
||||
// Find an open port.
|
||||
port = await findOpenPort(defaultPort);
|
||||
}
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
http.createServer(requestListener(tree)).listen(port, undefined, () => {
|
||||
console.log(
|
||||
`Server running at http://localhost:${port}. Press Ctrl+C to stop.`
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// Return the first open port number on or after the given port number.
|
||||
// From https://gist.github.com/mikeal/1840641?permalink_comment_id=2896667#gistcomment-2896667
|
||||
function findOpenPort(port) {
|
||||
const server = createServer();
|
||||
return new Promise((resolve, reject) =>
|
||||
server
|
||||
.on("error", (/** @type {any} */ error) =>
|
||||
error.code === "EADDRINUSE" ? server.listen(++port) : reject(error)
|
||||
)
|
||||
.on("listening", () => server.close(() => resolve(port)))
|
||||
.listen(port)
|
||||
);
|
||||
}
|
||||
40
node_modules/@weborigami/origami/src/dev/svg.js
generated
vendored
Normal file
40
node_modules/@weborigami/origami/src/dev/svg.js
generated
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
import graphviz from "graphviz-wasm";
|
||||
import getTreeArgument from "../common/getTreeArgument.js";
|
||||
import dot from "./treeDot.js";
|
||||
|
||||
let graphvizLoaded = false;
|
||||
|
||||
/**
|
||||
* Render a tree visually in SVG format.
|
||||
*
|
||||
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
||||
* @typedef {import("@weborigami/async-tree").Treelike} Treelike
|
||||
* @typedef {import("@weborigami/async-tree").PlainObject} PlainObject
|
||||
*
|
||||
* @this {AsyncTree|null}
|
||||
* @param {Treelike} [treelike]
|
||||
* @param {PlainObject} [options]
|
||||
*/
|
||||
export default async function svg(treelike, options = {}) {
|
||||
if (!graphvizLoaded) {
|
||||
await graphviz.loadWASM();
|
||||
graphvizLoaded = true;
|
||||
}
|
||||
const tree = await getTreeArgument(
|
||||
this,
|
||||
arguments,
|
||||
treelike,
|
||||
"dev:svg",
|
||||
true
|
||||
);
|
||||
const dotText = await dot.call(this, tree, options);
|
||||
if (dotText === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
const svgText = await graphviz.layout(dotText, "svg");
|
||||
/** @type {any} */
|
||||
const result = new String(svgText);
|
||||
result.mediaType = "image/svg+xml";
|
||||
result.unpack = () => tree;
|
||||
return result;
|
||||
}
|
||||
193
node_modules/@weborigami/origami/src/dev/treeDot.js
generated
vendored
Normal file
193
node_modules/@weborigami/origami/src/dev/treeDot.js
generated
vendored
Normal file
@@ -0,0 +1,193 @@
|
||||
import {
|
||||
Tree,
|
||||
isPlainObject,
|
||||
isStringLike,
|
||||
toString,
|
||||
trailingSlash,
|
||||
} from "@weborigami/async-tree";
|
||||
import * as serialize from "../common/serialize.js";
|
||||
import { getDescriptor } from "../common/utilities.js";
|
||||
|
||||
/**
|
||||
* Render a tree in DOT format.
|
||||
*
|
||||
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
||||
* @typedef {import("@weborigami/async-tree").Treelike} Treelike
|
||||
* @typedef {import("@weborigami/async-tree").PlainObject} PlainObject
|
||||
*
|
||||
* @this {AsyncTree|null}
|
||||
* @param {Treelike} [treelike]
|
||||
* @param {PlainObject} [options]
|
||||
*/
|
||||
export default async function dot(treelike, options = {}) {
|
||||
const tree = Tree.from(treelike, { deep: true });
|
||||
const rootLabel = getDescriptor(tree) ?? "";
|
||||
const treeArcs = await statements(tree, "", rootLabel, options);
|
||||
return `digraph g {
|
||||
bgcolor="transparent";
|
||||
nodesep=1;
|
||||
rankdir=LR;
|
||||
ranksep=1.5;
|
||||
node [color=gray70; fillcolor="white"; fontname="Helvetica"; fontsize="10"; nojustify=true; style="filled"; shape=box];
|
||||
edge [arrowhead=vee; arrowsize=0.75; color=gray60; fontname="Helvetica"; fontsize="10"; labeldistance=5];
|
||||
|
||||
${treeArcs.join("\n")}
|
||||
}`;
|
||||
}
|
||||
|
||||
async function statements(tree, nodePath, nodeLabel, options) {
|
||||
let result = [];
|
||||
const createLinks = options.createLinks ?? true;
|
||||
|
||||
// Add a node for the root of this (sub)tree.
|
||||
const rootUrl = nodePath || ".";
|
||||
const url = createLinks ? `; URL="${rootUrl}"` : "";
|
||||
const rootLabel = nodeLabel ? `; xlabel="${nodeLabel}"` : "";
|
||||
result.push(
|
||||
` "${nodePath}" [shape=circle${rootLabel}; label=""; color=gray40; width=0.15${url}];`
|
||||
);
|
||||
|
||||
// Draw edges and collect labels for the nodes they lead to.
|
||||
let nodes = new Map();
|
||||
for (const key of await tree.keys()) {
|
||||
const destPath = nodePath ? `${trailingSlash.add(nodePath)}${key}` : key;
|
||||
const arc = ` "${nodePath}" -> "${destPath}" [label="${key}"];`;
|
||||
result.push(arc);
|
||||
|
||||
let isError = false;
|
||||
let value;
|
||||
try {
|
||||
value = await tree.get(key);
|
||||
} catch (/** @type {any} */ error) {
|
||||
isError = true;
|
||||
value =
|
||||
error.name && error.message
|
||||
? `${error.name}: ${error.message}`
|
||||
: error.name ?? error.message ?? error;
|
||||
}
|
||||
|
||||
const expandable =
|
||||
value instanceof Array || isPlainObject(value) || Tree.isAsyncTree(value);
|
||||
if (expandable) {
|
||||
const subtree = Tree.from(value);
|
||||
const subStatements = await statements(subtree, destPath, null, options);
|
||||
result = result.concat(subStatements);
|
||||
} else {
|
||||
const label = isStringLike(value)
|
||||
? toString(value)
|
||||
: value !== undefined
|
||||
? await serialize.toYaml(value)
|
||||
: "";
|
||||
if (value === undefined) {
|
||||
isError = true;
|
||||
}
|
||||
|
||||
const data = { label };
|
||||
if (isError) {
|
||||
data.isError = true;
|
||||
}
|
||||
nodes.set(key, data);
|
||||
}
|
||||
}
|
||||
|
||||
// If we have more than one label, we'll focus on the labels' differences.
|
||||
// We'll use the first label as a representative baseline for all labels but
|
||||
// the first (which will use the second label as a baseline).
|
||||
const values = [...nodes.values()];
|
||||
const showLabelDiffs = values.length > 1;
|
||||
const label1 = showLabelDiffs ? String(values[0].label) : undefined;
|
||||
const label2 = showLabelDiffs ? String(values[1].label) : undefined;
|
||||
|
||||
// Trim labels.
|
||||
let i = 0;
|
||||
for (const data of nodes.values()) {
|
||||
let label = data.label;
|
||||
if (label === null) {
|
||||
data.label = "[binary data]";
|
||||
} else if (label) {
|
||||
let clippedStart = false;
|
||||
let clippedEnd = false;
|
||||
|
||||
if (showLabelDiffs) {
|
||||
const baseline = i === 0 ? label2 : label1;
|
||||
const diff = stringDiff(baseline, label);
|
||||
if (diff !== label) {
|
||||
label = diff;
|
||||
clippedStart = true;
|
||||
}
|
||||
}
|
||||
|
||||
label = label.trim();
|
||||
|
||||
if (label.length > 40) {
|
||||
// Long text, just use the beginning
|
||||
label = label.slice(0, 40);
|
||||
clippedEnd = true;
|
||||
}
|
||||
|
||||
// Left justify node label using weird Dot escape character
|
||||
// See https://stackoverflow.com/a/13104953/76472
|
||||
const endsWithNewline = label.endsWith("\n");
|
||||
label = label.replace(/\n/g, "\\l");
|
||||
|
||||
label = label.replace(/"/g, '\\"'); // Escape quotes
|
||||
label = label.replace(/[\ \t]+/g, " "); // Collapse spaces and tabs
|
||||
|
||||
// Add ellipses if we clipped the label. We'd prefer to end with a real
|
||||
// ellipsis, but GraphViz warns about "non-ASCII character 226" if we do.
|
||||
// (That's not even the ellipsis character!) We could use a real ellipsis
|
||||
// for the start, but then they might look different.
|
||||
if (clippedStart) {
|
||||
label = "..." + label;
|
||||
}
|
||||
if (clippedEnd) {
|
||||
label += "...";
|
||||
}
|
||||
|
||||
if (!endsWithNewline) {
|
||||
// See note above
|
||||
label += "\\l";
|
||||
}
|
||||
|
||||
data.label = label;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
// Draw labels.
|
||||
for (const [key, node] of nodes.entries()) {
|
||||
const icon = node.isError ? "⚠️ " : "";
|
||||
// GraphViz has trouble rendering DOT nodes whose labels contain ellipsis
|
||||
// characters, so we map those to three periods. GraphViz appears to turn
|
||||
// those back into ellipsis characters when rendering the graph.
|
||||
const text = node.label.replace(/…/g, "...");
|
||||
const label = `label="${icon}${text}"`;
|
||||
const color = node.isError ? `; color="red"` : "";
|
||||
const fill = node.isError ? `; fillcolor="#FFF4F4"` : "";
|
||||
const destPath = nodePath ? `${trailingSlash.add(nodePath)}${key}` : key;
|
||||
const url = createLinks ? `; URL="${destPath}"` : "";
|
||||
result.push(` "${destPath}" [${label}${color}${fill}${url}];`);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Return the second string, removing the initial portion it shares with the
|
||||
// first string. The returned string will start with the first non-whitespace
|
||||
// character of the first line that differs from the first string.
|
||||
function stringDiff(first, second) {
|
||||
let i = 0;
|
||||
// Find point of first difference.
|
||||
while (i < first.length && i < second.length && first[i] === second[i]) {
|
||||
i++;
|
||||
}
|
||||
// Back up to start of that line.
|
||||
while (i > 0 && second[i - 1] !== "\n") {
|
||||
i--;
|
||||
}
|
||||
// Move forward to first non-whitespace character.
|
||||
while (i < second.length && /\s/.test(second[i])) {
|
||||
i++;
|
||||
}
|
||||
return second.slice(i);
|
||||
}
|
||||
89
node_modules/@weborigami/origami/src/dev/watch.js
generated
vendored
Normal file
89
node_modules/@weborigami/origami/src/dev/watch.js
generated
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
import { Tree } from "@weborigami/async-tree";
|
||||
import { formatError } from "@weborigami/language";
|
||||
import ConstantTree from "../common/ConstantTree.js";
|
||||
import getTreeArgument from "../common/getTreeArgument.js";
|
||||
|
||||
/**
|
||||
* Let a tree (e.g., of files) respond to changes.
|
||||
*
|
||||
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
||||
* @typedef {import("@weborigami/async-tree").Treelike} Treelike
|
||||
* @typedef {import("../../index.ts").Invocable} Invocable
|
||||
*
|
||||
* @this {AsyncTree|null}
|
||||
* @param {Treelike} [treelike]
|
||||
* @param {Invocable} [fn]
|
||||
*/
|
||||
export default async function watch(treelike, fn) {
|
||||
/** @type {any} */
|
||||
const container = await getTreeArgument(
|
||||
this,
|
||||
arguments,
|
||||
treelike,
|
||||
"dev:watch"
|
||||
);
|
||||
|
||||
// Watch the indicated tree.
|
||||
await /** @type {any} */ (container).watch?.();
|
||||
|
||||
if (fn === undefined) {
|
||||
return container;
|
||||
}
|
||||
|
||||
// The caller supplied a function to reevaluate whenever the tree changes.
|
||||
let tree = await evaluateTree(container, fn);
|
||||
|
||||
// We want to return a stable reference to the tree, so we'll use a prototype
|
||||
// chain that will always point to the latest tree. We'll extend the tree's
|
||||
// prototype chain with an empty object, and use that as a handle (pointer to
|
||||
// a pointer) to the tree. This is what we'll return to the caller.
|
||||
const handle = Object.create(tree);
|
||||
|
||||
// Reevaluate the function whenever the tree changes.
|
||||
container.addEventListener?.("change", async () => {
|
||||
const tree = await evaluateTree(container, fn);
|
||||
updateIndirectPointer(handle, tree);
|
||||
});
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
async function evaluateTree(parent, fn) {
|
||||
let tree;
|
||||
let message;
|
||||
let result;
|
||||
try {
|
||||
result = await fn();
|
||||
} catch (/** @type {any} */ error) {
|
||||
message = formatError(error);
|
||||
}
|
||||
tree = result ? Tree.from(result, { parent }) : undefined;
|
||||
if (tree) {
|
||||
return tree;
|
||||
}
|
||||
if (!message) {
|
||||
message = `warning: watch expression did not return a tree`;
|
||||
}
|
||||
console.warn(message);
|
||||
tree = new ConstantTree(message);
|
||||
tree.parent = parent;
|
||||
return tree;
|
||||
}
|
||||
|
||||
// Update an indirect pointer to a target.
|
||||
function updateIndirectPointer(indirect, target) {
|
||||
// Clean the pointer of any named properties or symbols that have been set
|
||||
// directly on it.
|
||||
try {
|
||||
for (const key of Object.getOwnPropertyNames(indirect)) {
|
||||
delete indirect[key];
|
||||
}
|
||||
for (const key of Object.getOwnPropertySymbols(indirect)) {
|
||||
delete indirect[key];
|
||||
}
|
||||
} catch {
|
||||
// Ignore errors.
|
||||
}
|
||||
|
||||
Object.setPrototypeOf(indirect, target);
|
||||
}
|
||||
7
node_modules/@weborigami/origami/src/handlers/css.handler.js
generated
vendored
Normal file
7
node_modules/@weborigami/origami/src/handlers/css.handler.js
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
// .css files use the .txt loader
|
||||
import fileTypeText from "./txt.handler.js";
|
||||
|
||||
export default {
|
||||
...fileTypeText,
|
||||
mediaType: "text/css",
|
||||
};
|
||||
16
node_modules/@weborigami/origami/src/handlers/handlerExports.js
generated
vendored
Normal file
16
node_modules/@weborigami/origami/src/handlers/handlerExports.js
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
export { default as cssHandler } from "./css.handler.js";
|
||||
export { default as htmHandler } from "./htm.handler.js";
|
||||
export { default as htmlHandler } from "./html.handler.js";
|
||||
export { default as jpegHandler } from "./jpeg.handler.js";
|
||||
export { default as jpgHandler } from "./jpg.handler.js";
|
||||
export { default as jsHandler } from "./js.handler.js";
|
||||
export { default as jsonHandler } from "./json.handler.js";
|
||||
export { default as mdHandler } from "./md.handler.js";
|
||||
export { default as mjsHandler } from "./mjs.handler.js";
|
||||
export { default as oriHandler } from "./ori.handler.js";
|
||||
export { default as oridocumentHandler } from "./oridocument.handler.js";
|
||||
export { default as txtHandler } from "./txt.handler.js";
|
||||
export { default as wasmHandler } from "./wasm.handler.js";
|
||||
export { default as xhtmlHandler } from "./xhtml.handler.js";
|
||||
export { default as yamlHandler } from "./yaml.handler.js";
|
||||
export { default as ymlHandler } from "./yml.handler.js";
|
||||
37
node_modules/@weborigami/origami/src/handlers/handlers.js
generated
vendored
Normal file
37
node_modules/@weborigami/origami/src/handlers/handlers.js
generated
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
import {
|
||||
jsHandler,
|
||||
oriHandler,
|
||||
oridocumentHandler,
|
||||
wasmHandler,
|
||||
yamlHandler,
|
||||
} from "../internal.js";
|
||||
import cssHandler from "./css.handler.js";
|
||||
import htmHandler from "./htm.handler.js";
|
||||
import htmlHandler from "./html.handler.js";
|
||||
import jpegHandler from "./jpeg.handler.js";
|
||||
import jpgHandler from "./jpg.handler.js";
|
||||
import jsonHandler from "./json.handler.js";
|
||||
import mdHandler from "./md.handler.js";
|
||||
import mjsHandler from "./mjs.handler.js";
|
||||
import txtHandler from "./txt.handler.js";
|
||||
import xhtmlHandler from "./xhtml.handler.js";
|
||||
import ymlHandler from "./yml.handler.js";
|
||||
|
||||
export default {
|
||||
"css.handler": cssHandler,
|
||||
"htm.handler": htmHandler,
|
||||
"html.handler": htmlHandler,
|
||||
"jpeg.handler": jpegHandler,
|
||||
"jpg.handler": jpgHandler,
|
||||
"js.handler": jsHandler,
|
||||
"json.handler": jsonHandler,
|
||||
"md.handler": mdHandler,
|
||||
"mjs.handler": mjsHandler,
|
||||
"ori.handler": oriHandler,
|
||||
"oridocument.handler": oridocumentHandler,
|
||||
"txt.handler": txtHandler,
|
||||
"wasm.handler": wasmHandler,
|
||||
"xhtml.handler": xhtmlHandler,
|
||||
"yaml.handler": yamlHandler,
|
||||
"yml.handler": ymlHandler,
|
||||
};
|
||||
2
node_modules/@weborigami/origami/src/handlers/htm.handler.js
generated
vendored
Normal file
2
node_modules/@weborigami/origami/src/handlers/htm.handler.js
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
// .htm is a synonynm for .html
|
||||
export { default } from "./html.handler.js";
|
||||
7
node_modules/@weborigami/origami/src/handlers/html.handler.js
generated
vendored
Normal file
7
node_modules/@weborigami/origami/src/handlers/html.handler.js
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
// .html files use the .txt loader
|
||||
import fileTypeText from "./txt.handler.js";
|
||||
|
||||
export default {
|
||||
...fileTypeText,
|
||||
mediaType: "text/html",
|
||||
};
|
||||
62
node_modules/@weborigami/origami/src/handlers/jpeg.handler.js
generated
vendored
Normal file
62
node_modules/@weborigami/origami/src/handlers/jpeg.handler.js
generated
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
import exifParser from "exif-parser";
|
||||
|
||||
const exifDateTags = [
|
||||
"ModifyDate",
|
||||
"MDPrepDate",
|
||||
"DateTimeOriginal",
|
||||
"CreateDate",
|
||||
"PreviewDateTime",
|
||||
"GPSDateStamp",
|
||||
];
|
||||
|
||||
/**
|
||||
* A JPEG file with possible Exif metadata
|
||||
*/
|
||||
export default {
|
||||
mediaType: "image/jpeg",
|
||||
|
||||
/** @type {import("@weborigami/language").UnpackFunction} */
|
||||
async unpack(packed, options) {
|
||||
if (packed instanceof Uint8Array) {
|
||||
// Downgrade to old Node Buffer for exif-parser.
|
||||
packed = Buffer.from(packed);
|
||||
}
|
||||
const parser = exifParser.create(packed);
|
||||
parser.enableTagNames(true);
|
||||
parser.enableSimpleValues(true);
|
||||
const parsed = await parser.parse();
|
||||
|
||||
// The exif-parser `enableSimpleValues` option should convert dates to
|
||||
// JavaScript Date objects, but that doesn't seem to work. Ensure dates are
|
||||
// Date objects.
|
||||
const exif = parsed.tags;
|
||||
for (const tag of exifDateTags) {
|
||||
if (typeof exif[tag] === "number") {
|
||||
exif[tag] = new Date(exif[tag] * 1000);
|
||||
}
|
||||
}
|
||||
|
||||
const result = {
|
||||
height: parsed.imageSize.height,
|
||||
width: parsed.imageSize.width,
|
||||
exif,
|
||||
};
|
||||
|
||||
// Promote some Exif properties to the top level.
|
||||
const tagsToPromote = {
|
||||
ImageDescription: "caption",
|
||||
ModifyDate: "modified",
|
||||
Orientation: "orientation",
|
||||
};
|
||||
for (const [tag, key] of Object.entries(tagsToPromote)) {
|
||||
if (exif[tag] !== undefined) {
|
||||
result[key] = exif[tag];
|
||||
}
|
||||
}
|
||||
|
||||
// Add aspect ratio for use with `aspect-ratio` CSS.
|
||||
result.aspectRatio = result.width / result.height;
|
||||
|
||||
return result;
|
||||
},
|
||||
};
|
||||
2
node_modules/@weborigami/origami/src/handlers/jpg.handler.js
generated
vendored
Normal file
2
node_modules/@weborigami/origami/src/handlers/jpg.handler.js
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
// .jpg is a synonym for .jpeg
|
||||
export { default } from "./jpeg.handler.js";
|
||||
20
node_modules/@weborigami/origami/src/handlers/js.handler.js
generated
vendored
Normal file
20
node_modules/@weborigami/origami/src/handlers/js.handler.js
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
import { processUnpackedContent } from "../internal.js";
|
||||
|
||||
/**
|
||||
* A JavaScript file
|
||||
*
|
||||
* Unpacking a JavaScript file returns its default export, or its set of exports
|
||||
* if there is more than one.
|
||||
*/
|
||||
export default {
|
||||
mediaType: "application/javascript",
|
||||
|
||||
/** @type {import("@weborigami/language").UnpackFunction} */
|
||||
async unpack(packed, options = {}) {
|
||||
const { key, parent } = options;
|
||||
if (parent && "import" in parent) {
|
||||
const content = await /** @type {any} */ (parent).import?.(key);
|
||||
return processUnpackedContent(content, parent);
|
||||
}
|
||||
},
|
||||
};
|
||||
27
node_modules/@weborigami/origami/src/handlers/json.handler.js
generated
vendored
Normal file
27
node_modules/@weborigami/origami/src/handlers/json.handler.js
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
import { symbols } from "@weborigami/async-tree";
|
||||
import * as utilities from "../common/utilities.js";
|
||||
|
||||
/**
|
||||
* A JSON file
|
||||
*
|
||||
* Unpacking a JSON file returns the parsed data.
|
||||
*/
|
||||
export default {
|
||||
mediaType: "application/json",
|
||||
|
||||
/** @type {import("@weborigami/language").UnpackFunction} */
|
||||
unpack(packed) {
|
||||
const json = utilities.toString(packed);
|
||||
if (!json) {
|
||||
throw new Error("Tried to parse something as JSON but it wasn't text.");
|
||||
}
|
||||
const data = JSON.parse(json);
|
||||
if (data && typeof data === "object" && Object.isExtensible(data)) {
|
||||
Object.defineProperty(data, symbols.deep, {
|
||||
enumerable: false,
|
||||
value: true,
|
||||
});
|
||||
}
|
||||
return data;
|
||||
},
|
||||
};
|
||||
7
node_modules/@weborigami/origami/src/handlers/md.handler.js
generated
vendored
Normal file
7
node_modules/@weborigami/origami/src/handlers/md.handler.js
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
// .md files use the .txt loader
|
||||
import fileTypeText from "./txt.handler.js";
|
||||
|
||||
export default {
|
||||
...fileTypeText,
|
||||
mediaType: "text/markdown",
|
||||
};
|
||||
2
node_modules/@weborigami/origami/src/handlers/mjs.handler.js
generated
vendored
Normal file
2
node_modules/@weborigami/origami/src/handlers/mjs.handler.js
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
// .mjs is a synonynm for .js
|
||||
export { jsHandler as default } from "../internal.js";
|
||||
46
node_modules/@weborigami/origami/src/handlers/ori.handler.js
generated
vendored
Normal file
46
node_modules/@weborigami/origami/src/handlers/ori.handler.js
generated
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
import { symbols } from "@weborigami/async-tree";
|
||||
import { compile } from "@weborigami/language";
|
||||
import * as utilities from "../common/utilities.js";
|
||||
import { builtinsTree, processUnpackedContent } from "../internal.js";
|
||||
|
||||
/**
|
||||
* An Origami expression file
|
||||
*
|
||||
* Unpacking an Origami file returns the result of evaluating the expression.
|
||||
*/
|
||||
export default {
|
||||
mediaType: "text/plain",
|
||||
|
||||
/** @type {import("@weborigami/language").UnpackFunction} */
|
||||
async unpack(packed, options = {}) {
|
||||
const parent =
|
||||
options.parent ??
|
||||
/** @type {any} */ (packed).parent ??
|
||||
/** @type {any} */ (packed)[symbols.parent];
|
||||
|
||||
// Construct an object to represent the source code.
|
||||
const sourceName = options.key;
|
||||
let url;
|
||||
if (sourceName && parent?.url) {
|
||||
let parentHref = parent.url.href;
|
||||
if (!parentHref.endsWith("/")) {
|
||||
parentHref += "/";
|
||||
}
|
||||
url = new URL(sourceName, parentHref);
|
||||
}
|
||||
|
||||
const source = {
|
||||
text: utilities.toString(packed),
|
||||
name: options.key,
|
||||
url,
|
||||
};
|
||||
|
||||
// Compile the source code as an Origami program and evaluate it.
|
||||
const compiler = options.compiler ?? compile.program;
|
||||
const fn = compiler(source);
|
||||
const target = parent ?? builtinsTree;
|
||||
let content = await fn.call(target);
|
||||
|
||||
return processUnpackedContent(content, parent);
|
||||
},
|
||||
};
|
||||
43
node_modules/@weborigami/origami/src/handlers/oridocument.handler.js
generated
vendored
Normal file
43
node_modules/@weborigami/origami/src/handlers/oridocument.handler.js
generated
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
import { symbols } from "@weborigami/async-tree";
|
||||
import { compile } from "@weborigami/language";
|
||||
import * as utilities from "../common/utilities.js";
|
||||
import { processUnpackedContent } from "../internal.js";
|
||||
|
||||
/**
|
||||
* An Origami template document: a plain text file that contains Origami
|
||||
* expressions.
|
||||
*/
|
||||
export default {
|
||||
mediaType: "text/plain",
|
||||
|
||||
/** @type {import("@weborigami/language").UnpackFunction} */
|
||||
async unpack(packed, options = {}) {
|
||||
const parent =
|
||||
options.parent ??
|
||||
/** @type {any} */ (packed).parent ??
|
||||
/** @type {any} */ (packed)[symbols.parent];
|
||||
|
||||
// Construct an object to represent the source code.
|
||||
const sourceName = options.key;
|
||||
let url;
|
||||
if (sourceName && parent?.url) {
|
||||
let parentHref = parent.url.href;
|
||||
if (!parentHref.endsWith("/")) {
|
||||
parentHref += "/";
|
||||
}
|
||||
url = new URL(sourceName, parentHref);
|
||||
}
|
||||
|
||||
const source = {
|
||||
text: utilities.toString(packed),
|
||||
name: options.key,
|
||||
url,
|
||||
};
|
||||
|
||||
// Compile the text as an Origami template document.
|
||||
const templateDefineFn = compile.templateDocument(source);
|
||||
const templateFn = await templateDefineFn.call(parent);
|
||||
|
||||
return processUnpackedContent(templateFn, parent);
|
||||
},
|
||||
};
|
||||
78
node_modules/@weborigami/origami/src/handlers/txt.handler.js
generated
vendored
Normal file
78
node_modules/@weborigami/origami/src/handlers/txt.handler.js
generated
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
import { isPacked, symbols } from "@weborigami/async-tree";
|
||||
import { parseYaml, toYaml } from "../common/serialize.js";
|
||||
import * as utilities from "../common/utilities.js";
|
||||
|
||||
/**
|
||||
* A text file with possible front matter
|
||||
*
|
||||
* The unpacking process will parse out any YAML or JSON front matter and attach
|
||||
* it to the document as data. The first line of the text must be "---",
|
||||
* followed by a block of JSON or YAML, followed by another line of "---". Any
|
||||
* lines following will be treated as the document text.
|
||||
*
|
||||
* If there is no front matter, the document will be treated as plain text and
|
||||
* returned as a String object.
|
||||
*
|
||||
* If there is front matter, any Origami expressions in the front matter will be
|
||||
* evaluated. The result will be a plain JavaScript object with the evaluated
|
||||
* data and a `@text` property containing the document text.
|
||||
*/
|
||||
export default {
|
||||
mediaType: "text/plain",
|
||||
|
||||
/**
|
||||
* If the input is already in some packed format, it will be returned as is.
|
||||
*
|
||||
* Otherwise, the properties of the object will be formatted as YAML. If the
|
||||
* object has a `@text` property, that will be used as the body of the text
|
||||
* document; otherwise, an empty string will be used.
|
||||
*
|
||||
* @param {any} object
|
||||
* @returns {Promise<import("@weborigami/async-tree").Packed>}
|
||||
*/
|
||||
async pack(object) {
|
||||
if (isPacked(object)) {
|
||||
return object;
|
||||
} else if (!object || typeof object !== "object") {
|
||||
throw new TypeError("The input to pack must be a JavaScript object.");
|
||||
}
|
||||
|
||||
const text = object["@text"] ?? "";
|
||||
|
||||
/** @type {any} */
|
||||
const dataWithoutText = Object.assign({}, object);
|
||||
delete dataWithoutText["@text"];
|
||||
if (Object.keys(dataWithoutText).length > 0) {
|
||||
const frontMatter = (await toYaml(dataWithoutText)).trimEnd();
|
||||
return `---\n${frontMatter}\n---\n${text}`;
|
||||
} else {
|
||||
return text;
|
||||
}
|
||||
},
|
||||
|
||||
/** @type {import("@weborigami/language").UnpackFunction} */
|
||||
unpack(packed, options = {}) {
|
||||
const parent = options.parent ?? null;
|
||||
const text = utilities.toString(packed);
|
||||
if (text === null) {
|
||||
throw new Error("Tried to treat something as text but it wasn't text.");
|
||||
}
|
||||
|
||||
const regex =
|
||||
/^(---\r?\n(?<frontText>[\s\S]*?\r?\n?)---\r?\n)(?<body>[\s\S]*$)/;
|
||||
const match = regex.exec(text);
|
||||
let unpacked;
|
||||
if (match) {
|
||||
// Document object with front matter
|
||||
const { body, frontText } = /** @type {any} */ (match.groups);
|
||||
const frontData = parseYaml(frontText);
|
||||
unpacked = Object.assign({}, frontData, { "@text": body });
|
||||
} else {
|
||||
// Plain text
|
||||
unpacked = new String(text);
|
||||
}
|
||||
|
||||
unpacked[symbols.parent] = parent;
|
||||
return unpacked;
|
||||
},
|
||||
};
|
||||
17
node_modules/@weborigami/origami/src/handlers/wasm.handler.js
generated
vendored
Normal file
17
node_modules/@weborigami/origami/src/handlers/wasm.handler.js
generated
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
import { processUnpackedContent } from "../internal.js";
|
||||
|
||||
/**
|
||||
* A WebAssembly module
|
||||
*
|
||||
* Unpacking a WebAssembly module returns its exports.
|
||||
*/
|
||||
export default {
|
||||
mediaType: "application/wasm",
|
||||
|
||||
/** @type {import("@weborigami/language").UnpackFunction} */
|
||||
async unpack(packed, options = {}) {
|
||||
const wasmModule = await WebAssembly.instantiate(packed);
|
||||
// @ts-ignore TypeScript thinks wasmModule is already an Instance.
|
||||
return processUnpackedContent(wasmModule.instance.exports, options.parent);
|
||||
},
|
||||
};
|
||||
2
node_modules/@weborigami/origami/src/handlers/xhtml.handler.js
generated
vendored
Normal file
2
node_modules/@weborigami/origami/src/handlers/xhtml.handler.js
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
// .xhtml is a synonynm for .html
|
||||
export { default } from "./html.handler.js";
|
||||
36
node_modules/@weborigami/origami/src/handlers/yaml.handler.js
generated
vendored
Normal file
36
node_modules/@weborigami/origami/src/handlers/yaml.handler.js
generated
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
import { symbols } from "@weborigami/async-tree";
|
||||
import * as YAMLModule from "yaml";
|
||||
import { parseYaml } from "../common/serialize.js";
|
||||
import * as utilities from "../common/utilities.js";
|
||||
import { processUnpackedContent } from "../internal.js";
|
||||
|
||||
// See notes at serialize.js
|
||||
// @ts-ignore
|
||||
const YAML = YAMLModule.default ?? YAMLModule.YAML;
|
||||
|
||||
/**
|
||||
* A YAML file
|
||||
*
|
||||
* Unpacking a YAML file returns the parsed data.
|
||||
*
|
||||
*/
|
||||
export default {
|
||||
mediaType: "application/yaml",
|
||||
|
||||
/** @type {import("@weborigami/language").UnpackFunction} */
|
||||
unpack(packed, options = {}) {
|
||||
const parent = options.parent ?? null;
|
||||
const yaml = utilities.toString(packed);
|
||||
if (!yaml) {
|
||||
throw new Error("Tried to parse something as YAML but it wasn't text.");
|
||||
}
|
||||
const data = parseYaml(yaml);
|
||||
if (data && typeof data === "object" && Object.isExtensible(data)) {
|
||||
Object.defineProperty(data, symbols.deep, {
|
||||
enumerable: false,
|
||||
value: true,
|
||||
});
|
||||
}
|
||||
return processUnpackedContent(data, parent);
|
||||
},
|
||||
};
|
||||
2
node_modules/@weborigami/origami/src/handlers/yml.handler.js
generated
vendored
Normal file
2
node_modules/@weborigami/origami/src/handlers/yml.handler.js
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
// .yml is a synonym for .yaml
|
||||
export { yamlHandler as default } from "../internal.js";
|
||||
103
node_modules/@weborigami/origami/src/help/help.js
generated
vendored
Normal file
103
node_modules/@weborigami/origami/src/help/help.js
generated
vendored
Normal file
@@ -0,0 +1,103 @@
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "url";
|
||||
import YAML from "yaml";
|
||||
import assertTreeIsDefined from "../common/assertTreeIsDefined.js";
|
||||
import version from "../origami/version.js";
|
||||
|
||||
/**
|
||||
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
||||
*
|
||||
* @this {AsyncTree|null}
|
||||
* @param {string} [key]
|
||||
*/
|
||||
export default async function help(key) {
|
||||
assertTreeIsDefined(this, "help:");
|
||||
|
||||
const helpFilename = path.resolve(
|
||||
fileURLToPath(import.meta.url),
|
||||
"../help.yaml"
|
||||
);
|
||||
const helpYaml = await fs.readFile(helpFilename);
|
||||
const helpData = YAML.parse(String(helpYaml));
|
||||
|
||||
if (key === undefined) {
|
||||
// Show all namespace descriptions
|
||||
return namespaceDescriptions(helpData);
|
||||
}
|
||||
|
||||
// Try treating key as a namespace.
|
||||
const namespace = helpData[key];
|
||||
if (namespace) {
|
||||
return namespaceCommands(namespace, key);
|
||||
}
|
||||
|
||||
// Try treating key as a builtin command.
|
||||
for (const [namespace, { commands }] of Object.entries(helpData)) {
|
||||
if (commands && Object.hasOwn(commands, key)) {
|
||||
return commandDescription(commands[key], namespace, key);
|
||||
}
|
||||
}
|
||||
|
||||
return `help: "${key}" not found`;
|
||||
}
|
||||
|
||||
async function commandDescription(commandHelp, namespace, command) {
|
||||
const url = commandHelp.collection
|
||||
? `${namespace}.html`
|
||||
: `${namespace}/${command}.html`;
|
||||
const text = [
|
||||
"",
|
||||
formatCommandDescription(commandHelp, namespace, command),
|
||||
"",
|
||||
`For more information: https://weborigami.org/builtins/${url}`,
|
||||
];
|
||||
return text.join("\n");
|
||||
}
|
||||
|
||||
function formatCommandDescription(commandHelp, namespace, command) {
|
||||
const { args, description } = commandHelp;
|
||||
return ` ${namespace}:${command}${args ?? ""} - ${description}`;
|
||||
}
|
||||
|
||||
async function namespaceCommands(namespaceHelp, namespace) {
|
||||
const text = [];
|
||||
|
||||
const commands = namespaceHelp.commands;
|
||||
if (commands === undefined) {
|
||||
text.push(`"${namespace}" works like a protocol at the start of a path.`);
|
||||
} else {
|
||||
if (namespaceHelp.description) {
|
||||
const description = namespaceHelp.description;
|
||||
const lowercase = description[0].toLowerCase() + description.slice(1);
|
||||
text.push(
|
||||
`The "${namespace}" namespace contains commands to ${lowercase}.`
|
||||
);
|
||||
}
|
||||
text.push("");
|
||||
for (const [command, commandHelp] of Object.entries(commands)) {
|
||||
text.push(formatCommandDescription(commandHelp, namespace, command));
|
||||
}
|
||||
text.push("");
|
||||
}
|
||||
|
||||
const url = namespaceHelp.collection ? `${namespace}.html` : `${namespace}/`;
|
||||
text.push(`For more information: https://weborigami.org/builtins/${url}`);
|
||||
return text.join("\n");
|
||||
}
|
||||
|
||||
async function namespaceDescriptions(helpData) {
|
||||
const text = [
|
||||
`Origami ${version} has commands grouped into the following namespaces:\n`,
|
||||
];
|
||||
for (const [key, value] of Object.entries(helpData)) {
|
||||
const description = value.description;
|
||||
if (description) {
|
||||
text.push(` ${key}: ${description}`);
|
||||
}
|
||||
}
|
||||
text.push(
|
||||
`\nType "ori help:<namespace>" for more or visit https://weborigami.org/builtins`
|
||||
);
|
||||
return text.join("\n");
|
||||
}
|
||||
399
node_modules/@weborigami/origami/src/help/help.yaml
generated
vendored
Normal file
399
node_modules/@weborigami/origami/src/help/help.yaml
generated
vendored
Normal file
@@ -0,0 +1,399 @@
|
||||
dev:
|
||||
description: Develop and debug Origami projects
|
||||
commands:
|
||||
breakpoint:
|
||||
args: (a)
|
||||
description: Break into the JavaScript debugger, then return a
|
||||
changes:
|
||||
args: (old, new)
|
||||
description: Return a tree of changes
|
||||
debug:
|
||||
args: (tree)
|
||||
description: Add debug features to the tree
|
||||
explore:
|
||||
args: ()
|
||||
description: Explore the current scope [when run in browser]
|
||||
log:
|
||||
args: (a, message)
|
||||
description: Log message to the console and return a
|
||||
serve:
|
||||
args: (tree, port)
|
||||
description: Start a web server for the tree
|
||||
svg:
|
||||
args: (tree, options)
|
||||
description: Render a tree visually in SVG format
|
||||
watch:
|
||||
args: (tree, fn)
|
||||
description: Reevaluate fn when tree changes
|
||||
|
||||
explore:
|
||||
description: URL protocol to treat a website with JSON keys as a tree
|
||||
|
||||
files:
|
||||
description: URL protocol for file system folders and files
|
||||
|
||||
help:
|
||||
description: Get help on builtin namespaces and commands
|
||||
|
||||
http:
|
||||
description: URL protocol for web resources via HTTP
|
||||
|
||||
https:
|
||||
description: URL protocol for web resources via HTTPS
|
||||
|
||||
httpstree:
|
||||
description: URL protocol for a website tree via HTTPS
|
||||
|
||||
httptree:
|
||||
description: URL protocol for a website tree via HTTP
|
||||
|
||||
image:
|
||||
description: Format and resize images
|
||||
commands:
|
||||
format:
|
||||
args: (image, format, options)
|
||||
description: Return the image in a different format
|
||||
resize:
|
||||
args: (image, options)
|
||||
description: Resize the image
|
||||
|
||||
inherited:
|
||||
description: URL protocol to get an inherited value instead of a local one
|
||||
|
||||
js:
|
||||
description: JavaScript classes and functions
|
||||
collection: true
|
||||
commands:
|
||||
Array:
|
||||
description: JavaScript Array class
|
||||
BigInt:
|
||||
description: JavaScript BigInt class
|
||||
Boolean:
|
||||
description: JavaScript Boolean class
|
||||
Date:
|
||||
description: JavaScript Date class
|
||||
Error:
|
||||
description: JavaScript Error class
|
||||
Infinity:
|
||||
description: JavaScript Infinity constant
|
||||
Intl:
|
||||
description: JavaScript Intl object
|
||||
JSON:
|
||||
description: JavaScript JSON object
|
||||
Map:
|
||||
description: JavaScript Map class
|
||||
Math:
|
||||
description: JavaScript Math object
|
||||
NaN:
|
||||
description: JavaScript NaN constant
|
||||
Number:
|
||||
description: JavaScript Number class
|
||||
Object:
|
||||
description: JavaScript Object class
|
||||
RegExp:
|
||||
description: JavaScript RegExp class
|
||||
Set:
|
||||
description: JavaScript Set class
|
||||
String:
|
||||
description: JavaScript String class
|
||||
Symbol:
|
||||
description: JavaScript Symbol class
|
||||
decodeURI:
|
||||
description: JavaScript decodeURI function
|
||||
decodeURIComponent:
|
||||
description: JavaScript decodeURIComponent function
|
||||
encodeURI:
|
||||
description: JavaScript encodeURI function
|
||||
encodeURIComponent:
|
||||
description: JavaScript encodeURIComponent function
|
||||
"false":
|
||||
description: JavaScript false constant
|
||||
fetch:
|
||||
description: JavaScript fetch function
|
||||
isFinite:
|
||||
description: JavaScript isFinite function
|
||||
isNaN:
|
||||
description: JavaScript isNaN function
|
||||
"null":
|
||||
description: JavaScript null constant
|
||||
parseFloat:
|
||||
description: JavaScript parseFloat function
|
||||
parseInt:
|
||||
description: JavaScript parseInt function
|
||||
"true":
|
||||
description: JavaScript true constant
|
||||
undefined:
|
||||
description: JavaScript undefined constant
|
||||
|
||||
new:
|
||||
description: Create instances of JavaScript classes
|
||||
|
||||
node:
|
||||
description: Node.js classes and modules
|
||||
collection: true
|
||||
commands:
|
||||
Buffer:
|
||||
description: Node.js Buffer class
|
||||
path:
|
||||
description: Node.js path module
|
||||
process:
|
||||
description: Node.js process object
|
||||
url:
|
||||
description: Node.js URL module
|
||||
|
||||
origami:
|
||||
description: Perform general Origami language functions
|
||||
commands:
|
||||
basename:
|
||||
args: (key)
|
||||
description: Removes an extension from the key if present
|
||||
builtins:
|
||||
description: The set of installed builtin functions
|
||||
config:
|
||||
description: The current project's configuration
|
||||
extension:
|
||||
description: Helpers for working with file extensions
|
||||
json:
|
||||
args: (obj)
|
||||
description: Render the object in JSON format
|
||||
jsonParse:
|
||||
args: (text)
|
||||
description: Parse text as JSON
|
||||
naturalOrder:
|
||||
description: A comparison function for natural sort order
|
||||
once:
|
||||
args: (fn)
|
||||
description: Run the function only once, return the same result
|
||||
ori:
|
||||
args: (text)
|
||||
description: Evaluate the text as an Origami expression
|
||||
post:
|
||||
args: (url, data)
|
||||
description: POST the given data to the URL
|
||||
project:
|
||||
description: The root folder for the current Origami project
|
||||
regexMatch:
|
||||
args: (text, regex)
|
||||
description: Return matches of the regex in the text
|
||||
repeat:
|
||||
args: (n, obj)
|
||||
description: An array of n copies of the object
|
||||
shell:
|
||||
args: (text)
|
||||
description: Run the text as a shell command, return the output
|
||||
slash:
|
||||
description: Helpers for working with trailing slashes
|
||||
stdin:
|
||||
description: Returns the content of the standard input stream
|
||||
string:
|
||||
args: (obj)
|
||||
description: Coerce a buffer or document to a string
|
||||
toFunction:
|
||||
args: (obj)
|
||||
description: Coerce a tree or packed function definition to a function
|
||||
unpack:
|
||||
args: (buffer)
|
||||
description: Unpack the buffer into a usable form
|
||||
version:
|
||||
args: ()
|
||||
description: Return the version number of the Origami language
|
||||
yaml:
|
||||
args: (obj)
|
||||
description: Render the object in YAML format
|
||||
yamlParse:
|
||||
args: (text)
|
||||
description: Parse text as YAML
|
||||
|
||||
package:
|
||||
description: URL protocol for packages installed in node_modules
|
||||
|
||||
scope:
|
||||
description: URL protocol to explicitly reference a key in scope
|
||||
|
||||
site:
|
||||
description: Add common website features
|
||||
commands:
|
||||
audit:
|
||||
args: (tree)
|
||||
description: Identify broken internal links and references
|
||||
crawl:
|
||||
args: (tree, base)
|
||||
description: A tree of a site's discoverable resources
|
||||
index:
|
||||
args: (tree)
|
||||
description: A default index.html page for the tree
|
||||
jsonKeys:
|
||||
args: (tree)
|
||||
description: Add .keys.json files to a tree
|
||||
redirect:
|
||||
args: (url, options)
|
||||
description: Redirect to the given URL
|
||||
rss:
|
||||
args: (feed)
|
||||
description: Transforms a JSON Feed tree to RSS XML
|
||||
sitemap:
|
||||
args: (tree)
|
||||
description: Generate a sitemap for the tree
|
||||
slug:
|
||||
args: (text)
|
||||
description: A version of the text suitable for use in URLs
|
||||
static:
|
||||
args: (tree)
|
||||
description: Define common static files for the tree
|
||||
|
||||
text:
|
||||
description: Manipulate text
|
||||
commands:
|
||||
document:
|
||||
args: (text, [data])
|
||||
description: Create a document object with the text and data
|
||||
indent:
|
||||
description: Tagged template literal for normalizing indentation
|
||||
inline:
|
||||
args: (text)
|
||||
description: Inline Origami expressions found in the text
|
||||
mdHtml:
|
||||
args: (markdown)
|
||||
description: Render the markdown as HTML
|
||||
|
||||
tree:
|
||||
description: Work with trees
|
||||
commands:
|
||||
addNextPrevious:
|
||||
args: (tree)
|
||||
description: Add next/previous fields to the tree's values
|
||||
assign:
|
||||
args: (target, source)
|
||||
description: Apply key/values from source to target
|
||||
cache:
|
||||
args: (tree, [cache], [filter])
|
||||
description: Caches values from the tree
|
||||
clear:
|
||||
args: (tree)
|
||||
description: Remove all values from the tree
|
||||
concat:
|
||||
args: (...objs)
|
||||
description: Concatenate text and/or trees of text
|
||||
copy:
|
||||
args: (source, target)
|
||||
description: Copy the source tree to the target
|
||||
deepMap:
|
||||
args: (tree, options)
|
||||
description: Map the keys and values of a deep tree
|
||||
deepMerge:
|
||||
args: (...trees)
|
||||
description: Return a deeply-merged tree
|
||||
deepReverse:
|
||||
args: (tree)
|
||||
description: Reverse order of keys at all levels of the tree
|
||||
deepTake:
|
||||
args: (tree, n)
|
||||
description: The first n values from the deep tree
|
||||
deepValues:
|
||||
args: (tree)
|
||||
description: The in-order leaf values of the tree
|
||||
defineds:
|
||||
args: (tree)
|
||||
description: Only the defined values of the tree
|
||||
entries:
|
||||
args: (tree)
|
||||
description: The tree's [key, value] pairs
|
||||
first:
|
||||
args: (tree)
|
||||
description: The first value in the tree
|
||||
forEach:
|
||||
args: (tree, fn)
|
||||
description: Apply fn to each (value, key)
|
||||
from:
|
||||
args: (object, options)
|
||||
description: Create a tree from an object
|
||||
fromFn:
|
||||
args: (fn, [keys])
|
||||
description: A tree defined by a value function
|
||||
globs:
|
||||
args: (patterns)
|
||||
description: A tree whose keys can include wildcard patterns
|
||||
group:
|
||||
args: (tree, fn)
|
||||
description: A new tree with values grouped by the function
|
||||
has:
|
||||
args: (tree, key)
|
||||
description: True if key exists in tree
|
||||
inners:
|
||||
args: (tree)
|
||||
description: The tree's interior nodes
|
||||
isAsyncMutableTree:
|
||||
args: (object)
|
||||
description: True if object is an async mutable tree
|
||||
isAsyncTree:
|
||||
args: (object)
|
||||
description: True if object is an async tree
|
||||
isTraversable:
|
||||
args: (object)
|
||||
description: True if object is traversable
|
||||
isTreelike:
|
||||
args: (object)
|
||||
description: True if object can be coerced to a tree
|
||||
keys:
|
||||
args: (tree)
|
||||
description: The keys of the tree
|
||||
length:
|
||||
args: (tree)
|
||||
description: The tree's size (number of keys)
|
||||
map:
|
||||
args: (tree, options)
|
||||
description: Create a new tree by mapping keys and/or values
|
||||
mapReduce:
|
||||
args: (tree, valueFn, reduceFn)
|
||||
description: Map values and reduce them
|
||||
match:
|
||||
args: (pattern, fn, [keys])
|
||||
description: Matches simple patterns or regular expressions
|
||||
merge:
|
||||
args: (...trees)
|
||||
description: Return a new tree merging the given trees
|
||||
paginate:
|
||||
args: (tree, [n])
|
||||
description: Group the tree's values into fixed-size sets
|
||||
parent:
|
||||
args: (tree)
|
||||
description: The parent of the given tree node
|
||||
paths:
|
||||
args: (tree)
|
||||
description: Slash-separated paths for the tree's values
|
||||
plain:
|
||||
args: (tree)
|
||||
description: Render the tree as a plain JavaScript object
|
||||
remove:
|
||||
args: (tree, key)
|
||||
description: Remove the value for the key from tree
|
||||
reverse:
|
||||
args: (tree)
|
||||
description: Reverse the order of the tree's keys
|
||||
root:
|
||||
args: (tree)
|
||||
description: The root node of the given tree
|
||||
setDeep:
|
||||
args: (target, source)
|
||||
description: Applies the source tree to the target
|
||||
shuffle:
|
||||
args: (tree)
|
||||
description: Shuffle the keys of the tree
|
||||
sort:
|
||||
args: (tree, options)
|
||||
description: A new tree with its keys sorted
|
||||
take:
|
||||
args: (tree, n)
|
||||
description: The first n values in the tree
|
||||
traverse:
|
||||
args: (tree, ...keys)
|
||||
description: Return the value at the path of keys
|
||||
traverseOrThrow:
|
||||
args: (tree, ...keys)
|
||||
description: Return the value at the path of keys or throw
|
||||
traversePath:
|
||||
args: (tree, path)
|
||||
description: Traverse a slash-separated path
|
||||
values:
|
||||
args: (tree)
|
||||
description: The tree's values
|
||||
18
node_modules/@weborigami/origami/src/image/format.js
generated
vendored
Normal file
18
node_modules/@weborigami/origami/src/image/format.js
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
import assertTreeIsDefined from "../common/assertTreeIsDefined.js";
|
||||
import imageFormatFn from "./formatFn.js";
|
||||
|
||||
/**
|
||||
* Return the image in a different format.
|
||||
*
|
||||
* @this {import("@weborigami/types").AsyncTree|null}
|
||||
*
|
||||
* @this {import("@weborigami/types").AsyncTree|null}
|
||||
* @param {import("@weborigami/async-tree").Packed} input
|
||||
* @param {keyof import("sharp").FormatEnum|import("sharp").AvailableFormatInfo}
|
||||
* format
|
||||
* @param {any} options
|
||||
*/
|
||||
export default async function imageFormat(input, format, options) {
|
||||
assertTreeIsDefined(this, "image:format");
|
||||
return imageFormatFn.call(this, format, options)(input);
|
||||
}
|
||||
18
node_modules/@weborigami/origami/src/image/formatFn.js
generated
vendored
Normal file
18
node_modules/@weborigami/origami/src/image/formatFn.js
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
import sharp from "sharp";
|
||||
import assertTreeIsDefined from "../common/assertTreeIsDefined.js";
|
||||
|
||||
/**
|
||||
* Return a function that transforms an input image to a different format.
|
||||
*
|
||||
* @this {import("@weborigami/types").AsyncTree|null}
|
||||
* @param {keyof import("sharp").FormatEnum|import("sharp").AvailableFormatInfo}
|
||||
* format
|
||||
* @param {any} options
|
||||
*/
|
||||
export default function imageFormatFn(format, options) {
|
||||
assertTreeIsDefined(this, "image/formatFn");
|
||||
return (buffer) =>
|
||||
buffer instanceof Uint8Array || buffer instanceof ArrayBuffer
|
||||
? sharp(buffer).toFormat(format, options).toBuffer()
|
||||
: undefined;
|
||||
}
|
||||
2
node_modules/@weborigami/origami/src/image/image.js
generated
vendored
Normal file
2
node_modules/@weborigami/origami/src/image/image.js
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
export { default as format } from "./format.js";
|
||||
export { default as resize } from "./resize.js";
|
||||
14
node_modules/@weborigami/origami/src/image/resize.js
generated
vendored
Normal file
14
node_modules/@weborigami/origami/src/image/resize.js
generated
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
import assertTreeIsDefined from "../common/assertTreeIsDefined.js";
|
||||
import imageResizeFn from "./resizeFn.js";
|
||||
|
||||
/**
|
||||
* Resize an image.
|
||||
*
|
||||
* @this {import("@weborigami/types").AsyncTree|null}
|
||||
* @param {import("@weborigami/async-tree").Packed} input
|
||||
* @param {import("sharp").ResizeOptions} options
|
||||
*/
|
||||
export default async function resize(input, options) {
|
||||
assertTreeIsDefined(this, "image:resize");
|
||||
return imageResizeFn.call(this, options)(input);
|
||||
}
|
||||
17
node_modules/@weborigami/origami/src/image/resizeFn.js
generated
vendored
Normal file
17
node_modules/@weborigami/origami/src/image/resizeFn.js
generated
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
import sharp from "sharp";
|
||||
import assertTreeIsDefined from "../common/assertTreeIsDefined.js";
|
||||
|
||||
/**
|
||||
* Return a function that resizes an image.
|
||||
*
|
||||
* @this {import("@weborigami/types").AsyncTree|null}
|
||||
* @param {import("sharp").ResizeOptions} options
|
||||
*/
|
||||
export default function imageResizeFn(options) {
|
||||
assertTreeIsDefined(this, "image/resizeFn");
|
||||
// Include `rotate()` to auto-rotate according to EXIF data.
|
||||
return (buffer) =>
|
||||
buffer instanceof Uint8Array || buffer instanceof ArrayBuffer
|
||||
? sharp(buffer).rotate().resize(options).toBuffer()
|
||||
: undefined;
|
||||
}
|
||||
24
node_modules/@weborigami/origami/src/internal.js
generated
vendored
Normal file
24
node_modules/@weborigami/origami/src/internal.js
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
//
|
||||
// This library includes a number of modules with circular dependencies. This
|
||||
// module exists to explicitly set the loading order for those modules. To
|
||||
// enforce use of this loading order, other modules should only load the modules
|
||||
// below via this module.
|
||||
//
|
||||
// About this pattern:
|
||||
// https://medium.com/visual-development/how-to-fix-nasty-circular-dependency-issues-once-and-for-all-in-javascript-typescript-a04c987cf0de
|
||||
//
|
||||
// Note: to avoid having VS Code auto-sort the imports, keep lines between them.
|
||||
|
||||
export { default as jsHandler } from "./handlers/js.handler.js";
|
||||
|
||||
export { default as oriHandler } from "./handlers/ori.handler.js";
|
||||
|
||||
export { default as oridocumentHandler } from "./handlers/oridocument.handler.js";
|
||||
|
||||
export { default as processUnpackedContent } from "./common/processUnpackedContent.js";
|
||||
|
||||
export { default as wasmHandler } from "./handlers/wasm.handler.js";
|
||||
|
||||
export { default as yamlHandler } from "./handlers/yaml.handler.js";
|
||||
|
||||
export { default as builtinsTree } from "./builtinsTree.js";
|
||||
37
node_modules/@weborigami/origami/src/js.js
generated
vendored
Normal file
37
node_modules/@weborigami/origami/src/js.js
generated
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
async function fetchWrapper(resource, options) {
|
||||
const response = await fetch(resource, options);
|
||||
return response.ok ? await response.arrayBuffer() : undefined;
|
||||
}
|
||||
|
||||
export default {
|
||||
Array,
|
||||
BigInt,
|
||||
Boolean,
|
||||
Date,
|
||||
Error,
|
||||
Infinity,
|
||||
Intl,
|
||||
JSON,
|
||||
Map,
|
||||
Math,
|
||||
NaN,
|
||||
Number,
|
||||
Object,
|
||||
RegExp,
|
||||
Set,
|
||||
String,
|
||||
Symbol,
|
||||
decodeURI,
|
||||
decodeURIComponent,
|
||||
encodeURI,
|
||||
encodeURIComponent,
|
||||
false: false,
|
||||
fetch: fetchWrapper,
|
||||
isFinite,
|
||||
isNaN,
|
||||
null: null,
|
||||
parseFloat,
|
||||
parseInt,
|
||||
true: true,
|
||||
undefined: undefined,
|
||||
};
|
||||
22
node_modules/@weborigami/origami/src/node.js
generated
vendored
Normal file
22
node_modules/@weborigami/origami/src/node.js
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
import path from "node:path";
|
||||
import process from "node:process";
|
||||
import url from "node:url";
|
||||
|
||||
// Patch process.env to be a plain object. Among other things, this lets us dump
|
||||
// the complete environment to the terminal with `ori @node/process/env`.
|
||||
const patchedProcess = Object.create(null, {
|
||||
...Object.getOwnPropertyDescriptors(process),
|
||||
env: {
|
||||
enumerable: true,
|
||||
get: function () {
|
||||
return Object.assign({}, process.env);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export default {
|
||||
Buffer,
|
||||
path,
|
||||
process: patchedProcess,
|
||||
url,
|
||||
};
|
||||
6
node_modules/@weborigami/origami/src/origami/basename.js
generated
vendored
Normal file
6
node_modules/@weborigami/origami/src/origami/basename.js
generated
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
import { extension } from "@weborigami/async-tree";
|
||||
|
||||
export default function basename(key) {
|
||||
const ext = extension.extname(key);
|
||||
return ext ? key.slice(0, -ext.length) : key;
|
||||
}
|
||||
20
node_modules/@weborigami/origami/src/origami/config.js
generated
vendored
Normal file
20
node_modules/@weborigami/origami/src/origami/config.js
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
import project from "./project.js";
|
||||
|
||||
/**
|
||||
* Return the configuration for the current project.
|
||||
*
|
||||
* The configuration is the project's config.ori file (if defined in the project
|
||||
* root) plus the Origami builtins.
|
||||
*
|
||||
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
||||
*
|
||||
* @this {AsyncTree|null}
|
||||
* @param {any} [key]
|
||||
*/
|
||||
export default async function config(key) {
|
||||
const projectTree = await project.call(this);
|
||||
// HACK: We use specific knowledge of how @project returns a tree to get the
|
||||
// config. The config is always the parent of the project folder.
|
||||
const parent = projectTree.parent;
|
||||
return key === undefined ? parent : parent.get(key);
|
||||
}
|
||||
28
node_modules/@weborigami/origami/src/origami/json.js
generated
vendored
Normal file
28
node_modules/@weborigami/origami/src/origami/json.js
generated
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
/** @typedef {import("@weborigami/types").AsyncTree} AsyncTree */
|
||||
import { isUnpackable, toPlainValue } from "@weborigami/async-tree";
|
||||
import assertTreeIsDefined from "../common/assertTreeIsDefined.js";
|
||||
|
||||
/**
|
||||
* Render the given object in JSON format.
|
||||
*
|
||||
* @this {AsyncTree|null}
|
||||
* @param {any} [obj]
|
||||
*/
|
||||
export default async function json(obj) {
|
||||
assertTreeIsDefined(this, "origami:json");
|
||||
// A fragment of the logic from getTreeArgument.js
|
||||
if (arguments.length > 0 && obj === undefined) {
|
||||
throw new Error(
|
||||
"An Origami function was called with an initial argument, but its value is undefined."
|
||||
);
|
||||
}
|
||||
obj = obj ?? this;
|
||||
if (obj === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
if (isUnpackable(obj)) {
|
||||
obj = await obj.unpack();
|
||||
}
|
||||
const value = await toPlainValue(obj);
|
||||
return JSON.stringify(value, null, 2);
|
||||
}
|
||||
6
node_modules/@weborigami/origami/src/origami/jsonParse.js
generated
vendored
Normal file
6
node_modules/@weborigami/origami/src/origami/jsonParse.js
generated
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
import { toString } from "../common/utilities.js";
|
||||
|
||||
export default async function jsonParse(input) {
|
||||
const text = toString(input);
|
||||
return text ? JSON.parse(text) : undefined;
|
||||
}
|
||||
1
node_modules/@weborigami/origami/src/origami/naturalOrder.js
generated
vendored
Normal file
1
node_modules/@weborigami/origami/src/origami/naturalOrder.js
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export { naturalOrder as default } from "@weborigami/async-tree";
|
||||
18
node_modules/@weborigami/origami/src/origami/once.js
generated
vendored
Normal file
18
node_modules/@weborigami/origami/src/origami/once.js
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
import assertTreeIsDefined from "../common/assertTreeIsDefined.js";
|
||||
|
||||
const fnPromiseMap = new WeakMap();
|
||||
|
||||
/**
|
||||
* Evaluate the given function only once and cache the result.
|
||||
*
|
||||
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
||||
* @this {AsyncTree|null}
|
||||
* @param {Function} fn
|
||||
*/
|
||||
export default async function once(fn) {
|
||||
assertTreeIsDefined(this, "origami:once");
|
||||
if (!fnPromiseMap.has(fn)) {
|
||||
fnPromiseMap.set(fn, fn.call(this));
|
||||
}
|
||||
return fnPromiseMap.get(fn);
|
||||
}
|
||||
93
node_modules/@weborigami/origami/src/origami/ori.js
generated
vendored
Executable file
93
node_modules/@weborigami/origami/src/origami/ori.js
generated
vendored
Executable file
@@ -0,0 +1,93 @@
|
||||
import {
|
||||
Tree,
|
||||
getRealmObjectPrototype,
|
||||
toString,
|
||||
} from "@weborigami/async-tree";
|
||||
import { compile } from "@weborigami/language";
|
||||
import builtinsTree from "../builtinsTree.js";
|
||||
import assertTreeIsDefined from "../common/assertTreeIsDefined.js";
|
||||
import { toYaml } from "../common/serialize.js";
|
||||
|
||||
const TypedArray = Object.getPrototypeOf(Uint8Array);
|
||||
|
||||
/**
|
||||
* Parse an Origami expression, evaluate it in the context of a tree (provided
|
||||
* by `this`), and return the result as text.
|
||||
*
|
||||
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
||||
*
|
||||
* @this {AsyncTree|null}
|
||||
* @param {string} expression
|
||||
*/
|
||||
export default async function ori(
|
||||
expression,
|
||||
options = { formatResult: true }
|
||||
) {
|
||||
assertTreeIsDefined(this, "origami:ori");
|
||||
|
||||
// In case expression has come from a file, cast it to a string.
|
||||
expression = toString(expression);
|
||||
|
||||
// Run in the context of `this` if defined, otherwise use the builtins.
|
||||
const tree = this ?? builtinsTree;
|
||||
|
||||
// Compile the expression. Avoid caching scope references so that, e.g.,
|
||||
// passing a function to the `watch` builtin will always look the current
|
||||
// value of things in scope.
|
||||
const fn = compile.expression(expression, { scopeCaching: false });
|
||||
|
||||
// Execute
|
||||
let result = await fn.call(tree);
|
||||
|
||||
// If result was a function, execute it.
|
||||
if (typeof result === "function") {
|
||||
result = await result.call(tree);
|
||||
}
|
||||
|
||||
return options.formatResult ? await formatResult(result) : result;
|
||||
}
|
||||
|
||||
async function formatResult(result) {
|
||||
if (
|
||||
typeof result === "string" ||
|
||||
result instanceof ArrayBuffer ||
|
||||
result instanceof TypedArray
|
||||
) {
|
||||
// Use as is
|
||||
return result;
|
||||
}
|
||||
|
||||
/** @type {string|String|undefined} */
|
||||
let text;
|
||||
|
||||
// Does the result have a meaningful toString() method (and not the dumb
|
||||
// Object.toString)? Exception: if the result is an array, we'll use YAML
|
||||
// instead.
|
||||
if (!result) {
|
||||
// Return falsy values as is.
|
||||
text = result;
|
||||
} else if (
|
||||
!(result instanceof Array) &&
|
||||
(typeof result !== "object" ||
|
||||
result.toString !== getRealmObjectPrototype(result)?.toString)
|
||||
) {
|
||||
text = result.toString();
|
||||
} else if (typeof result === "object") {
|
||||
// Render YAML
|
||||
text = await toYaml(result);
|
||||
} else {
|
||||
// Use result itself.
|
||||
text = result;
|
||||
}
|
||||
|
||||
// If the result is treelike, attach it to the text output.
|
||||
if (Tree.isTreelike(result)) {
|
||||
if (typeof text === "string") {
|
||||
// @ts-ignore
|
||||
text = new String(text);
|
||||
}
|
||||
/** @type {any} */ (text).unpack = () => result;
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
29
node_modules/@weborigami/origami/src/origami/origami.js
generated
vendored
Normal file
29
node_modules/@weborigami/origami/src/origami/origami.js
generated
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
// Use a dynamic import to avoid circular dependencies
|
||||
export const builtins = import("../internal.js").then(
|
||||
(internal) => internal.builtinsTree
|
||||
);
|
||||
|
||||
export { extension } from "@weborigami/async-tree";
|
||||
export { toFunction } from "../common/utilities.js";
|
||||
|
||||
export { default as help } from "../help/help.js"; // Alias
|
||||
export { default as basename } from "./basename.js";
|
||||
export { default as config } from "./config.js";
|
||||
export { default as json } from "./json.js";
|
||||
export { default as jsonParse } from "./jsonParse.js";
|
||||
export { default as naturalOrder } from "./naturalOrder.js";
|
||||
export { default as once } from "./once.js";
|
||||
export { default as ori } from "./ori.js";
|
||||
export { default as pack } from "./pack.js";
|
||||
export { default as post } from "./post.js";
|
||||
export { default as project } from "./project.js";
|
||||
export { default as regexMatch } from "./regexMatch.js";
|
||||
export { default as repeat } from "./repeat.js";
|
||||
export { default as shell } from "./shell.js";
|
||||
export { default as slash } from "./slash.js";
|
||||
export { default as stdin } from "./stdin.js";
|
||||
export { default as string } from "./string.js";
|
||||
export { default as unpack } from "./unpack.js";
|
||||
export { default as version } from "./version.js";
|
||||
export { default as yaml } from "./yaml.js";
|
||||
export { default as yamlParse } from "./yamlParse.js";
|
||||
13
node_modules/@weborigami/origami/src/origami/pack.js
generated
vendored
Normal file
13
node_modules/@weborigami/origami/src/origami/pack.js
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
import assertTreeIsDefined from "../common/assertTreeIsDefined.js";
|
||||
|
||||
/**
|
||||
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
||||
*
|
||||
* @this {AsyncTree|null}
|
||||
* @param {any} obj
|
||||
* @returns
|
||||
*/
|
||||
export default function pack(obj) {
|
||||
assertTreeIsDefined(this, "origami:pack");
|
||||
return obj?.pack?.();
|
||||
}
|
||||
45
node_modules/@weborigami/origami/src/origami/post.js
generated
vendored
Normal file
45
node_modules/@weborigami/origami/src/origami/post.js
generated
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
import {
|
||||
isStringLike,
|
||||
isUnpackable,
|
||||
toPlainValue,
|
||||
toString,
|
||||
Tree,
|
||||
} from "@weborigami/async-tree";
|
||||
|
||||
/**
|
||||
* @this {import("@weborigami/types").AsyncTree|null}
|
||||
* @param {string} url
|
||||
* @param {any} data
|
||||
*/
|
||||
export default async function post(url, data) {
|
||||
let body;
|
||||
let headers;
|
||||
if (isUnpackable(data)) {
|
||||
data = await data.unpack();
|
||||
}
|
||||
if (Tree.isTreelike(data)) {
|
||||
const value = await toPlainValue(data);
|
||||
body = JSON.stringify(value);
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
};
|
||||
} else if (isStringLike(data)) {
|
||||
body = toString(data);
|
||||
headers = {
|
||||
"Content-Type": "text/plain",
|
||||
};
|
||||
} else {
|
||||
body = data;
|
||||
}
|
||||
const response = await fetch(url, {
|
||||
method: "POST",
|
||||
body,
|
||||
headers,
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(
|
||||
`Failed to POST to ${url}. Error ${response.status}: ${response.statusText}`
|
||||
);
|
||||
}
|
||||
return response.arrayBuffer();
|
||||
}
|
||||
99
node_modules/@weborigami/origami/src/origami/project.js
generated
vendored
Normal file
99
node_modules/@weborigami/origami/src/origami/project.js
generated
vendored
Normal file
@@ -0,0 +1,99 @@
|
||||
/** @typedef {import("@weborigami/types").AsyncTree} AsyncTree */
|
||||
import { Tree } from "@weborigami/async-tree";
|
||||
import { OrigamiFiles } from "@weborigami/language";
|
||||
import assertTreeIsDefined from "../common/assertTreeIsDefined.js";
|
||||
import { builtinsTree, oriHandler } from "../internal.js";
|
||||
|
||||
const configFileName = "config.ori";
|
||||
|
||||
/**
|
||||
* Return the tree for the current project's root folder.
|
||||
*
|
||||
* This searches the current directory and its ancestors for an Origami file
|
||||
* called `config.ori`. If an Origami configuration file is found, the
|
||||
* containing folder is considered to be the project root. This returns a tree
|
||||
* for that folder, with the exported configuration as the context for that
|
||||
* folder — that is, the tree exported by the configuration will be the scope.
|
||||
*
|
||||
* If no Origami configuration file is found, the current folder will be
|
||||
* returned as a tree, with the builtins as its parent.
|
||||
*
|
||||
* @this {AsyncTree|null}
|
||||
* @param {any} [key]
|
||||
*/
|
||||
export default async function project(key) {
|
||||
assertTreeIsDefined(this, "origami:project");
|
||||
|
||||
const dirname = process.cwd();
|
||||
const currentTree = new OrigamiFiles(dirname);
|
||||
currentTree.parent = builtinsTree;
|
||||
|
||||
// Search up the tree for the configuration file or package.json to determine
|
||||
// the project root.
|
||||
const configContainer =
|
||||
(await findAncestorFile(currentTree, configFileName)) ??
|
||||
(await findAncestorFile(currentTree, "package.json"));
|
||||
|
||||
let projectRoot;
|
||||
if (!configContainer) {
|
||||
// No configuration file or package.json found; use the current directory.
|
||||
projectRoot = currentTree;
|
||||
} else {
|
||||
// Load the configuration file if one exists.
|
||||
const buffer = await configContainer.get(configFileName);
|
||||
if (!buffer) {
|
||||
// Project root defined by package.json
|
||||
projectRoot = configContainer;
|
||||
} else {
|
||||
// Load Origami configuration file
|
||||
const config = await oriHandler.unpack(buffer, {
|
||||
key: configFileName,
|
||||
parent: configContainer,
|
||||
});
|
||||
if (!config) {
|
||||
const configPath = /** @type {any} */ (configContainer).path;
|
||||
throw new Error(
|
||||
`Couldn't load the Origami configuration in ${configPath}/${configFileName}`
|
||||
);
|
||||
}
|
||||
|
||||
// The config tree may refer to the container tree *and vice versa*. To
|
||||
// support this, we put the container in the tree twice. The chain will
|
||||
// be: projectRoot -> configTree -> configContainer -> builtins, where
|
||||
// the projectRoot and configContainer are the same folder.
|
||||
const configTree = Tree.from(config);
|
||||
projectRoot = new OrigamiFiles(configContainer.path);
|
||||
projectRoot.parent = configTree;
|
||||
configTree.parent = configContainer;
|
||||
configContainer.parent = builtinsTree;
|
||||
}
|
||||
}
|
||||
|
||||
return key === undefined ? projectRoot : projectRoot.get(key);
|
||||
}
|
||||
|
||||
// Return the first ancestor of the given tree that contains a file with the
|
||||
// given name.
|
||||
async function findAncestorFile(start, fileName) {
|
||||
let current = start;
|
||||
while (current) {
|
||||
const value = await current.get(fileName);
|
||||
if (value) {
|
||||
// Found the desired file; its container is the project root. Set the
|
||||
// parent to the builtins; in the context of this project, there's nothing
|
||||
// higher up.
|
||||
current.parent = builtinsTree;
|
||||
return current;
|
||||
}
|
||||
// Not found; try the parent.
|
||||
const parent = await current.get("..");
|
||||
if (
|
||||
!parent ||
|
||||
(parent.path && current.path && parent.path === current.path)
|
||||
) {
|
||||
break;
|
||||
}
|
||||
current = parent;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
5
node_modules/@weborigami/origami/src/origami/regexMatch.js
generated
vendored
Normal file
5
node_modules/@weborigami/origami/src/origami/regexMatch.js
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
import regexMatchFn from "./regexMatchFn.js";
|
||||
|
||||
export default function regexMatch(text, regex) {
|
||||
return regexMatchFn(regex)(text);
|
||||
}
|
||||
9
node_modules/@weborigami/origami/src/origami/regexMatchFn.js
generated
vendored
Normal file
9
node_modules/@weborigami/origami/src/origami/regexMatchFn.js
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
const parsers = {};
|
||||
|
||||
export default function regexParseFn(text) {
|
||||
if (!parsers[text]) {
|
||||
const regexp = new RegExp(text);
|
||||
parsers[text] = (input) => input.match(regexp)?.groups;
|
||||
}
|
||||
return parsers[text];
|
||||
}
|
||||
5
node_modules/@weborigami/origami/src/origami/repeat.js
generated
vendored
Normal file
5
node_modules/@weborigami/origami/src/origami/repeat.js
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
export default async function repeat(count, content) {
|
||||
const array = new Array(count);
|
||||
array.fill(content);
|
||||
return array;
|
||||
}
|
||||
13
node_modules/@weborigami/origami/src/origami/shell.js
generated
vendored
Normal file
13
node_modules/@weborigami/origami/src/origami/shell.js
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
import { exec as callbackExec } from "node:child_process";
|
||||
import util from "node:util";
|
||||
const exec = util.promisify(callbackExec);
|
||||
|
||||
export default async function shell(command) {
|
||||
try {
|
||||
const { stdout } = await exec(command);
|
||||
return stdout;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
1
node_modules/@weborigami/origami/src/origami/slash.js
generated
vendored
Normal file
1
node_modules/@weborigami/origami/src/origami/slash.js
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export { trailingSlash as default } from "@weborigami/async-tree";
|
||||
29
node_modules/@weborigami/origami/src/origami/stdin.js
generated
vendored
Normal file
29
node_modules/@weborigami/origami/src/origami/stdin.js
generated
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
import process from "node:process";
|
||||
|
||||
export default async function stdin() {
|
||||
return readAll(process.stdin);
|
||||
}
|
||||
|
||||
function readAll(readable) {
|
||||
return new Promise((resolve) => {
|
||||
const chunks = [];
|
||||
|
||||
readable.on("readable", () => {
|
||||
let chunk;
|
||||
while (null !== (chunk = readable.read())) {
|
||||
chunks.push(chunk);
|
||||
}
|
||||
});
|
||||
|
||||
readable.on("end", () => {
|
||||
const size = chunks.reduce((acc, chunk) => acc + chunk.length, 0);
|
||||
const buffer = new Uint8Array(size);
|
||||
let offset = 0;
|
||||
for (const chunk of chunks) {
|
||||
buffer.set(chunk, offset);
|
||||
offset += chunk.length;
|
||||
}
|
||||
resolve(buffer);
|
||||
});
|
||||
});
|
||||
}
|
||||
14
node_modules/@weborigami/origami/src/origami/string.js
generated
vendored
Normal file
14
node_modules/@weborigami/origami/src/origami/string.js
generated
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
import assertTreeIsDefined from "../common/assertTreeIsDefined.js";
|
||||
import { toString } from "../common/utilities.js";
|
||||
|
||||
/**
|
||||
* Convert an object to a string.
|
||||
*
|
||||
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
||||
* @this {AsyncTree|null}
|
||||
* @param {any} object
|
||||
*/
|
||||
export default function stringBuiltin(object) {
|
||||
assertTreeIsDefined(this, "origami:string");
|
||||
return toString(object);
|
||||
}
|
||||
14
node_modules/@weborigami/origami/src/origami/unpack.js
generated
vendored
Normal file
14
node_modules/@weborigami/origami/src/origami/unpack.js
generated
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
import assertTreeIsDefined from "../common/assertTreeIsDefined.js";
|
||||
|
||||
/**
|
||||
* Unpack a packed format like a Uint8Array or ArrayBuffer to a usable form like
|
||||
* text or a plain JavaScript object.
|
||||
*
|
||||
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
||||
* @this {AsyncTree|null}
|
||||
* @param {any} obj
|
||||
*/
|
||||
export default function unpack(obj) {
|
||||
assertTreeIsDefined(this, "origami:unpack");
|
||||
return obj?.unpack?.() ?? obj;
|
||||
}
|
||||
13
node_modules/@weborigami/origami/src/origami/version.js
generated
vendored
Normal file
13
node_modules/@weborigami/origami/src/origami/version.js
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
// When this is no longer experimental in Node:
|
||||
// import packageJson from "../../package.json" with { type: "json" };
|
||||
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const packageJsonPath = path.resolve(dirname, "../../package.json");
|
||||
const buffer = await fs.readFile(packageJsonPath);
|
||||
const data = JSON.parse(String(buffer));
|
||||
|
||||
export default data.version;
|
||||
29
node_modules/@weborigami/origami/src/origami/yaml.js
generated
vendored
Normal file
29
node_modules/@weborigami/origami/src/origami/yaml.js
generated
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
/** @typedef {import("@weborigami/types").AsyncTree} AsyncTree */
|
||||
import { isUnpackable, toPlainValue } from "@weborigami/async-tree";
|
||||
import YAML from "yaml";
|
||||
import assertTreeIsDefined from "../common/assertTreeIsDefined.js";
|
||||
|
||||
/**
|
||||
* Render the object as text in YAML format.
|
||||
*
|
||||
* @this {AsyncTree|null}
|
||||
* @param {any} [obj]
|
||||
*/
|
||||
export default async function toYaml(obj) {
|
||||
assertTreeIsDefined(this, "origami:yaml");
|
||||
// A fragment of the logic from getTreeArgument.js
|
||||
if (arguments.length > 0 && obj === undefined) {
|
||||
throw new Error(
|
||||
"An Origami function was called with an initial argument, but its value is undefined."
|
||||
);
|
||||
}
|
||||
obj = obj ?? this;
|
||||
if (obj === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
if (isUnpackable(obj)) {
|
||||
obj = await obj.unpack();
|
||||
}
|
||||
const value = await toPlainValue(obj);
|
||||
return YAML.stringify(value);
|
||||
}
|
||||
7
node_modules/@weborigami/origami/src/origami/yamlParse.js
generated
vendored
Normal file
7
node_modules/@weborigami/origami/src/origami/yamlParse.js
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
import * as serialize from "../common/serialize.js";
|
||||
import { toString } from "../common/utilities.js";
|
||||
|
||||
export default async function yamlParse(input) {
|
||||
const text = toString(input);
|
||||
return text ? serialize.parseYaml(text) : undefined;
|
||||
}
|
||||
19
node_modules/@weborigami/origami/src/protocols/explore.js
generated
vendored
Normal file
19
node_modules/@weborigami/origami/src/protocols/explore.js
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
import { ExplorableSiteTree } from "@weborigami/async-tree";
|
||||
import assertTreeIsDefined from "../common/assertTreeIsDefined.js";
|
||||
import constructSiteTree from "../common/constructSiteTree.js";
|
||||
|
||||
/**
|
||||
* A site tree with JSON Keys via HTTPS.
|
||||
*
|
||||
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
||||
* @typedef {import("@weborigami/async-tree").Treelike} Treelike
|
||||
* @typedef {import("../../index.ts").Invocable} Invocable
|
||||
*
|
||||
* @this {AsyncTree|null}
|
||||
* @param {string} host
|
||||
* @param {...string} keys
|
||||
*/
|
||||
export default function explore(host, ...keys) {
|
||||
assertTreeIsDefined(this, "explore:");
|
||||
return constructSiteTree("https:", ExplorableSiteTree, this, host, ...keys);
|
||||
}
|
||||
30
node_modules/@weborigami/origami/src/protocols/files.js
generated
vendored
Normal file
30
node_modules/@weborigami/origami/src/protocols/files.js
generated
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
import { OrigamiFiles } from "@weborigami/language";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import process from "node:process";
|
||||
import assertTreeIsDefined from "../common/assertTreeIsDefined.js";
|
||||
|
||||
/**
|
||||
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
||||
*
|
||||
* @this {AsyncTree|null}
|
||||
* @param {string[]} keys
|
||||
*/
|
||||
export default async function files(...keys) {
|
||||
assertTreeIsDefined(this, "files:");
|
||||
|
||||
// If path begins with `~`, treat it relative to the home directory.
|
||||
// Otherwise, treat it relative to the current working directory.
|
||||
let relativePath = keys.join(path.sep);
|
||||
let basePath;
|
||||
if (relativePath.startsWith("~")) {
|
||||
basePath = os.homedir();
|
||||
relativePath = relativePath.slice(2);
|
||||
} else {
|
||||
basePath = process.cwd();
|
||||
}
|
||||
const resolved = path.resolve(basePath, relativePath);
|
||||
|
||||
const result = new OrigamiFiles(resolved);
|
||||
return result;
|
||||
}
|
||||
18
node_modules/@weborigami/origami/src/protocols/http.js
generated
vendored
Normal file
18
node_modules/@weborigami/origami/src/protocols/http.js
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
import assertTreeIsDefined from "../common/assertTreeIsDefined.js";
|
||||
import constructHref from "../common/constructHref.js";
|
||||
import fetchAndHandleExtension from "../common/fetchAndHandleExtension.js";
|
||||
|
||||
/**
|
||||
* Retrieve the indicated web resource via HTTP.
|
||||
*
|
||||
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
||||
*
|
||||
* @this {AsyncTree|null}
|
||||
* @param {string} host
|
||||
* @param {...string} keys
|
||||
*/
|
||||
export default async function http(host, ...keys) {
|
||||
assertTreeIsDefined(this, "http:");
|
||||
const href = constructHref("http:", host, ...keys);
|
||||
return fetchAndHandleExtension.call(this, href);
|
||||
}
|
||||
18
node_modules/@weborigami/origami/src/protocols/https.js
generated
vendored
Normal file
18
node_modules/@weborigami/origami/src/protocols/https.js
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
import assertTreeIsDefined from "../common/assertTreeIsDefined.js";
|
||||
import constructHref from "../common/constructHref.js";
|
||||
import fetchAndHandleExtension from "../common/fetchAndHandleExtension.js";
|
||||
|
||||
/**
|
||||
* Retrieve the indicated web resource via HTTPS.
|
||||
*
|
||||
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
||||
*
|
||||
* @this {AsyncTree|null}
|
||||
* @param {string} host
|
||||
* @param {...string} keys
|
||||
*/
|
||||
export default async function https(host, ...keys) {
|
||||
assertTreeIsDefined(this, "https:");
|
||||
const href = constructHref("https:", host, ...keys);
|
||||
return fetchAndHandleExtension.call(this, href);
|
||||
}
|
||||
19
node_modules/@weborigami/origami/src/protocols/httpstree.js
generated
vendored
Normal file
19
node_modules/@weborigami/origami/src/protocols/httpstree.js
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
import { SiteTree } from "@weborigami/async-tree";
|
||||
import assertTreeIsDefined from "../common/assertTreeIsDefined.js";
|
||||
import constructSiteTree from "../common/constructSiteTree.js";
|
||||
|
||||
/**
|
||||
* Return a website tree via HTTPS.
|
||||
*
|
||||
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
||||
* @typedef {import("@weborigami/async-tree").Treelike} Treelike
|
||||
* @typedef {import("../../index.ts").Invocable} Invocable
|
||||
*
|
||||
* @this {AsyncTree|null}
|
||||
* @param {string} host
|
||||
* @param {...string} keys
|
||||
*/
|
||||
export default function httpstree(host, ...keys) {
|
||||
assertTreeIsDefined(this, "treehttps:");
|
||||
return constructSiteTree("https:", SiteTree, this, host, ...keys);
|
||||
}
|
||||
19
node_modules/@weborigami/origami/src/protocols/httptree.js
generated
vendored
Normal file
19
node_modules/@weborigami/origami/src/protocols/httptree.js
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
import { SiteTree } from "@weborigami/async-tree";
|
||||
import assertTreeIsDefined from "../common/assertTreeIsDefined.js";
|
||||
import constructSiteTree from "../common/constructSiteTree.js";
|
||||
|
||||
/**
|
||||
* Return a website tree via HTTP.
|
||||
*
|
||||
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
||||
* @typedef {import("@weborigami/async-tree").Treelike} Treelike
|
||||
* @typedef {import("../../index.ts").Invocable} Invocable
|
||||
*
|
||||
* @this {AsyncTree|null}
|
||||
* @param {string} host
|
||||
* @param {...string} keys
|
||||
*/
|
||||
export default function httptree(host, ...keys) {
|
||||
assertTreeIsDefined(this, "httptree:");
|
||||
return constructSiteTree("http:", SiteTree, this, host, ...keys);
|
||||
}
|
||||
18
node_modules/@weborigami/origami/src/protocols/inherited.js
generated
vendored
Normal file
18
node_modules/@weborigami/origami/src/protocols/inherited.js
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Tree } from "@weborigami/async-tree";
|
||||
import { ops } from "@weborigami/language";
|
||||
import assertTreeIsDefined from "../common/assertTreeIsDefined.js";
|
||||
|
||||
/**
|
||||
* Return the inherited value (if any) for the indicated key.
|
||||
*
|
||||
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
||||
*
|
||||
* @this {AsyncTree|null}
|
||||
* @param {string[]} keys
|
||||
*/
|
||||
export default async function inherited(...keys) {
|
||||
assertTreeIsDefined(this, "inherited:");
|
||||
const key = keys.shift();
|
||||
const value = await ops.inherited.call(this, key);
|
||||
return keys.length > 0 ? await Tree.traverse(value, ...keys) : value;
|
||||
}
|
||||
42
node_modules/@weborigami/origami/src/protocols/new.js
generated
vendored
Normal file
42
node_modules/@weborigami/origami/src/protocols/new.js
generated
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
import { isUnpackable, scope as scopeFn, Tree } from "@weborigami/async-tree";
|
||||
import assertTreeIsDefined from "../common/assertTreeIsDefined.js";
|
||||
|
||||
/**
|
||||
* Find the indicated class constructor in scope, then return a function which
|
||||
* invokes it with `new`.
|
||||
*
|
||||
* This can also take a single argument that is a class.
|
||||
*
|
||||
* @this {import("@weborigami/types").AsyncTree|null}
|
||||
* @param {...any} keys
|
||||
*/
|
||||
export default async function instantiate(...keys) {
|
||||
assertTreeIsDefined(this, "new:");
|
||||
let constructor;
|
||||
const scope = this ? scopeFn(this) : null;
|
||||
if (
|
||||
keys.length === 1 &&
|
||||
(typeof keys[0] === "object" || typeof keys[0] === "function")
|
||||
) {
|
||||
constructor = keys[0];
|
||||
} else if (scope) {
|
||||
constructor = await Tree.traverseOrThrow(scope, ...keys);
|
||||
} else {
|
||||
throw new TypeError(`new: The scope isn't defined.`);
|
||||
}
|
||||
if (isUnpackable(constructor)) {
|
||||
constructor = await constructor.unpack();
|
||||
}
|
||||
// Origami may pass `undefined` as the first argument to the constructor. We
|
||||
// don't pass that along, because constructors like `Date` don't like it.
|
||||
return (...args) => {
|
||||
const object =
|
||||
args.length === 1 && args[0] === undefined
|
||||
? new constructor()
|
||||
: new constructor(...args);
|
||||
if (Tree.isAsyncTree(object)) {
|
||||
object.parent = scope;
|
||||
}
|
||||
return object;
|
||||
};
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user