fewrd 0.3.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,34 +1,32 @@
1
- import { type Fold } from './index.ts';
2
- import type { Book, Resolver } from './types.ts';
1
+ import { type Resolve } from './index.ts';
3
2
  export interface PlaygroundCase {
4
3
  name: string;
4
+ /** The key of the conf in `confs` this case is found with. */
5
+ conf: string;
5
6
  text: string;
7
+ /** The tags folded when the case is first shown, and the gist that fold should give. */
8
+ fold?: string[];
9
+ gist?: string;
6
10
  }
7
- interface CommonOptions {
8
- cases: PlaygroundCase[];
9
- /** Evaluated once at mount, against `cases`, to seed the fold control's initial selection. Never called again. */
10
- fold?: Fold;
11
+ export interface PlaygroundConf {
12
+ conf: unknown;
13
+ resolvers?: Readonly<Record<string, Resolve>>;
11
14
  }
12
- export type MountOptions = (CommonOptions & {
13
- book: Book;
14
- })
15
- /** Book data (e.g. parsed JSON) and its resolvers: the playground shows an editor and recompiles on every edit. */
16
- | (CommonOptions & {
17
- data: unknown;
18
- resolvers?: Readonly<Record<string, Resolver>>;
19
- /** Shows a save button: called with the editor's text once it parses as JSON. Reset then returns to what was saved. */
20
- save?: (json: string) => unknown;
21
- });
22
- /** Distinct entities across a book's recipes, in first-appearance order. */
23
- export declare function entities(book: Book): string[];
24
- /** Which entities a supplied fold would fold across the given cases, or none if no fold was supplied. */
25
- export declare function initialSelection(book: Book, cases: PlaygroundCase[], fold?: Fold): string[];
26
15
  /**
27
- * Mount fewrd's playground UI into `el`, rendering `options.cases` against
28
- * `options.book` — or against `options.data` compiled with
29
- * `options.resolvers`, editable live. Self-contained: injects the CSS it
30
- * needs, scoped to this mount so multiple mounts on one page never collide
31
- * and unrelated page content is never touched.
16
+ * Mount the playground on `el`: `confs` are named confs (data, typically JSON
17
+ * imports) with their resolvers, and every case says which one it uses. One case
18
+ * shows at a time, stepped with the buttons, the jump menu or the arrow keys; the
19
+ * editor shows the conf of the current case and recompiles it on every edit, so
20
+ * only that domain's cases change. Injects its own scoped styles; light and dark
21
+ * follow the OS unless the theme switch says otherwise.
22
+ *
23
+ * ponytail: the case is found again on every keystroke; debounce if a conf or a
24
+ * case ever makes typing lag. A character outside the BMP takes two string
25
+ * positions but one glyph, so bands after it drift by one `ch`; measure with
26
+ * Range rects if real subjects need it. A newline in a text is not handled. The
27
+ * tree is not keyboard-navigable: the bands are, and they light their tree rows.
32
28
  */
33
- export declare function mount(el: Element, options: MountOptions): void;
34
- export {};
29
+ export declare function mount(el: HTMLElement, options: {
30
+ confs: Readonly<Record<string, PlaygroundConf>>;
31
+ cases: readonly PlaygroundCase[];
32
+ }): void;
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "fewrd",
3
- "version": "0.3.0",
3
+ "version": "1.0.1",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
- "description": "Finds what recurs in a string, cuts it loose, and gets you the gist in a few words.",
6
+ "description": "Finds what recurs in a string, and lets a reader fold it away.",
7
7
  "keywords": [
8
8
  "text",
9
9
  "segmentation",
@@ -20,8 +20,7 @@
20
20
  "homepage": "https://github.com/egildo/fewrd#readme",
21
21
  "bugs": "https://github.com/egildo/fewrd/issues",
22
22
  "files": [
23
- "dist",
24
- "book.schema.json"
23
+ "dist"
25
24
  ],
26
25
  "exports": {
27
26
  ".": {
@@ -31,12 +30,12 @@
31
30
  "./playground": {
32
31
  "types": "./dist/types/playground.d.ts",
33
32
  "default": "./dist/playground.js"
34
- },
35
- "./book.schema.json": "./book.schema.json"
33
+ }
36
34
  },
37
35
  "scripts": {
38
36
  "test": "node --test \"test/*.test.ts\"",
39
37
  "typecheck": "tsc",
38
+ "bench": "node bench.ts",
40
39
  "build": "vite build --config vite.lib.ts && tsc -p tsconfig.build.json",
41
40
  "prepublishOnly": "npm run build",
42
41
  "dev": "vite playground --port 5577 --strictPort"
package/book.schema.json DELETED
@@ -1,62 +0,0 @@
1
- {
2
- "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "title": "fewrd book",
4
- "description": "A fewrd recipe book written as data. Compile it with compile(data, { resolvers }) from 'fewrd'.",
5
- "type": "object",
6
- "properties": {
7
- "$schema": {
8
- "type": "string",
9
- "description": "Where this schema is, e.g. ./node_modules/fewrd/book.schema.json. Ignored by compile."
10
- },
11
- "version": {
12
- "type": "string",
13
- "minLength": 1,
14
- "description": "Keys a cached reading. Change it whenever a change to the recipes can change output."
15
- },
16
- "defs": {
17
- "type": "object",
18
- "description": "Named pattern fragments: source only, no slashes, no flags. Reference one from any pattern or fragment as %{NAME}; it splices in as one unit.",
19
- "propertyNames": { "pattern": "^[A-Za-z_][A-Za-z0-9_]*$" },
20
- "additionalProperties": { "type": "string" }
21
- },
22
- "recipes": {
23
- "type": "array",
24
- "description": "Recipes in priority order.",
25
- "items": { "$ref": "#/$defs/recipe" }
26
- }
27
- },
28
- "required": ["version", "recipes"],
29
- "additionalProperties": false,
30
- "$defs": {
31
- "pattern": {
32
- "type": "string",
33
- "pattern": "^/.*/[dgimsuvy]*$",
34
- "description": "A regex as /source/flags, e.g. \"/C\\\\.?U\\\\.?P/iu\". %{NAME} splices in a fragment from defs."
35
- },
36
- "neighbour": {
37
- "type": "object",
38
- "properties": {
39
- "part": { "type": "string", "minLength": 1, "description": "The part name this neighbour attaches as, e.g. label, date, unit." },
40
- "rx": { "$ref": "#/$defs/pattern", "description": "Written plainly: the engine pins it to the anchor's edge." }
41
- },
42
- "required": ["part", "rx"],
43
- "additionalProperties": false
44
- },
45
- "recipe": {
46
- "type": "object",
47
- "properties": {
48
- "entity": { "type": "string", "minLength": 1, "description": "What this recipe recognises; mentions carry it." },
49
- "anchor": { "$ref": "#/$defs/pattern", "description": "The value, strict. Its part is value (lead on a rest recipe)." },
50
- "left": { "type": "array", "items": { "$ref": "#/$defs/neighbour" }, "description": "Neighbours tried leftward from the anchor, in priority order." },
51
- "right": { "type": "array", "items": { "$ref": "#/$defs/neighbour" }, "description": "Neighbours tried rightward from the anchor, in priority order." },
52
- "requires": { "type": "array", "items": { "type": "string" }, "description": "Parts that must attach, or the candidate is dropped." },
53
- "resolve": { "type": "string", "minLength": 1, "description": "Name of a resolver passed to compile: canonical value from the parts, or null (not this entity after all)." },
54
- "weak": { "type": "boolean", "description": "Fills only the gaps the strong recipes leave." },
55
- "rest": { "type": "boolean", "description": "The mention runs from its anchor to the end of its level, and what follows is read again inside it. Neighbours are ignored." },
56
- "glued": { "type": "boolean", "description": "The anchor may start or end inside a word." }
57
- },
58
- "required": ["entity", "anchor"],
59
- "additionalProperties": false
60
- }
61
- }
62
- }
@@ -1,26 +0,0 @@
1
- import type { Book, CompileError, CompileOptions } from './types.ts';
2
- /** The keys each object accepts and requires. `book.schema.json` must agree (tested). */
3
- export declare const KEYS: {
4
- book: {
5
- all: string[];
6
- required: string[];
7
- };
8
- recipe: {
9
- all: string[];
10
- required: string[];
11
- };
12
- neighbour: {
13
- all: string[];
14
- required: string[];
15
- };
16
- };
17
- /**
18
- * Compile a book written as data (typically `JSON.parse` output) into a Book.
19
- * `resolve` names are looked up in `options.resolvers`; nothing in `data` is
20
- * ever evaluated as code. A recipe with any error is left out; the others
21
- * keep their order.
22
- */
23
- export declare function compile(data: unknown, options?: CompileOptions): {
24
- book: Book;
25
- errors: CompileError[];
26
- };
@@ -1,3 +0,0 @@
1
- import type { Book, Cuts } from './types.ts';
2
- /** Read `text` with `book`: every mention, and the partition of the text into leaves. */
3
- export declare function read(text: string, book: Book): Cuts;
@@ -1,21 +0,0 @@
1
- import type { Cuts, Mention } from './types.ts';
2
- export type Fold = (mention: Mention, index: number) => boolean;
3
- /**
4
- * Which leaves the condensed view shows. With nothing folded, every leaf.
5
- *
6
- * - A mention folds when the policy says so, or when the mention holding it folds.
7
- * - A bracket pair folds when nothing between them survives.
8
- * - The separators between two surviving leaves survive untouched when no
9
- * fold fell among them; otherwise only the strongest one survives, and none
10
- * at an edge, after an opening bracket or before closing punctuation.
11
- */
12
- export declare function shown(cuts: Cuts, fold: Fold): boolean[];
13
- /** The condensed view as plain text. */
14
- export declare function gist(cuts: Cuts, fold: Fold): string;
15
- /**
16
- * Tagged HTML holding both views: every leaf is there, and what the condensed
17
- * view drops carries `data-fold` — so `.condensed [data-fold] { display: none }`
18
- * is the whole switch. Mentions are `<span data-entity data-mention>`, parts
19
- * `<span data-part>`, separators `<span data-sep>`, nested as the mentions nest.
20
- */
21
- export declare function html(cuts: Cuts, fold: Fold): string;
@@ -1,123 +0,0 @@
1
- /** Half-open, indices into the ORIGINAL string. */
2
- export interface Span {
3
- start: number;
4
- end: number;
5
- }
6
- /**
7
- * A neighbour a recipe may attach beside its anchor. Write `rx` plainly: the
8
- * engine pins it to the anchor's edge (`$` on the left, sticky on the right).
9
- */
10
- export interface Neighbour {
11
- part: string;
12
- rx: RegExp;
13
- }
14
- /**
15
- * One pattern: a strict anchor, then a closed list of neighbours tried outward.
16
- * Each neighbour attaches at most once; after every attachment the list is
17
- * tried again from the top, so declaration order is priority at each step.
18
- */
19
- export interface Recipe {
20
- entity: string;
21
- /** The value, strict. Its part is `value` (`lead` on a `rest` recipe). */
22
- anchor: RegExp;
23
- left?: readonly Neighbour[];
24
- right?: readonly Neighbour[];
25
- /** Parts that must attach, or the candidate is dropped. */
26
- requires?: readonly string[];
27
- /** Canonical value from the attached parts' text, or null: not this entity after all. */
28
- resolve?: (parts: Readonly<Record<string, string>>) => string | null;
29
- /** Fills only the gaps the strong recipes leave. */
30
- weak?: boolean;
31
- /**
32
- * The mention runs from its anchor to the end of its level, and what follows
33
- * the anchor is read again inside it. Neighbours are ignored.
34
- */
35
- rest?: boolean;
36
- /** The anchor may start or end inside a word. Default: it may not. */
37
- glued?: boolean;
38
- }
39
- /** Recipes in priority order, and the version that keys a cached reading. */
40
- export interface Book {
41
- version: string;
42
- recipes: readonly Recipe[];
43
- }
44
- /** A recipe's `resolve`, supplied by name at compile time. */
45
- export type Resolver = NonNullable<Recipe['resolve']>;
46
- /** A `Neighbour` as data. `rx` is a pattern: `"/source/flags"`. */
47
- export interface NeighbourData {
48
- part: string;
49
- rx: string;
50
- }
51
- /**
52
- * A `Recipe` as data. Patterns are `"/source/flags"` strings, where
53
- * `%{NAME}` splices in `BookData.defs.NAME` as one unit; `resolve` names a
54
- * function from `CompileOptions.resolvers`.
55
- */
56
- export interface RecipeData {
57
- entity: string;
58
- anchor: string;
59
- left?: NeighbourData[];
60
- right?: NeighbourData[];
61
- requires?: string[];
62
- resolve?: string;
63
- weak?: boolean;
64
- rest?: boolean;
65
- glued?: boolean;
66
- }
67
- /** A `Book` as data: the JSON a book is written in. */
68
- export interface BookData {
69
- $schema?: string;
70
- version: string;
71
- /** Name → pattern source (no slashes, no flags), referenced as `%{NAME}`. */
72
- defs?: Record<string, string>;
73
- recipes: RecipeData[];
74
- }
75
- /** One problem found by `compile`. `recipe` indexes `BookData.recipes`. */
76
- export interface CompileError {
77
- /** Where in the data, e.g. `recipes[2].left[0].rx`; `""` is the root. */
78
- path: string;
79
- recipe?: number;
80
- entity?: string;
81
- message: string;
82
- }
83
- export interface CompileOptions {
84
- resolvers?: Readonly<Record<string, Resolver>>;
85
- }
86
- export interface Mention {
87
- entity: string;
88
- /** Index into the book's recipes. */
89
- recipe: number;
90
- /** Every part, contiguous: what folding removes. */
91
- extent: Span;
92
- /** In text order. */
93
- parts: {
94
- part: string;
95
- span: Span;
96
- }[];
97
- /** `resolve`'s answer, or the anchor's text. */
98
- value: string;
99
- /** The `rest` mention this one sits inside. */
100
- parent?: number;
101
- }
102
- export type LeafKind = 'text' | 'sep' | 'open' | 'close' | 'part';
103
- /**
104
- * One piece of the partition. `mention` is the innermost mention holding it
105
- * (a `text` or `sep` leaf may sit inside a `rest` mention).
106
- */
107
- export interface Leaf {
108
- start: number;
109
- end: number;
110
- kind: LeafKind;
111
- part?: string;
112
- mention?: number;
113
- }
114
- /**
115
- * The cuts index: leaves partition `text` in order, no gap, no overlap —
116
- * concatenating their slices gives `text` back, character for character.
117
- */
118
- export interface Cuts {
119
- text: string;
120
- book: string;
121
- mentions: Mention[];
122
- leaves: Leaf[];
123
- }