reladraw 0.1.0 → 0.3.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/ast.d.ts CHANGED
@@ -7,13 +7,88 @@ export type Direction = (typeof DIRECTIONS)[number];
7
7
  export declare function isDirection(word: string): word is Direction;
8
8
  export type Axis = 'x' | 'y';
9
9
  /**
10
- * Which edge of the target a `level with` shares. `centre` is the plain form;
10
+ * Which side of the target a `level with` shares. `center` is the plain form;
11
11
  * the rest are written in front of it, as in `top level with media`.
12
12
  */
13
- export declare const EDGES: readonly ["centre", "top", "bottom", "left", "right"];
14
- export type Edge = (typeof EDGES)[number];
15
- /** An edge belongs to one axis, so an alignment never has to say which. */
16
- export declare const EDGE_AXIS: Record<Edge, Axis>;
13
+ export declare const SIDES: readonly ["center", "top", "bottom", "left", "right"];
14
+ export type Side = (typeof SIDES)[number];
15
+ /** A side belongs to one axis, so an alignment never has to say which. */
16
+ export declare const SIDE_AXIS: Record<Side, Axis>;
17
+ /**
18
+ * The nine points of a box anybody can name without measuring: the four
19
+ * corners, the four side midpoints, and the center. One closed set, accepted
20
+ * everywhere the language has a position — which is the rule that replaced a
21
+ * scatter of one-position slots, each decided on its own and each a little
22
+ * piece of the same expressiveness loss.
23
+ *
24
+ * Closed-and-meaningful is allowed where open-and-ordinal is not: these are
25
+ * words a reader decodes, and a diagram written in them still moves correctly
26
+ * when a box moves, which is the property the refusal of `x: 140` protects.
27
+ *
28
+ * A compound position is hyphenated and is one token, matching the diagonal
29
+ * directions above. A *phrase* of separate keywords stays spaced (`level
30
+ * with`); a compound *word* does not.
31
+ *
32
+ * Every midpoint carries `-center` rather than standing alone as `top` or
33
+ * `left`. Two reasons, and the second is the binding one. A side midpoint reads
34
+ * as "the bottom edge, centered along it", which is what the word says. And
35
+ * `top`, `bottom`, `left` and `right` already name a *side* in this language —
36
+ * an edge's `from:` and an alignment's `top level with` — so a bare `bottom`
37
+ * would mean a side in one place and a point in another. `from: bottom` spreads
38
+ * attachments along the side; `from: bottom-center` will pin one to the point.
39
+ */
40
+ export declare const POSITIONS: readonly ["top-left", "top-center", "top-right", "left-center", "center", "right-center", "bottom-left", "bottom-center", "bottom-right"];
41
+ export type Position = (typeof POSITIONS)[number];
42
+ export declare function isPosition(word: string): word is Position;
43
+ /**
44
+ * A part of a node that a placement may name: its text, one of its four sides,
45
+ * or one of its nine points. Written as a separate word after the node's name
46
+ * — `hub text`, `server right`, `board top-right`.
47
+ *
48
+ * No noun is carried. `board right side` was considered and dropped, because
49
+ * the language already tells a segment from a point by *spelling*: a bare
50
+ * `top` names a side everywhere (`from: bottom`, `top level with`) and a
51
+ * hyphenated `top-right` names a point, which is exactly why every midpoint
52
+ * carries `-center` rather than standing alone. A noun would mark with a word
53
+ * a distinction the hyphen already marks.
54
+ *
55
+ * The spaced form needs no reservation against a child called `text`: a
56
+ * sibling is always written dotted (`right of server.mirror`), so `hub text`
57
+ * spaced can never be `hub.text`. The dotted spelling was rejected because it
58
+ * interferes with the dot's one meaning.
59
+ */
60
+ export declare const PART_SIDES: readonly ["top", "bottom", "left", "right"];
61
+ export type PartSide = (typeof PART_SIDES)[number];
62
+ export declare const PARTS: readonly ["text", "top", "bottom", "left", "right", "top-left", "top-center", "top-right", "left-center", "center", "right-center", "bottom-left", "bottom-center", "bottom-right"];
63
+ export type Part = (typeof PARTS)[number];
64
+ export declare function isPart(word: string): word is Part;
65
+ /**
66
+ * Which way "toward the box's center" points from a part. This is the whole of
67
+ * what `inside` and `outside` mean, which is why neither needs a table of its
68
+ * own: `inside` is this direction, `outside` is its opposite, and one rule
69
+ * covers every part.
70
+ *
71
+ * `center` and `text` are absent on purpose. Neither is on the boundary, so
72
+ * there is no direction toward the interior from them, and both are refused by
73
+ * name where the shorthand is read.
74
+ */
75
+ export declare const INWARD: Partial<Record<Part, Direction>>;
76
+ /** The other way round, for `outside`. */
77
+ export declare const OPPOSITE: Record<Direction, Direction>;
78
+ /** The parts `inside` and `outside` can be read from — everything on the boundary. */
79
+ export declare const BOUNDARY_PARTS: readonly string[];
80
+ /**
81
+ * One thing a placement is placed against: a node, or a part of a node.
82
+ *
83
+ * A part target is the one target that is an *extent* rather than a node, which
84
+ * is what it buys over naming a sibling — a column of children against a
85
+ * container's right edge is flush by that edge and commits to no row, and
86
+ * nothing anchored to a single node can say that.
87
+ */
88
+ export interface PlacementTarget {
89
+ name: string;
90
+ part?: Part;
91
+ }
17
92
  /**
18
93
  * Naming more than one target places the node against the box that just bounds
19
94
  * them all — `right of borg and bare` clears both. It is a single target that
@@ -21,15 +96,23 @@ export declare const EDGE_AXIS: Record<Edge, Axis>;
21
96
  * several placements: two separate `level with` statements are two demands that
22
97
  * fight, while one naming two targets is a single demand about one region.
23
98
  */
24
- export type Targets = string[];
99
+ export type Targets = PlacementTarget[];
25
100
  /**
26
- * `right of docker` — the node sits a gap beyond one of the target's edges.
101
+ * `right of docker` — the node sits a gap beyond one of the target's sides.
27
102
  * A direction rules out part of an axis rather than fixing a point, which is
28
103
  * what lets two of them bracket a node between two targets.
29
104
  */
30
105
  export interface OffsetPlacement {
31
106
  kind: 'offset';
32
107
  direction: Direction;
108
+ /**
109
+ * `inside` or `outside`, where the author wrote one of those instead of a
110
+ * direction. Both are shorthands whose expansion is *derived* rather than
111
+ * listed — inside is the direction from the named part toward the box's
112
+ * center, outside is away from it — so the direction above is the whole of
113
+ * their meaning and this only remembers the word, to quote back.
114
+ */
115
+ written?: 'inside' | 'outside';
33
116
  targets: Targets;
34
117
  /**
35
118
  * The gap this one placement asks for, from `(gap: wide)` written after the
@@ -40,11 +123,31 @@ export interface OffsetPlacement {
40
123
  gap?: string;
41
124
  line: number;
42
125
  }
43
- /** `level with docker` — share an edge or a centre line, with no gap in between. */
126
+ /** `level with docker` — share a side or a center line, with no gap in between. */
44
127
  export interface AlignPlacement {
45
128
  kind: 'align';
46
129
  axis: Axis;
47
- edge: Edge;
130
+ side: Side;
131
+ targets: Targets;
132
+ line: number;
133
+ }
134
+ /**
135
+ * `on hub top-right` — the node's center sits at the part's center, so a node
136
+ * on a corner straddles it.
137
+ *
138
+ * This is the one genuine addition beside `inside` and `outside`, and it is a
139
+ * both-axes center alignment the language did not have: `level with` gives the
140
+ * vertical and the edge alignments give whichever axis their edge belongs to,
141
+ * and there is no horizontal center alignment at all — so the only long form
142
+ * would need two new words rather than one. `centered on` was proposed and
143
+ * rejected: beside `inside` and `outside` the three read as a series and `on`
144
+ * is unmistakable.
145
+ *
146
+ * One target only, unlike the other two. It names an exact point of one box,
147
+ * and the box that bounds two things is not a box anybody drew.
148
+ */
149
+ export interface OnPlacement {
150
+ kind: 'on';
48
151
  targets: Targets;
49
152
  line: number;
50
153
  }
@@ -52,19 +155,54 @@ export interface AlignPlacement {
52
155
  * One thing the author said about where a node goes. A node carries as many as
53
156
  * it needs; the resolver intersects them.
54
157
  */
55
- export type Placement = OffsetPlacement | AlignPlacement;
158
+ export type Placement = OffsetPlacement | AlignPlacement | OnPlacement;
56
159
  /**
57
160
  * The modifiers a placement understands, in brackets after its targets. Refused
58
- * by name when unrecognised, for the reason `DIAGRAM_KEYS` are: a modifier that
161
+ * by name when unrecognized, for the reason `DIAGRAM_KEYS` are: a modifier that
59
162
  * silently does nothing looks like a bug in the tool rather than a typo.
60
163
  */
61
164
  export declare const PLACEMENT_KEYS: readonly ["gap"];
165
+ /**
166
+ * `contents: (widths: match, align: center)` — how a container's children sit
167
+ * inside it. A container has a fill, a border, a title and its contents, and
168
+ * this is the one key that points at the last of them.
169
+ *
170
+ * Two properties, independent, which is why they are bracketed modifiers of one
171
+ * key rather than two keys side by side. The cross product has a live cell in
172
+ * every corner — *equal widths, ranged left, under a long title* is the case
173
+ * that proves it — so one token could never carry both. And they sit at
174
+ * different levels: `contents:` is a property of the node, `widths:` a property
175
+ * of the contents, which written as peers would read as two facts about the box
176
+ * when they are one about the box and one about its children.
177
+ *
178
+ * It replaced `align: widths`, which was a size operation wearing an
179
+ * alignment's name and had a one-element value set — a flag in a property's
180
+ * clothes. With it gone, `align` means one thing everywhere: how a text's lines
181
+ * range against each other.
182
+ */
183
+ export declare const CONTENTS_KEYS: readonly ["widths", "align"];
184
+ /**
185
+ * What `widths:` may say. `natural` is the default and is today's behavior.
186
+ * `match` is what `align: widths` did — every child as wide as the widest.
187
+ * `fill` is the whole content band, which is `match` wherever the contents set
188
+ * the container's width and the better answer wherever the title wins.
189
+ */
190
+ export declare const CONTENT_WIDTHS: readonly ["natural", "match", "fill"];
191
+ /**
192
+ * Where the block of contents sits when it is narrower than the band. `right`
193
+ * is accepted although no diagram has yet wanted it: refusing it would give
194
+ * `align:` a different value set depending on which bracket it is in, which is
195
+ * the divergence this scheme exists to remove.
196
+ */
197
+ export declare const CONTENT_ALIGNMENTS: readonly ["left", "center", "right"];
62
198
  /** How a placement reads back in the author's own words, for error messages. */
63
199
  export declare function describePlacement(placement: Placement): string;
64
- /** "borg", "borg and bare", "borg, bare and media" — as the author would write them. */
200
+ /** "borg", "hub text", "borg and bare" — as the author would write them. */
65
201
  export declare function listTargets(targets: Targets): string;
202
+ /** One target in the author's words: the node's name, and its part if it named one. */
203
+ export declare function nameTarget(target: PlacementTarget): string;
66
204
  /**
67
- * `between computer1 and computer2` on a link — the gap it passes through.
205
+ * `between desktop1 and laptop1` on an edge — the gap it passes through.
68
206
  *
69
207
  * This is not a claim about the whole line. It binds only the stretch where the
70
208
  * line is actually passing the pair, and says nothing about where it goes
@@ -87,53 +225,59 @@ export declare function describeAxis(axis: Axis): string;
87
225
  /** `key: value` pairs trailing a statement. Values are always strings here. */
88
226
  export type Attrs = Record<string, string>;
89
227
  /**
90
- * What a label's brackets may say: `"Docker" (at: bottom, align: centre)`.
228
+ * What a text's brackets may say: `"Docker" (at: bottom-center, color: muted)`.
91
229
  *
92
- * They are bracketed onto the label rather than written among the node's
230
+ * They are bracketed onto the text rather than written among the node's
93
231
  * attributes for the same reason a gap is bracketed onto its placement — they
94
232
  * modify that one thing, and the brackets make the scope visible instead of
95
- * positional. `at` and `align` are independent: neither implies the other, and a
96
- * label at the bottom is an ordinary label that happens to be at the bottom.
233
+ * positional. What is left at the top level is then about the node itself:
234
+ * `shape`, `icon`, `fill`, `border`, `gap`, `overlap`, `style`.
235
+ *
236
+ * In a style, which has no string for a bracket to hang off, the bracket hangs
237
+ * off the key instead: `style synced text: (color: muted)`.
238
+ *
239
+ * `at` and `align` are independent and neither implies the other. `at` is where
240
+ * the block of text sits in the node — one of the nine named positions — and
241
+ * `align` is how its lines range against each other once it is there.
97
242
  */
98
- export declare const LABEL_KEYS: readonly ["at", "align"];
99
- export interface BoxStmt {
100
- kind: 'box';
243
+ export declare const TEXT_KEYS: readonly ["color", "size", "wrap", "align", "at"];
244
+ export interface NodeStmt {
245
+ kind: 'node';
101
246
  name: string;
102
247
  text: string;
103
- /** The label's bracketed modifiers, as written. Usually empty. */
104
- label: Attrs;
248
+ /**
249
+ * Whether that text was written, or is the name standing in for it. A node
250
+ * drawn as a picture takes no such default — see `buildTree` — and only this
251
+ * flag can tell `node cube_a` from `node cube_a "cube_a"`.
252
+ */
253
+ statedText: boolean;
254
+ /** The text's bracketed modifiers, as written. Usually empty. */
255
+ textAttrs: Attrs;
105
256
  /** Everything the author said about where this goes. Empty for the anchor. */
106
257
  placements: Placement[];
107
258
  attrs: Attrs;
108
259
  line: number;
109
260
  }
110
- export interface LinkStmt {
111
- kind: 'link';
261
+ export interface EdgeStmt {
262
+ kind: 'edge';
112
263
  from: string;
113
264
  to: string;
114
265
  /** `<->` rather than `->`. */
115
266
  both: boolean;
116
- label?: string;
117
- /** `between computer1 and computer2` — the gap the line passes through. */
267
+ text?: string;
268
+ /** The text's bracketed modifiers, as written. Usually empty. */
269
+ textAttrs: Attrs;
270
+ /** `between desktop1 and laptop1` — the gap the line passes through. */
118
271
  between?: Passage;
119
272
  attrs: Attrs;
120
273
  line: number;
121
274
  }
122
- export interface NoteStmt {
123
- kind: 'note';
124
- name: string;
125
- text: string;
126
- /** Everything the author said about where this goes. Empty for the anchor. */
127
- placements: Placement[];
128
- attrs: Attrs;
129
- line: number;
130
- }
131
275
  export interface DeckStmt {
132
276
  kind: 'deck';
133
277
  /** The container to draw with offset copies behind it. */
134
278
  name: string;
135
- /** One label per copy, back to front as written. */
136
- labels: string[];
279
+ /** One text per copy, back to front as written. */
280
+ texts: string[];
137
281
  line: number;
138
282
  }
139
283
  /**
@@ -148,13 +292,88 @@ export interface DiagramStmt {
148
292
  }
149
293
  /** The attributes a `diagram` statement understands. */
150
294
  export declare const DIAGRAM_KEYS: readonly ["background"];
295
+ /**
296
+ * The attributes whose value is a color rather than text. A color is written
297
+ * as the viewer will receive it and the renderer keeps no list of color words
298
+ * of its own, so there is nothing to check a value *against* — but quoting is
299
+ * the author saying "this is text", and an unquoted value cannot hold a space,
300
+ * so prose has to be quoted to get in at all. Refusing a quoted color is
301
+ * therefore the whole of what can be checked here, and it happens to be the
302
+ * mistake people actually make: `subtext: "medium-fine"` reads as the text
303
+ * that goes underneath, and was accepted and dropped in silence.
304
+ */
305
+ export declare const COLOR_KEYS: readonly ["fill", "border", "line", "background"];
306
+ /**
307
+ * A color attribute names the *part* it colors, and a part exists only on the
308
+ * kinds that have one. A node has a border and text; a note and a glyph body are
309
+ * text and nothing else; an edge is a line and its text.
310
+ *
311
+ * This table is what makes the words checkable. `border:` on a note is refused
312
+ * by name rather than ignored — the same rule as an unknown `diagram` key, and
313
+ * for the same reason: an attribute that silently does nothing looks like the
314
+ * tool being broken.
315
+ *
316
+ * Each entry is written the way the author would write it, since the text's is
317
+ * a bracket rather than a bare key, and this list is only ever quoted back.
318
+ *
319
+ * A style spanning kinds writes one key per kind — `border: #d2904e line:
320
+ * #d2904e` — since a style contributes a part only to the kinds that have it.
321
+ * That is what replaced `stroke:`, which named no part and so could never be
322
+ * wrong, and which is why a node's text had no word of its own until now.
323
+ */
324
+ export declare const COLOR_PARTS: Record<Kind, readonly string[]>;
325
+ /**
326
+ * The four things an attribute can be written on. Three of them are nodes, and
327
+ * which one a node is, is what its body says: `shape:` draws an outline,
328
+ * `icon:` draws a picture, and `shape: none` draws neither. None of the three
329
+ * is a statement keyword — a node is a node — but each takes a different set of
330
+ * attributes, which is what makes it a kind here.
331
+ */
332
+ export type Kind = 'shape' | 'icon' | 'none' | 'edge';
333
+ /**
334
+ * Every attribute each kind understands. An attribute a kind has no use for is
335
+ * refused by name rather than dropped, the same rule as an unknown `diagram`
336
+ * key, a `PLACEMENT_KEYS` modifier or a color part — and for the same reason,
337
+ * which the color parts only closed one level down: a key that silently does
338
+ * nothing looks like the tool being broken rather than like a typo.
339
+ *
340
+ * The color entries repeat `COLOR_PARTS` and must agree with it. They are
341
+ * written out rather than spliced in because this table is the answer to "what
342
+ * may I write here", and a reader of it should not have to assemble the list
343
+ * from two places.
344
+ *
345
+ * The exclusions are the whole of what this table decides, and each is a place
346
+ * the old silence hid something:
347
+ *
348
+ * - A node drawn as a picture, or with no body at all, takes no `fill:` or
349
+ * `border:`. There is no outline for either to reach.
350
+ * - Neither of those takes `contents:` either, which says how a node's children
351
+ * sit, and neither may have any.
352
+ * - `shape:` and `icon:` each name the body, so each appears only on the kind it
353
+ * makes. `shape:` is on `none` as well, because `shape: none` is how that kind
354
+ * is written in the first place.
355
+ * - An edge takes no `gap:` or `overlap:`. Those are about where a box sits, and
356
+ * an edge is not placed — it joins two things that are.
357
+ */
358
+ export declare const ATTR_KEYS: Record<Kind, readonly string[]>;
359
+ /**
360
+ * Every word that is an attribute *somewhere*, which is what separates a
361
+ * misspelling from a key written on the wrong kind of thing. The two deserve
362
+ * different errors: one has no remedy but the spelling, the other has a real
363
+ * meaning somewhere else in the file.
364
+ *
365
+ * `DIAGRAM_KEYS` is in here so that `background:` on a node is understood to be
366
+ * a real word in the wrong place — that mistake wants to be pointed at `fill:`,
367
+ * not told the word does not exist.
368
+ */
369
+ export declare const ALL_ATTR_KEYS: readonly string[];
151
370
  export interface StyleStmt {
152
371
  kind: 'style';
153
372
  name: string;
154
373
  attrs: Attrs;
155
374
  line: number;
156
375
  }
157
- export type Stmt = BoxStmt | LinkStmt | NoteStmt | DeckStmt | StyleStmt | DiagramStmt;
376
+ export type Stmt = NodeStmt | EdgeStmt | DeckStmt | StyleStmt | DiagramStmt;
158
377
  export interface Document {
159
378
  statements: Stmt[];
160
379
  }