flowfig 0.11.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/verify.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { resolve } from 'node:path';
2
- import { edgeId, nodes, toBeat } from './model.js';
2
+ import { edgeId, nodes, scenes, toBeat } from './model.js';
3
3
  import { headingSlug, links } from './source.js';
4
4
  import { codeFile, isDefined, outside, readFile } from './code.js';
5
5
  import { edgeResult } from './edges.js';
@@ -31,6 +31,7 @@ export const coverageLine = (figure, c) => `${figure}: ${c.boxesDefined} of ${c.
31
31
  (c.found + c.notFound + c.unsure + c.notChecked
32
32
  ? `; edges: ${c.found} found, ${c.notFound} not found, ${c.unsure} unsure, ${c.notChecked} not checked`
33
33
  : '');
34
+ export const levelLines = (c) => (c.levels ?? []).map(({ path, coverage: l }) => `[${path}] ${l.boxesDefined} of ${l.boxes} parts defined; edges: ${l.found} found, ${l.notFound} not found, ${l.unsure} unsure, ${l.notChecked} not checked`);
34
35
  export const unsureLines = (c) => c.unsureEdges.map((e) => `unsure edge "${e.id}": ${e.reason}`);
35
36
  export const SPEC_MARK = '<metadata id="figure-spec">';
36
37
  export function detailFindings(fig, root, read = readFile) {
@@ -49,69 +50,116 @@ export function verifyReport(fig, { root = process.cwd(), read = readFile, cache
49
50
  const findings = [];
50
51
  const coverage = emptyCoverage();
51
52
  findings.push(...detailFindings(fig, root, read));
52
- const all = links(fig);
53
- if (!all.length)
53
+ const linked = links(fig);
54
+ if (!linked.length)
54
55
  return {
55
56
  findings: [...findings, { rule: 'no-source', severity: 'warning', ids: [], message: 'no box, edge or hop has a source' }],
56
57
  coverage,
57
58
  };
58
59
  const files = new Map();
59
- for (const l of all) {
60
- const full = resolve(root, l.path);
61
- if (!files.has(full))
62
- files.set(full, outside(root, l.path) ? null : read(full));
63
- const text = files.get(full);
64
- const before = findings.length;
65
- let doubt = false;
66
- const fail = (rule, what) => findings.push({ rule, severity: 'error', ids: [], message: `${l.owner} -> ${l.source}: ${what}` });
67
- if (text == null)
68
- fail('missing-file', 'file not found');
69
- else if (l.symbol) {
70
- const code = codeFile(root, l.path, read, cache);
71
- if (code) {
72
- const r = defines(root, code, l.symbol, read, cache);
73
- if (r === false)
74
- fail('missing-symbol', 'symbol not defined');
75
- doubt = r == null;
60
+ const levels = [];
61
+ const all = scenes(fig);
62
+ const byKey = new Map(all.map((s) => [s.path.join('>'), s]));
63
+ const sourceOf = new Map(all.flatMap((s) => nodes(s.props.layout).map((n) => [n.id, n.source])));
64
+ for (const scene of all) {
65
+ const level = emptyCoverage();
66
+ for (const l of linked.filter((x) => (x.level ?? '') === scene.path.join('>'))) {
67
+ const full = resolve(root, l.path);
68
+ if (!files.has(full))
69
+ files.set(full, outside(root, l.path) ? null : read(full));
70
+ const text = files.get(full);
71
+ const before = findings.length;
72
+ let doubt = false;
73
+ const fail = (rule, what) => findings.push({ rule, severity: 'error', ids: [], message: `${l.owner} -> ${l.source}: ${what}` });
74
+ if (text == null)
75
+ fail('missing-file', 'file not found');
76
+ else if (l.symbol) {
77
+ const code = codeFile(root, l.path, read, cache);
78
+ if (code) {
79
+ const r = defines(root, code, l.symbol, read, cache);
80
+ if (r === false)
81
+ fail('missing-symbol', 'symbol not defined');
82
+ doubt = r == null;
83
+ }
84
+ else if (!new RegExp(`(^|[^\\w$])${escape(l.symbol)}(?![\\w$])`).test(text) &&
85
+ !(/\.(md|markdown)$/i.test(l.path) && hasHeading(text, l.symbol)))
86
+ fail('missing-symbol', 'symbol not found');
87
+ }
88
+ if (l.owner.startsWith('box "') || l.owner.startsWith('part "')) {
89
+ level.boxes++;
90
+ if (findings.length === before && !doubt)
91
+ level.boxesDefined++;
76
92
  }
77
- else if (!new RegExp(`(^|[^\\w$])${escape(l.symbol)}(?![\\w$])`).test(text) &&
78
- !(/\.(md|markdown)$/i.test(l.path) && hasHeading(text, l.symbol)))
79
- fail('missing-symbol', 'symbol not found');
80
- }
81
- if (l.owner.startsWith('box "')) {
82
- coverage.boxes++;
83
- if (findings.length === before && !doubt)
84
- coverage.boxesDefined++;
85
- }
86
- }
87
- const source = new Map(nodes(fig.layout).map((n) => [n.id, n.source]));
88
- const check = (who, id, caller, callee, via) => {
89
- const { result, reason } = caller ? edgeResult(root, caller, callee, via, read, cache) : { result: 'not-checked', reason: '' };
90
- if (result === 'found')
91
- coverage.found++;
92
- else if (result === 'not-found') {
93
- coverage.notFound++;
94
- findings.push({ rule: 'edge-not-found', severity: 'warning', ids: [id], message: `${who}: ${reason}` });
95
- }
96
- else if (result === 'unsure') {
97
- coverage.unsure++;
98
- coverage.unsureEdges.push({ id, reason });
99
93
  }
100
- else
101
- coverage.notChecked++;
102
- };
103
- for (const e of fig.edges)
104
- check(`edge "${edgeId(e)}"`, edgeId(e), e.source ?? source.get(e.from), source.get(e.to), e.via);
105
- const byId = new Map(fig.edges.map((e) => [edgeId(e), e]));
106
- for (const step of fig.steps ?? [])
107
- for (const beat of step.flow)
108
- for (const h of toBeat(beat).hops) {
109
- const e = byId.get(h.edge);
110
- if (!e || (h.source == null && h.via == null))
111
- continue;
112
- const [a, b] = h.back ? [e.to, e.from] : [e.from, e.to];
113
- check(`hop on "${h.edge}"`, h.edge, h.source ?? (h.back ? undefined : e.source) ?? source.get(a), source.get(b), h.via);
94
+ const props = scene.props;
95
+ const source = new Map(nodes(props.layout).map((n) => [n.id, n.source]));
96
+ const call = (caller, callee, via) => caller ? edgeResult(root, caller, callee, via, read, cache) : { result: 'not-checked', reason: '' };
97
+ const ports = new Set(scene.ports.map((p) => p.id));
98
+ const edgeCall = (e) => {
99
+ const direct = call(e.source ?? source.get(e.from), source.get(e.to), e.via);
100
+ const into = ports.has(e.from);
101
+ if (direct.result === 'found' || e.source != null || e.via != null || into === ports.has(e.to))
102
+ return direct;
103
+ const n = into ? e.from : e.to;
104
+ const chains = [];
105
+ const inner = [];
106
+ let callee = source.get(into ? e.to : e.from);
107
+ for (let k = scene.path.length; k > 0 && byKey.get(scene.path.slice(0, k).join('>')).ports.some((p) => p.id === n); k--) {
108
+ const owner = sourceOf.get(scene.path[k - 1]);
109
+ inner.push([owner, callee]);
110
+ callee = owner;
111
+ chains.push([...inner, into ? [source.get(n), owner] : [owner, source.get(n)]].map(([a, b]) => call(a, b)));
114
112
  }
113
+ if (chains.some((c) => c.every((r) => r.result === 'found')))
114
+ return { result: 'found', reason: '' };
115
+ for (const r of ['not-found', 'not-checked'])
116
+ if (direct.result === r && chains.every((c) => c.some((x) => x.result === r)))
117
+ return direct;
118
+ const doubt = [direct, ...chains.flat()].find((r) => r.result === 'unsure');
119
+ return {
120
+ result: 'unsure',
121
+ reason: doubt?.reason ?? `${direct.reason}; the call through box "${scene.path.at(-1)}" is not checked`,
122
+ };
123
+ };
124
+ const check = (who, id, { result, reason }) => {
125
+ if (result === 'found')
126
+ level.found++;
127
+ else if (result === 'not-found') {
128
+ level.notFound++;
129
+ findings.push({
130
+ rule: 'edge-not-found',
131
+ severity: 'warning',
132
+ ids: [id],
133
+ message: `${who}${scene.line ? ` [${scene.line}]` : ''}: ${reason}`,
134
+ });
135
+ }
136
+ else if (result === 'unsure') {
137
+ level.unsure++;
138
+ level.unsureEdges.push({ id, reason });
139
+ }
140
+ else
141
+ level.notChecked++;
142
+ };
143
+ for (const e of props.edges)
144
+ check(`edge "${edgeId(e)}"`, edgeId(e), edgeCall(e));
145
+ const byId = new Map(props.edges.map((e) => [edgeId(e), e]));
146
+ for (const step of props.steps ?? [])
147
+ for (const beat of step.flow)
148
+ for (const h of toBeat(beat).hops) {
149
+ const e = byId.get(h.edge);
150
+ if (!e || (h.source == null && h.via == null))
151
+ continue;
152
+ const [a, b] = h.back ? [e.to, e.from] : [e.from, e.to];
153
+ check(`hop on "${h.edge}"`, h.edge, call(h.source ?? (h.back ? undefined : e.source) ?? source.get(a), source.get(b), h.via));
154
+ }
155
+ for (const k of ['boxes', 'boxesDefined', 'found', 'notFound', 'unsure', 'notChecked'])
156
+ coverage[k] += level[k];
157
+ coverage.unsureEdges.push(...level.unsureEdges);
158
+ if (scene.line)
159
+ levels.push({ path: scene.line, coverage: level });
160
+ }
161
+ if (levels.length)
162
+ coverage.levels = levels;
115
163
  return { findings, coverage };
116
164
  }
117
165
  export const verify = (fig, opts = {}) => verifyReport(fig, opts).findings;
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const VERSION = "0.11.0";
1
+ export declare const VERSION = "0.12.0";
package/dist/version.js CHANGED
@@ -1,2 +1,2 @@
1
1
  // The release list bumps this with package.json.
2
- export const VERSION = '0.11.0';
2
+ export const VERSION = '0.12.0';
@@ -0,0 +1,59 @@
1
+ import type { FigLook } from './model.ts';
2
+ export type Pt = [number, number];
3
+ export type P3 = [number, number, number];
4
+ export declare const U = 40, C: number, S: number;
5
+ export declare const P: (x: number, y: number, z?: number) => Pt;
6
+ export declare const BY = 1.6;
7
+ export declare const antenna: ([x, y, z]: P3) => string;
8
+ export declare function hull(points: Pt[]): Pt[];
9
+ export type Lot = {
10
+ x: number;
11
+ y: number;
12
+ hull: Pt[];
13
+ tag: Pt;
14
+ door: number;
15
+ depot: {
16
+ x: number;
17
+ y: number;
18
+ } | null;
19
+ roof: Pt | null;
20
+ };
21
+ export declare function street(looks: (FigLook | undefined)[], withAntenna: boolean[], pitch: number, step?: number): {
22
+ svg: string;
23
+ lots: Lot[];
24
+ bends: Pt[];
25
+ };
26
+ export declare const LANE = 6.6, TD = 0.78, BAR_Y = 5.7, GZ = 0.8, GL = 1.2;
27
+ export declare const BED_ROWS: number[];
28
+ export declare const INK: {
29
+ ink: string;
30
+ data: string;
31
+ fail: string;
32
+ muted: string;
33
+ };
34
+ export declare const UNIT: Record<'box' | 'barrel' | 'notes', string>;
35
+ export declare function truckArt(depot: {
36
+ x: number;
37
+ y: number;
38
+ }): {
39
+ door: string;
40
+ yard: string;
41
+ road: string;
42
+ slots: {
43
+ L: number[];
44
+ R: number[];
45
+ };
46
+ };
47
+ export declare const PHONE: {
48
+ svg: string;
49
+ w: number;
50
+ h: number;
51
+ };
52
+ export type Gate = {
53
+ px: number;
54
+ ex: number;
55
+ y: number;
56
+ z: number;
57
+ L: number;
58
+ };
59
+ export declare function gateArt(g: Gate): string;