reladraw 0.1.0 → 0.2.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/README.md +25 -23
- package/SYNTAX.md +137 -54
- package/dist/ast.d.ts +73 -5
- package/dist/ast.js +99 -6
- package/dist/constants.d.ts +8 -5
- package/dist/constants.js +14 -12
- package/dist/grammar.d.ts +7 -7
- package/dist/grammar.js +9 -9
- package/dist/icons.d.ts +3 -3
- package/dist/icons.js +2 -2
- package/dist/lexer.d.ts +1 -1
- package/dist/lexer.js +1 -1
- package/dist/parser.js +47 -6
- package/dist/render.js +97 -81
- package/dist/resolve.js +144 -26
- package/package.json +1 -1
package/SYNTAX.md
CHANGED
|
@@ -10,7 +10,7 @@ One statement per line. No multi-line statements, no line continuations, no bloc
|
|
|
10
10
|
|
|
11
11
|
Blank lines and `//` comments are ignored. A comment runs to the end of the line and may trail a statement, so `box a "Docker" // the one that matters` is fine. Indentation is ignored entirely — a formatter may add it for readability, and stale indentation cannot change what a file means.
|
|
12
12
|
|
|
13
|
-
A lone `/` is an ordinary character rather than the start of a comment, and `#` is ordinary too — it opens a hex
|
|
13
|
+
A lone `/` is an ordinary character rather than the start of a comment, and `#` is ordinary too — it opens a hex color. Comments were spelled `#` in an earlier version and moved to `//` so that `fill: #14532d` could be written the way every other tool writes a color.
|
|
14
14
|
|
|
15
15
|
A statement is a positional head followed by optional `key: value` attributes. Attributes begin at the first token ending in a colon, which is the only rule a parser needs to tell the two apart. Everything positional therefore comes first — name, text and placements, in that order — and once an attribute has appeared nothing positional may follow it.
|
|
16
16
|
|
|
@@ -33,7 +33,7 @@ box <name> ["<text>"] [<placement> ...] [attributes]
|
|
|
33
33
|
|
|
34
34
|
`name` identifies the node and must be unique. `text` is what appears inside it, with ` / ` — a slash with whitespace on both sides — marking a line break.
|
|
35
35
|
|
|
36
|
-
The text is optional, and a box without it is
|
|
36
|
+
The text is optional, and a box without it is labeled with its own name:
|
|
37
37
|
|
|
38
38
|
```
|
|
39
39
|
box parser
|
|
@@ -50,11 +50,13 @@ The whitespace is part of the marker, not decoration. A slash inside a word is a
|
|
|
50
50
|
|
|
51
51
|
For a label that wants a spaced slash and no break, `\/` escapes it: `"Before \/ After"` is one line. The escapes are `\"`, `\\` and `\/`.
|
|
52
52
|
|
|
53
|
-
`
|
|
53
|
+
`wrap: <n>` folds the text at word boundaries every `n` characters, on top of whatever ` / ` already breaks. It is how you make a block of text narrow and tall so it can sit snugly beside something, rather than wide and short so it cannot.
|
|
54
54
|
|
|
55
|
-
|
|
55
|
+
It was called `width` until it was renamed, and the old name is now an error naming the new one. `width` was wrong in the way this project's naming rule catches: it says how wide something is, and this says nothing of the sort — `width: 200` meaning units was accepted, folded at two hundred characters, and did nothing visible. The reference had to carry a sentence explaining that the number was not a distance, which is the tell that a name is doing the wrong job.
|
|
56
56
|
|
|
57
|
-
|
|
57
|
+
`size: small | normal | large` sets how big the text is, and works on a box, a note or a link label. The sizes are named for the reason gaps are named: a number would be typography by coordinate, stale the moment the document is set at another size, and silent about *why* one piece of text is smaller than another. An unrecognized value is an error naming it.
|
|
58
|
+
|
|
59
|
+
Every kind of text has a default, and `size:` overrides it exactly as `fill:` overrides the theme's color. Only `note` defaults to anything other than `normal`, and it defaults to `small`.
|
|
58
60
|
|
|
59
61
|
Containment is a dotted name. A node named `server.docker` is inside `server`. The parent must be declared before the child.
|
|
60
62
|
|
|
@@ -66,10 +68,10 @@ box server.deploy "services deploy dir"
|
|
|
66
68
|
|
|
67
69
|
A container is sized by its contents. Children stack vertically in written order unless a child carries a placement of its own.
|
|
68
70
|
|
|
69
|
-
A container with empty text and no fill or
|
|
71
|
+
A container with empty text and no fill or border takes up no room of its own and draws nothing. It exists so that everything inside it can be placed against as a single shape:
|
|
70
72
|
|
|
71
73
|
```
|
|
72
|
-
style invisible fill: none
|
|
74
|
+
style invisible fill: none border: none
|
|
73
75
|
|
|
74
76
|
box hub "" style: invisible
|
|
75
77
|
box hub.dropbox "Dropbox"
|
|
@@ -83,16 +85,18 @@ Here the server clears the whole cluster. Placed `left of hub.dropbox` instead,
|
|
|
83
85
|
A container's label can say where in the box it goes, in brackets on the label itself:
|
|
84
86
|
|
|
85
87
|
```
|
|
86
|
-
box docker "Docker" (at: bottom, align:
|
|
88
|
+
box docker "Docker" (at: bottom, align: center) below deploy
|
|
87
89
|
```
|
|
88
90
|
|
|
89
|
-
`at: top | bottom` says which end of the box the label sits at; the contents take the other end. `align: left |
|
|
91
|
+
`at: top | bottom` says which end of the box the label sits at; the contents take the other end. `align: left | center | right` says how the text sits across it. The defaults are `top` and `left` in a container, and the comma is optional punctuation.
|
|
90
92
|
|
|
91
93
|
The two are independent and neither implies the other. `(at: bottom)` on its own is an ordinary label that happens to be at the bottom.
|
|
92
94
|
|
|
93
95
|
They are bracketed onto the label rather than written among the node's attributes for the same reason [a gap is bracketed onto its placement](#a-gap-belongs-to-the-placement): they modify that one thing, and the brackets make the scope visible instead of leaving it to be inferred from what happens to sit nearby.
|
|
94
96
|
|
|
95
|
-
A leaf's label is
|
|
97
|
+
A leaf has no contents, so there is no band and nothing for `at` to be at either end of; writing it on a childless box is an error. `align` is fine there, and its default is `center` rather than `left`, because a leaf's label is centered in its box. It is worth having: any label of more than one line — from a ` / ` break or from `wrap:` — has lines of unequal length, and how those sit across each other is a real question in a leaf as much as in a container.
|
|
98
|
+
|
|
99
|
+
Both are refused on a note, which has no box at all.
|
|
96
100
|
|
|
97
101
|
`align: widths` on a container widens every direct child to match the widest of them, so a stack of boxes with labels of different lengths draws as a column with one edge rather than a ragged one. It is the only value the key accepts; anything else is an error. Widths are the only thing it touches — it never moves a child.
|
|
98
102
|
|
|
@@ -116,16 +120,16 @@ A leaf's label is centred in its box with nothing to sit clear of, so it takes n
|
|
|
116
120
|
box ext_hd "External HD" icon: disk
|
|
117
121
|
```
|
|
118
122
|
|
|
119
|
-
A name says what the thing *is*, never what the picture looks like, for the same reason `gap: wide` beats `gap: 110`: naming the meaning is what lets the drawing be improved later without every diagram that uses it changing sense. An
|
|
123
|
+
A name says what the thing *is*, never what the picture looks like, for the same reason `gap: wide` beats `gap: 110`: naming the meaning is what lets the drawing be improved later without every diagram that uses it changing sense. An unrecognized name is an error listing the whole set, rather than a box that quietly draws no icon — you would go looking for the mistake in the wrong place.
|
|
120
124
|
|
|
121
125
|
The set is small on purpose, and it is not the same trade a drawing tool makes. There you pick a shape out of a visual palette and hundreds are browsable; here you type the word from memory, which caps the useful vocabulary at what fits in a head. Adding your own is not possible yet — see "Not built yet".
|
|
122
126
|
|
|
123
|
-
The glyph is two lines of the label tall, so it follows `size:` down and up with the text, and it sits at the top of a container beside the title and
|
|
127
|
+
The glyph is two lines of the label tall, so it follows `size:` down and up with the text, and it sits at the top of a container beside the title and centered in a leaf beside the label. There is nothing to write about where it goes or how big it is. It takes a column of its own, so the box grows to hold the label and the icon side by side and one never runs under the other.
|
|
124
128
|
|
|
125
129
|
`icon` is appearance, so a style can carry one and every store in a diagram then looks alike without the word being written more than once:
|
|
126
130
|
|
|
127
131
|
```
|
|
128
|
-
style store fill: #142814
|
|
132
|
+
style store fill: #142814 border: #486544 icon: database
|
|
129
133
|
box records "Records" style: store
|
|
130
134
|
```
|
|
131
135
|
|
|
@@ -144,12 +148,12 @@ box svc "" shape: instance
|
|
|
144
148
|
|
|
145
149
|
`box` is the plain rectangle and is what you get by saying nothing. `document` is the same box with its top-right corner folded — the flowchart symbol saying *this is an artifact, not a process*.
|
|
146
150
|
|
|
147
|
-
That distinction is worth having because it is a second channel alongside
|
|
151
|
+
That distinction is worth having because it is a second channel alongside color, and a stronger one. A fill means whatever you assigned it, and a reader has to learn it from the diagram; a folded corner has meant "a document" for as long as there have been flowcharts, and reads with no legend. Most diagrams lose the difference between a thing that runs and a thing that is produced, because every node is a rectangle.
|
|
148
152
|
|
|
149
153
|
Any icon name is also a shape, and then the node *is* the glyph: no outline, no fill, no padding, and its size is the picture's rather than its label's. A label goes underneath it.
|
|
150
154
|
|
|
151
155
|
```
|
|
152
|
-
box services "" fill: none
|
|
156
|
+
box services "" fill: none border: none
|
|
153
157
|
box services.web "web" shape: instance
|
|
154
158
|
box services.api "api" right of services.web gap: tight shape: instance
|
|
155
159
|
```
|
|
@@ -158,7 +162,7 @@ box services.api "api" right of services.web gap: tight shape: instance
|
|
|
158
162
|
|
|
159
163
|
A glyph body is an ordinary node in every other way. It takes placements, it takes links and sides, other boxes keep clear of it. That is why it exists rather than being an icon: an icon cannot be the end of an arrow. It cannot contain anything, though, and a glyph with children is an error — a picture is not a box.
|
|
160
164
|
|
|
161
|
-
Shapes are named for what a node is, never for the geometry: `document`, not `folded-corner`. Same rule as the icon names, and for the same reason. An
|
|
165
|
+
Shapes are named for what a node is, never for the geometry: `document`, not `folded-corner`. Same rule as the icon names, and for the same reason. An unrecognized name is an error listing what is available.
|
|
162
166
|
|
|
163
167
|
The fold is a fixed size rather than a fraction of the box, so it looks the same on a narrow node and a wide one. Sizing it proportionally is what makes it vanish on a long label.
|
|
164
168
|
|
|
@@ -197,13 +201,13 @@ Nothing states how far apart `hub` and `side` are. They start one gap apart, and
|
|
|
197
201
|
|
|
198
202
|
This is the step you would otherwise do by hand: shove two things apart to make room, then drag everything back together so the diagram isn't full of holes. The file states the relationships and the distances fall out.
|
|
199
203
|
|
|
200
|
-
**A lone directional placement still sets both axes.** `right of docker` on its own also
|
|
204
|
+
**A lone directional placement still sets both axes.** `right of docker` on its own also centers the node vertically on Docker, because walking right from something keeps you on its center line. That half is dropped as soon as another placement binds the axis, so it never fights anything you wrote.
|
|
201
205
|
|
|
202
|
-
**Two placements on one axis and nothing on the other is an error.** `right of docker left of macbook` with no vertical placement would have to choose between Docker's
|
|
206
|
+
**Two placements on one axis and nothing on the other is an error.** `right of docker left of macbook` with no vertical placement would have to choose between Docker's center line and the Macbook's, and that choice decides which row of the diagram the node shares. The tool refuses and names the axis you left unstated. Add `level with docker`, or `below` something, and it resolves.
|
|
203
207
|
|
|
204
208
|
**Placements that cannot all hold are an error naming them,** rather than a picture with one box on top of another. Because gaps are minimums, this only happens when something is pinned exactly — by `level with`, or by a loop of placements that each demand more room than the last.
|
|
205
209
|
|
|
206
|
-
`level with X` is the one placement that fixes a distance outright: share a
|
|
210
|
+
`level with X` is the one placement that fixes a distance outright: share a center line, no gap involved. It binds the vertical only.
|
|
207
211
|
|
|
208
212
|
```
|
|
209
213
|
box dumps "" right of server.docker left of dropbox_and_machines level with server.docker
|
|
@@ -211,7 +215,7 @@ box dumps "" right of server.docker left of dropbox_and_machines level with s
|
|
|
211
215
|
|
|
212
216
|
Horizontally between two different targets, vertically level with a third. No single relation can say that, which is why a node can carry several.
|
|
213
217
|
|
|
214
|
-
Naming an edge in front of it aligns that edge instead of the
|
|
218
|
+
Naming an edge in front of it aligns that edge instead of the center. `top level with media` puts the node's top edge on the media box's top edge; `bottom`, `left` and `right` work the same way. `top` and `bottom` bind the vertical, `left` and `right` the horizontal — so `left level with X` and `left of X` are different statements, and the word after `left` is what tells them apart.
|
|
215
219
|
|
|
216
220
|
### Several targets at once
|
|
217
221
|
|
|
@@ -222,11 +226,11 @@ note rotations "Weekly rotations …" right of bup_hd.borg and bup_hd.bare gap
|
|
|
222
226
|
note archive "archive remains …" left of bup_hd.archive and bup_hd.par2 gap: tight
|
|
223
227
|
```
|
|
224
228
|
|
|
225
|
-
Neither note says anything about its own vertical position, and neither needs to. A lone directional placement
|
|
229
|
+
Neither note says anything about its own vertical position, and neither needs to. A lone directional placement centers on what it names, and what these name is the region covering two boxes, so each note lands centered on the pair it explains.
|
|
226
230
|
|
|
227
|
-
This is why it is one placement with two targets rather than two placements. Two separate `level with` statements are two demands that both have to hold, and boxes at different heights cannot both share a
|
|
231
|
+
This is why it is one placement with two targets rather than two placements. Two separate `level with` statements are two demands that both have to hold, and boxes at different heights cannot both share a center line with the same node, so that combination is a contradiction. One statement naming two targets is a single demand about a single region.
|
|
228
232
|
|
|
229
|
-
Combined with the rule that a lone directional placement also binds the other axis, this is how you offset one row against another. `below a and b` reads as "under the pair,
|
|
233
|
+
Combined with the rule that a lone directional placement also binds the other axis, this is how you offset one row against another. `below a and b` reads as "under the pair, centered between them", because the direction binds the vertical against the region and the horizontal falls on the region's center line:
|
|
230
234
|
|
|
231
235
|
```
|
|
232
236
|
box a "" shape: instance
|
|
@@ -236,7 +240,7 @@ box d "" below a and b gap: tight shape: instance
|
|
|
236
240
|
box e "" below b and c gap: tight shape: instance
|
|
237
241
|
```
|
|
238
242
|
|
|
239
|
-
Three above, two below, each sitting in the gap between two of them. Adding a second placement to bind the horizontal is what would left-justify the lower row instead — the
|
|
243
|
+
Three above, two below, each sitting in the gap between two of them. Adding a second placement to bind the horizontal is what would left-justify the lower row instead — the centering is dropped as soon as anything else claims that axis.
|
|
240
244
|
|
|
241
245
|
For a direction the region is a floor, so `right of one and three` clears whichever of them sticks out furthest, and it works wherever the targets sit. For an alignment the region is an exact position, and there is one restriction worth knowing: it must not depend on the node being aligned to it. Aligning a note to the region covering `A` and `B` while `B` is placed relative to that same note is refused by name, because there is no order in which each could wait for the other.
|
|
242
246
|
|
|
@@ -285,7 +289,7 @@ Every axis is solved as one system, so a target does not have to come first. Wha
|
|
|
285
289
|
|
|
286
290
|
You never have to say that two boxes must not sit on top of each other. Every pair carries that already, and `overlap: allow` on either one is the opt-out for the rare case where one is meant to cover another. A container never counts as overlapping its own contents.
|
|
287
291
|
|
|
288
|
-
**The tool never picks which way to separate two boxes.** It reads the direction off the arrangement you already stated. Say `A` is left of `B`, put `x` between them, and hang a wide box below `x`: because `x` is right of `A` and the wide box is
|
|
292
|
+
**The tool never picks which way to separate two boxes.** It reads the direction off the arrangement you already stated. Say `A` is left of `B`, put `x` between them, and hang a wide box below `x`: because `x` is right of `A` and the wide box is centered under `x`, the file lets the wide box travel rightward away from `A` and offers no way back. So the only separation it allows is `A` moving further left. Nothing is chosen. Where nothing in the file orders a pair on either axis, the tool refuses and names the pair rather than guessing — which is what happens if you hang two boxes off the same side of the same target and expect them to sort themselves out.
|
|
289
293
|
|
|
290
294
|
**One place the tool decides something you didn't write.** Sometimes both axes already imply an order. The wide box is rightward of `A` and also below it, so the overlap clears either by pushing `A` and `B` apart or by dropping the wide box lower, and both satisfy everything you wrote. The rule is to **separate along the axis where the two boxes overlap least**, which is also the smallest movement, and which matches what a person does by hand.
|
|
291
295
|
|
|
@@ -303,13 +307,13 @@ link <from> <-> <to> ["<label>"] [between <a> and <b> [vertically|horizontally]]
|
|
|
303
307
|
|
|
304
308
|
`a <- b` is exactly `b -> a` — same arrow, same picture. What changes is which name you write first, and that is worth having: the first name reads as the subject of the line, and plenty of links are about the thing the arrow points at rather than the thing it leaves. `from:` and `to:` follow the arrow, not the writing order, so they still name the tail and the head.
|
|
305
309
|
|
|
306
|
-
Endpoints may be nested (`computer1.files`). A link never says where a box goes and routing is the renderer's problem, with one exception: a
|
|
310
|
+
Endpoints may be nested (`computer1.files`). A link never says where a box goes and routing is the renderer's problem, with one exception: a labeled link claims room in the gap it crosses, which is the next section.
|
|
307
311
|
|
|
308
|
-
A link's label breaks on ` / ` exactly as a node's does, and the block
|
|
312
|
+
A link's label breaks on ` / ` exactly as a node's does, and the block centers on the point the label would otherwise have occupied, so ``"run `deploy` / shell command"`` stacks its two lines around the midpoint of the line rather than running off along it. `wrap:` is a node attribute and does not apply — a link label folds where you say and nowhere else.
|
|
309
313
|
|
|
310
314
|
### A label makes room for itself
|
|
311
315
|
|
|
312
|
-
Putting something between two boxes is what pushes them apart, and a label drawn in a corridor is something in that corridor. So a
|
|
316
|
+
Putting something between two boxes is what pushes them apart, and a label drawn in a corridor is something in that corridor. So a labeled link widens the gap it crosses by what its label needs — the label, a run of line either side of it, and the arrowhead that covers part of that run — and by no more than that.
|
|
313
317
|
|
|
314
318
|
```
|
|
315
319
|
box parser "Parser"
|
|
@@ -319,9 +323,9 @@ link parser -> resolver "statements" from: right to: left
|
|
|
319
323
|
|
|
320
324
|
Nothing there says how far apart those two boxes are. The default gap is sized for two boxes to breathe rather than to hold a word, so without this the label would be drawn across both of them. Delete the label and the gap closes back to the default. Write `gap: wide` on that placement and nothing further happens, because the minimum you asked for is already the larger of the two — a gap is a minimum, and a label is one more thing bidding into it.
|
|
321
325
|
|
|
322
|
-
Which gap the label lands in is derived, never stated. Two boxes clear of each other on exactly one axis have exactly one corridor between them, and that is the one that widens. Two sitting corner to corner have no single corridor, because the line runs diagonally through open space, so nothing is widened for them. The room is measured along the run: a link
|
|
326
|
+
Which gap the label lands in is derived, never stated. Two boxes clear of each other on exactly one axis have exactly one corridor between them, and that is the one that widens. Two sitting corner to corner have no single corridor, because the line runs diagonally through open space, so nothing is widened for them. The room is measured along the run: a link traveling horizontally needs the label's width, one traveling vertically needs only its depth, so a long label across a vertical gap opens it by a single line and hangs out either side.
|
|
323
327
|
|
|
324
|
-
An
|
|
328
|
+
An unlabeled link asks for nothing, since every gap is wide enough for an arrowhead. A link carrying a `between` clause asks for nothing here either — its label rides in the channel it named rather than in the gap between its own two ends, and what that does *not* do yet is at the end of the next section but one.
|
|
325
329
|
|
|
326
330
|
### Which side a link leaves and arrives on
|
|
327
331
|
|
|
@@ -331,9 +335,9 @@ An unlabelled link asks for nothing, since every gap is wide enough for an arrow
|
|
|
331
335
|
link computer1.files <-> dropbox from: right to: top
|
|
332
336
|
```
|
|
333
337
|
|
|
334
|
-
That line leaves the right side of `computer1.files` heading right, and arrives at the top of `dropbox` heading down. Naming a side is a statement about how the line should leave or arrive, so the link is drawn as a curve that actually does. A link naming neither side stays the straight
|
|
338
|
+
That line leaves the right side of `computer1.files` heading right, and arrives at the top of `dropbox` heading down. Naming a side is a statement about how the line should leave or arrive, so the link is drawn as a curve that actually does. A link naming neither side stays the straight center-to-center line it has always been. Either end may be named on its own; the unnamed one aims at wherever its partner ended up.
|
|
335
339
|
|
|
336
|
-
You name a side and never a point on it. Alone on a side, a link lands at its
|
|
340
|
+
You name a side and never a point on it. Alone on a side, a link lands at its center. Sharing a side with other links, the attachments space themselves apart, and which one goes where is derived from where the far ends actually sit — of two links arriving at one top edge, the one coming from further left arrives further left. Move a box and the order follows it. This is the same rule as boxes not overlapping: the tool separates things by default and reads the direction off the solved layout rather than asking you.
|
|
337
341
|
|
|
338
342
|
The space it leaves is deliberately small, and shrinks further if the side is too short to hold the whole group. On a side short enough, it shrinks to nothing and the attachments coincide. Their labels still come apart, because the lines bow in the middle to make up what the edge could not give them, but the arrowheads themselves land on one point and nothing warns you — so a small box with several links arriving on one edge is worth a look.
|
|
339
343
|
|
|
@@ -356,7 +360,7 @@ A side too short to hold the whole group is squeezed, exactly as above — and t
|
|
|
356
360
|
|
|
357
361
|
#### Several links with no side named at all
|
|
358
362
|
|
|
359
|
-
Links between the same two boxes that name no side anywhere have the same problem in a harder form: an unnamed end has no side to be spread along. It aims at the far box's
|
|
363
|
+
Links between the same two boxes that name no side anywhere have the same problem in a harder form: an unnamed end has no side to be spread along. It aims at the far box's center and attaches wherever that ray crosses the border, so every link between one pair produces the same point, and three of them come out as one visible line with three labels stacked on it.
|
|
360
364
|
|
|
361
365
|
```
|
|
362
366
|
box a
|
|
@@ -398,7 +402,7 @@ link c -> d "threaded" between a and b vertically
|
|
|
398
402
|
|
|
399
403
|
Leave it out on a diagonal pair and the error asks for it, in your own node names. Write it where it was not needed and it is checked rather than quietly dropped, so a pair that is only apart vertically will tell you that `horizontally` is wrong. A pair that touches or overlaps has no gap at all, whatever you write, and naming a pair the link never actually passes is an error too.
|
|
400
404
|
|
|
401
|
-
Several links may share one channel, and they take a lane each. As with attachments on a side, which link gets which lane is derived from where their ends sit, so lines through a channel come out in the order their ends are in and do not cross. The lanes are spaced by what is actually running along them: a label's depth where a
|
|
405
|
+
Several links may share one channel, and they take a lane each. As with attachments on a side, which link gets which lane is derived from where their ends sit, so lines through a channel come out in the order their ends are in and do not cross. The lanes are spaced by what is actually running along them: a label's depth where a labeled link runs, an arrow's width where none does.
|
|
402
406
|
|
|
403
407
|
A named channel does not widen. It is measured off the layout you described, so if you name a gap too narrow for the lines you put through it they crowd together rather than pushing the two boxes apart. That is the difference between this and a label making room for itself, above: there, the corridor is the gap between the link's own two ends, and opening it moves them apart exactly as anything else put between them would. Here the pair is named by a link merely passing through, and nothing yet lets a link bid into a gap it is only a visitor in. It is the remaining half and it is not built.
|
|
404
408
|
|
|
@@ -410,7 +414,7 @@ note <name> "<text>" <placement> ...
|
|
|
410
414
|
|
|
411
415
|
A note is text with no box, anchored to a node so it travels with it.
|
|
412
416
|
|
|
413
|
-
Nothing bounds a note the way a border bounds a box, so a sentence-length note without a `
|
|
417
|
+
Nothing bounds a note the way a border bounds a box, so a sentence-length note without a `wrap` is drawn as one very long line and will cross whatever is beside it. Give every note a wrap.
|
|
414
418
|
|
|
415
419
|
A note starts one step smaller than a box label, because a note annotates the diagram rather than being part of it and at the same size an aside reads as a statement. That is a default, not a ceiling: say `size:` and it does what you said. The two things a note most often needs saying about it are how big its text is and how wide it runs, and both are attributes of the note rather than something to be inferred from the fact that it is one.
|
|
416
420
|
|
|
@@ -422,35 +426,111 @@ deck <name> "<label>" ["<label>" ...]
|
|
|
422
426
|
|
|
423
427
|
Draws the named container with offset copies behind it, one per label, to say "there are several of these and they are the same." Only the front copy shows its contents.
|
|
424
428
|
|
|
429
|
+
## Attributes
|
|
430
|
+
|
|
431
|
+
Every attribute, and what takes one. The kinds here are what a statement *draws* rather than which keyword declared it: a node whose `shape:` names an icon is drawn as a glyph body and takes a different set from an ordinary box.
|
|
432
|
+
|
|
433
|
+
| attribute | box | note | glyph body | link | says |
|
|
434
|
+
|---|---|---|---|---|---|
|
|
435
|
+
| `style` | ✓ | ✓ | ✓ | ✓ | the named bundle to take appearance from |
|
|
436
|
+
| `size` | ✓ | ✓ | ✓ | ✓ | how big the text is set |
|
|
437
|
+
| `gap` | ✓ | ✓ | ✓ | | the default distance to whatever it is placed against |
|
|
438
|
+
| `overlap` | ✓ | ✓ | ✓ | | `allow`, to opt out of non-overlap |
|
|
439
|
+
| `wrap` | ✓ | ✓ | ✓ | | how many characters fit on a line before the label folds |
|
|
440
|
+
| `align` | ✓ | | | | `widths`, to widen every child to the widest of them |
|
|
441
|
+
| `icon` | ✓ | | | | the glyph that takes the column beside the label |
|
|
442
|
+
| `shape` | ✓ | | ✓ | | what the node is drawn as |
|
|
443
|
+
| `from` `to` | | | | ✓ | which side the line leaves and arrives on |
|
|
444
|
+
| `fill` | ✓ | | | | color — see "A color names the part it colors" |
|
|
445
|
+
| `border` | ✓ | | | | color |
|
|
446
|
+
| `text` | ✓ | ✓ | ✓ | ✓ | color |
|
|
447
|
+
| `subtext` | ✓ | | ✓ | | color |
|
|
448
|
+
| `line` | | | | ✓ | color |
|
|
449
|
+
|
|
450
|
+
The `diagram` statement has a vocabulary of its own — `background`, and so far nothing else — which is checked the same way. Writing `background:` on a box is an error that points at `fill:`.
|
|
451
|
+
|
|
452
|
+
**A word this table does not give the kind is an error.** The two ways of being wrong get different answers, because they have different remedies. A word that is an attribute nowhere is a misspelling, and the error lists what the kind does take. A word that is an attribute *somewhere else* is usually a real statement written on the wrong half of the diagram, so the error says where it belongs:
|
|
453
|
+
|
|
454
|
+
```
|
|
455
|
+
"one" is a box and has from: left. `from:` belongs to a link — a box takes style, size, gap, ...
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
Three of the gaps in the table are worth saying out loud, because each was silent until then and none of them looks like a mistake while you are writing it. A glyph body takes no `icon:` — it is drawn *as* a picture and has no box for a second one to sit in. A note and a glyph body take no `align:`, which widens a node's children, and neither may have any. A link takes no `gap:` or `overlap:` — those say where a box sits, and a link is not placed, it joins two things that are.
|
|
459
|
+
|
|
460
|
+
**Changed 2026-09-09.** Until then a node or link attribute the tool did not recognize was parsed, stored and never read: `wibble: red` on a box drew nothing and said nothing. This was the last place in the language where a key could silently do nothing, and the rule everywhere else — an unknown `diagram` key, an unknown placement modifier, a color naming a part the kind has not got — has always been that a key which silently does nothing looks like the tool being broken rather than like a typo. A file that rendered with a stray word in it will now stop with an error naming it.
|
|
461
|
+
|
|
425
462
|
## Styles
|
|
426
463
|
|
|
427
464
|
```
|
|
428
465
|
style <name> <attributes>
|
|
429
466
|
```
|
|
430
467
|
|
|
431
|
-
A named bundle of appearance, applied with `style: <name>` on a node or link.
|
|
468
|
+
A named bundle of appearance, applied with `style: <name>` on a node or link. Color carries meaning through the style name rather than being written per node.
|
|
432
469
|
|
|
433
470
|
```
|
|
434
|
-
style backup
|
|
471
|
+
style backup border: #d2904e
|
|
435
472
|
box server.mirror "\\"important\\" mirror" style: backup
|
|
436
473
|
```
|
|
437
474
|
|
|
438
|
-
The appearance attributes are `
|
|
475
|
+
The appearance attributes are `fill`, `border`, `text`, `line`, `subtext`, `size`, `icon` and `shape`. The first five each take a color written as the viewer will receive it — `#142814`, or any CSS color, or `none`.
|
|
476
|
+
|
|
477
|
+
### A color names the part it colors
|
|
439
478
|
|
|
440
|
-
|
|
479
|
+
A color attribute says which part of a thing it colors, and a part exists only on the kinds that have one:
|
|
441
480
|
|
|
442
|
-
|
|
481
|
+
| attribute | colors | on |
|
|
482
|
+
|---|---|---|
|
|
483
|
+
| `fill` | the area inside the outline | a box |
|
|
484
|
+
| `border` | the outline | a box |
|
|
485
|
+
| `text` | the label | a box, a note, a glyph body, a link |
|
|
486
|
+
| `line` | the drawn line and its arrowheads | a link |
|
|
487
|
+
| `subtext` | every label line after the first | a box, a glyph body |
|
|
443
488
|
|
|
489
|
+
A word written on a kind that has no such part is refused by name, and the error lists the parts that kind does have — `border:` on a note is a mistake, not something to ignore, for the same reason an unknown `diagram` key is.
|
|
490
|
+
|
|
491
|
+
A link's label takes the line's color unless `text` says otherwise, so a link that means something by being orange means it in its words too, and there is still a way to say the words are not orange.
|
|
492
|
+
|
|
493
|
+
A style contributes a part only to the kinds that have it, so a style shared between boxes and links writes one key for each:
|
|
494
|
+
|
|
495
|
+
```
|
|
496
|
+
style backup border: #d2904e line: #d2904e
|
|
444
497
|
```
|
|
445
|
-
|
|
498
|
+
|
|
499
|
+
The boxes take the border, the links take the line, and neither sees the other's word. Writing only `border` there would color the boxes and leave the links plain.
|
|
500
|
+
|
|
501
|
+
### A style may carry what a thing cannot use
|
|
502
|
+
|
|
503
|
+
The table under "Attributes" is checked against what you wrote *on the statement*, never against what a style handed it. That is what makes a bundle spanning kinds possible at all: the benchmark's `style synced` carries a fill, a border and a subtext for five boxes and a `line` for the four links joining them, and every use of it leaves some of its keys unused. That is the style doing its job, not a mistake, so nothing is said about it.
|
|
504
|
+
|
|
505
|
+
What is refused is a style that gives a thing **nothing at all**:
|
|
506
|
+
|
|
507
|
+
```
|
|
508
|
+
style boxy fill: #142814 icon: disk
|
|
509
|
+
note n "An aside" style: boxy
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
A note is bare text, with neither a fill nor an icon, so `boxy` dresses it in nothing whatever and the name is on the wrong sort of thing. Partial overlap is the normal case; zero overlap is never anything else. A style that named every attribute in the language would slip through this, since it contributes to everything by construction — nobody writes one by accident, and the hole is left open rather than closed with a rule that would fire on `synced`.
|
|
513
|
+
|
|
514
|
+
A style's own keys are checked against the whole vocabulary, since a word that is an attribute of nothing is a misspelling wherever it sits. `style s wibble: red` is an error; a style was the last place one could hide.
|
|
515
|
+
|
|
516
|
+
**Removed: `stroke`.** It named no part — it meant the border of a box, the *text* of a note or a glyph body, and the line of a link, whichever the thing happened to have. That is coherent one kind at a time and ambiguous read across them; it meant no ink attribute could ever be *wrong*; and it left one thing with no way to be said at all, the color of the text on an ordinary box, which is why `subtext` exists in the odd shape it does. An older file carrying it gets an error naming the word to use instead.
|
|
517
|
+
|
|
518
|
+
A color is never written in quotes, and a quoted one is refused. There is nothing to check a color *against* — the tool keeps no list of color words, as below — so this is the one thing that can be checked, and it is the mistake that actually gets made: `subtext: "medium-fine"` reads as the text that goes underneath, and every attribute that takes a color would otherwise accept the string, find it is not a color, and draw nothing without saying so. The qualifier under a name is a second line of the label, not a `subtext` value.
|
|
519
|
+
|
|
520
|
+
There is no list of color words the tool knows. An earlier version had one, and it was wrong in the way such lists always are: `dark-green` existed only because somebody added it to a map in the renderer, and the next color a diagram wanted would have needed a code change to say. Writing the color directly removes both the list and the reason to grow it. `green` still works, because it is a CSS color, not because this tool has heard of it.
|
|
521
|
+
|
|
522
|
+
`subtext` colors every label line after the first, so a box can carry a name and a quieter qualifier under it:
|
|
523
|
+
|
|
524
|
+
```
|
|
525
|
+
style synced fill: #142814 border: #486544 subtext: muted
|
|
446
526
|
box pc.files "\"important\" directory / Dropbox-synced" style: synced
|
|
447
527
|
```
|
|
448
528
|
|
|
449
|
-
`icon` and `shape` belong in a style for the same reason a
|
|
529
|
+
`icon` and `shape` belong in a style for the same reason a color does: they say what kind of thing this is, and a kind wants to look alike everywhere it appears. `style artifact fill: #460000 shape: document` puts the folded corner on every dump in the diagram, and the use site stays one word.
|
|
450
530
|
|
|
451
|
-
Bundling `size` into a style is how a size comes to mean something. `style aside size: small
|
|
531
|
+
Bundling `size` into a style is how a size comes to mean something. `style aside size: small text: #8b8b8b` applied to several nodes says they are the same kind of remark, which a `size: small` written out at each of them does not.
|
|
452
532
|
|
|
453
|
-
`muted` is the one reserved word left, and it earns the exception: it means the theme's secondary text
|
|
533
|
+
`muted` is the one reserved word left, and it earns the exception: it means the theme's secondary text color rather than a fixed one, so a qualifier stays readable when the theme changes. Writing `subtext: #8b8b8b` instead would pin it to one theme. Say nothing and every line of a label reads alike, which is what most labels want — `Computer 1 / Ubuntu` is two lines of one name, not a name and a qualifier, and the distinction is the author's to make rather than the renderer's to guess.
|
|
454
534
|
|
|
455
535
|
## The diagram itself
|
|
456
536
|
|
|
@@ -464,16 +544,16 @@ Settings that belong to the drawing as a whole rather than to anything in it. Th
|
|
|
464
544
|
diagram background: #111111
|
|
465
545
|
```
|
|
466
546
|
|
|
467
|
-
One attribute so far. `background` takes a
|
|
547
|
+
One attribute so far. `background` takes a color the same way `fill` does, and it colors the page behind everything, including the strip a link label knocks out of whatever it crosses. Say nothing and the theme's own background stands.
|
|
468
548
|
|
|
469
|
-
An unknown key is refused by name — `diagram has no "backround" — it takes background` — rather than quietly ignored
|
|
549
|
+
An unknown key is refused by name — `diagram has no "backround" — it takes background` — rather than quietly ignored, the same as every other attribute. See "Attributes".
|
|
470
550
|
|
|
471
551
|
## What the language refuses
|
|
472
552
|
|
|
473
553
|
Deliberate omissions. What they protect is that the renderer never *chooses* an arrangement — it computes the one you described. Working out coordinates from a stated arrangement is arithmetic and is not what is being refused here; picking between arrangements that all satisfy what you wrote is. There is exactly one narrow exception, and it is named as such under "Boxes do not overlap".
|
|
474
554
|
|
|
475
555
|
- **Coordinates**, in any form, including as an escape hatch.
|
|
476
|
-
- **Guessing an axis nobody constrained.** When two placements bind one axis and nothing binds the other, the tool refuses rather than picking a target to
|
|
556
|
+
- **Guessing an axis nobody constrained.** When two placements bind one axis and nothing binds the other, the tool refuses rather than picking a target to center on. Choosing there would decide which row a box shares, not how far it sits from something.
|
|
477
557
|
- **Placements that run in a circle.** A loop where each placement demands more room than the last cannot be satisfied and is an error naming the placements involved. A target does *not* have to be positioned before the node naming it — the whole system is solved at once — so ordinary mutual references are fine.
|
|
478
558
|
- **Edge waypoints.** A point a line must pass through is a coordinate wearing a hat. Saying a line goes between two named things is not one — it names things the diagram already contains, and it survives those things moving.
|
|
479
559
|
- **Choosing a route.** The tool will not find its own way around an obstacle. A line that crosses something it should not is a line you have not yet said enough about, and `between` is how you say it.
|
|
@@ -489,7 +569,7 @@ Designed, decided, and absent from the code. Written down so the next version ha
|
|
|
489
569
|
|
|
490
570
|
**An icon outside the built-in six.** The set is closed, and a diagram wanting a glyph that is not in it has nowhere to go. The two shapes this could take are a declaration in the file, `icon <name> "<path data>"` beside `style`, and `icon: ./thing.svg` inlined by the tool at render time. Either keeps the output standalone, which is the constraint any answer has to meet.
|
|
491
571
|
|
|
492
|
-
**A named channel cannot make room for itself.** Lines through a `between` gap too narrow for them crowd together silently, in exactly the way attachments on a too-short side do. A
|
|
572
|
+
**A named channel cannot make room for itself.** Lines through a `between` gap too narrow for them crowd together silently, in exactly the way attachments on a too-short side do. A labeled link *does* now open the gap between its own two ends — see "A label makes room for itself" — and it does so by the measure-then-constrain route that region alignments already use, which is the route this wants too. What is missing is the harder case: several links sharing a channel between two nodes neither of them is an end of, where the room needed is the whole stack of lanes rather than one label.
|
|
493
573
|
|
|
494
574
|
## Known to be wrong
|
|
495
575
|
|
|
@@ -497,7 +577,7 @@ Not omissions — defects, left here so nobody rediscovers them. Most were found
|
|
|
497
577
|
|
|
498
578
|
~~A placement written after an attribute reports the wrong mistake.~~ Fixed. Attributes still end the positional part of a statement, so `box q "Q" gap: wide level with p` is still an error, but the message now names the token that starts the stray placement and says to move it in front of the first `key: value`, rather than reporting where the parser had got to. The shape was easy to write by accident because a gap read as though it belonged to the placement it followed — which it now does, in brackets.
|
|
499
579
|
|
|
500
|
-
**A bracketed node sits against one side of any slack.** When two opposing placements leave more room than the node needs — because something else forced the two targets further apart — the node sits against the side it was pushed from rather than
|
|
580
|
+
**A bracketed node sits against one side of any slack.** When two opposing placements leave more room than the node needs — because something else forced the two targets further apart — the node sits against the side it was pushed from rather than centered between them. In practice the tightest arrangement usually leaves no slack, so this rarely shows. Whether it should center instead is not decided.
|
|
501
581
|
|
|
502
582
|
~~A node placed only with `left of` or `above` drifted to the canvas edge.~~ Fixed. Every constraint reads "this one is at least so far right of that one", so the solve puts each node at the smallest position its constraints allow — right for anything with something behind it, but `left of X` bounds *X* rather than the node that wrote it, leaving such a node nothing to be pushed by. It settled at the edge of the drawing while its target was carried off by the rest of the diagram. A node with nothing behind it now travels until the first of its own placements binds, which is what "as close together as your placements allow" always said.
|
|
503
583
|
|
|
@@ -505,7 +585,7 @@ Not omissions — defects, left here so nobody rediscovers them. Most were found
|
|
|
505
585
|
|
|
506
586
|
~~One relation cannot say what a real arrangement needs.~~ Fixed by letting a node carry several placements: it takes its horizontal position from one target and its vertical from another, and two opposing placements put it between two more.
|
|
507
587
|
|
|
508
|
-
~~The unwritten axis is a silent guess.~~ Fixed. A lone placement's
|
|
588
|
+
~~The unwritten axis is a silent guess.~~ Fixed. A lone placement's centering is now the documented meaning of the direction rather than a fallback, and the case where it would have to choose between two targets is an error.
|
|
509
589
|
|
|
510
590
|
~~Two boxes can land on the same pixels in silence.~~ Fixed. Every pair of boxes must now clear the other, and where the file does not order them the tool says so instead of drawing one over the other. Links are still unchecked.
|
|
511
591
|
|
|
@@ -515,20 +595,23 @@ Not omissions — defects, left here so nobody rediscovers them. Most were found
|
|
|
515
595
|
|
|
516
596
|
That one was found by testing the lexer, not by rendering — and it could not have been found by rendering, because every label in the benchmark happens to use spaces around its separator. Worth knowing that the repository's own second test target is an OSI and **TCP/IP** diagram, so a picture the language was meant to be tested against could not have been written in it. A benchmark only exercises the cases it happens to contain.
|
|
517
597
|
|
|
518
|
-
~~A
|
|
598
|
+
~~A labeled link between two boxes at the default gap drew its label across both of them.~~ Fixed. The default gap is sized for boxes to breathe and a label is wider than that, so `link a -> b "statements"` on two adjacent boxes came out unreadable and nothing said so; the authoring workaround was to name a wider gap on a placement that had no reason to be wider. A labeled link now widens the corridor it crosses by what the label needs. Note what this is *not*: no coordinate, no repair of a solved layout, and nothing that finds a route — the corridor is derived from where the boxes landed and then becomes an ordinary minimum distance like any other.
|
|
519
599
|
|
|
520
600
|
~~Two links between the same pair of sides were drawn on top of each other.~~ Fixed. Each side was ordered on its own, by where the far ends sat, and for links that share both ends that signal says nothing — so the two edges were ordered without reference to each other and the lines converged in the middle instead of nesting. The visible damage was to the labels: both landed at the same point and the second knocked a hole through the first, leaving one word of it. A group like this now takes one lane order used at both ends. See "Several links between the same two sides".
|
|
521
601
|
|
|
522
|
-
~~Several links between the same two boxes with no side named were drawn on top of each other.~~ Fixed. This is the same defect as the one above, one step out: a bundle is a statement about two named edges, and an end with no side named has not made one, so nothing saw the group. `link a -> b` three times drew one visible line carrying one label. Each such link now takes its own line, parallel to the one it would have drawn alone and a lane away from it. Note what did *not* change: an unnamed end still attaches where the
|
|
602
|
+
~~Several links between the same two boxes with no side named were drawn on top of each other.~~ Fixed. This is the same defect as the one above, one step out: a bundle is a statement about two named edges, and an end with no side named has not made one, so nothing saw the group. `link a -> b` three times drew one visible line carrying one label. Each such link now takes its own line, parallel to the one it would have drawn alone and a lane away from it. Note what did *not* change: an unnamed end still attaches where the center-to-center ray crosses the border, so no single link anywhere moved. See "Several links with no side named at all".
|
|
603
|
+
|
|
604
|
+
~~An unknown attribute was ignored in silence.~~ Fixed. `wibble: red` on a box parsed, was stored, and was never read again — nothing drew and nothing was said. So did every real attribute written on a kind with no use for it: `icon:` on a node already drawn as a glyph, `gap:` on a link. This was the same defect the color parts had closed one level down a version earlier, and it is how that migration produced false results from the repository's own regression check, since the older build simply dropped every `border:` it had not heard of. See "Attributes".
|
|
523
605
|
|
|
524
|
-
~~A link label ignored the line break.~~ Fixed. ` / ` split a node's label and was never applied to a link's, so the marker came out as a literal slash on an arrow and the benchmark's two-line captions had to be flattened to one. The measurer had always returned the split lines; the renderer was handing it the raw string and drawing that instead. The block now
|
|
606
|
+
~~A link label ignored the line break.~~ Fixed. ` / ` split a node's label and was never applied to a link's, so the marker came out as a literal slash on an arrow and the benchmark's two-line captions had to be flattened to one. The measurer had always returned the split lines; the renderer was handing it the raw string and drawing that instead. The block now centers on the point the label already occupied, so a one-line label sits exactly where it did.
|
|
525
607
|
|
|
526
608
|
## Undecided
|
|
527
609
|
|
|
528
610
|
Open questions the benchmark raised, recorded so a later session does not rediscover them.
|
|
529
611
|
|
|
530
612
|
- Named gaps are the first step toward numbers, but making them minimums took most of the pressure off: they now set how much a diagram breathes, never whether something fits. Whether four names is the right number is still open.
|
|
531
|
-
- Four machines each holding an identically-
|
|
613
|
+
- Four machines each holding an identically-labeled `files` child means writing the same line four times. This is the strongest case for a set-level declaration, for terseness rather than for placement.
|
|
532
614
|
- The 2×2 arrangement around a hub is four independent statements, so a fifth machine has no slot to reflow into. There are only eight directions.
|
|
533
615
|
- ~~Two notes anchored to the same side of one node will collide.~~ Answered by putting both in an invisible container and placing the container, so they stack instead of stacking on top of each other. Writing it the colliding way is now an error rather than a bad picture, since nothing in the file orders the two. Whether the container idiom is good enough or wants dedicated syntax is open.
|
|
534
616
|
- Nothing yet expresses one box spanning several rows of a parallel column, which the OSI reference render needs.
|
|
617
|
+
- **How contents sit inside a container that is wider than they are.** A container is as wide as the wider of its title and its contents, and when the title wins, the children sit against the left of the slack — because every member goes at the smallest position its constraints allow, and nothing pushes them right. The column then reads as ragged inside a box whose own label may well be centered. `align: widths` already means "how the contents sit", so the key is the obvious home for a second, independent word; what is not settled is whether centering is one word there, whether it should be sayable per axis, and whether it is the same question as the bracketed node sitting against one side of its slack under "Known to be wrong" — which smells like it is. Automatic centering is ruled out either way: it would move every existing diagram whose container title is wider than its contents.
|
package/dist/ast.d.ts
CHANGED
|
@@ -7,10 +7,10 @@ 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. `
|
|
10
|
+
* Which edge 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 ["
|
|
13
|
+
export declare const EDGES: readonly ["center", "top", "bottom", "left", "right"];
|
|
14
14
|
export type Edge = (typeof EDGES)[number];
|
|
15
15
|
/** An edge belongs to one axis, so an alignment never has to say which. */
|
|
16
16
|
export declare const EDGE_AXIS: Record<Edge, Axis>;
|
|
@@ -40,7 +40,7 @@ export interface OffsetPlacement {
|
|
|
40
40
|
gap?: string;
|
|
41
41
|
line: number;
|
|
42
42
|
}
|
|
43
|
-
/** `level with docker` — share an edge or a
|
|
43
|
+
/** `level with docker` — share an edge or a center line, with no gap in between. */
|
|
44
44
|
export interface AlignPlacement {
|
|
45
45
|
kind: 'align';
|
|
46
46
|
axis: Axis;
|
|
@@ -55,7 +55,7 @@ export interface AlignPlacement {
|
|
|
55
55
|
export type Placement = OffsetPlacement | AlignPlacement;
|
|
56
56
|
/**
|
|
57
57
|
* The modifiers a placement understands, in brackets after its targets. Refused
|
|
58
|
-
* by name when
|
|
58
|
+
* by name when unrecognized, for the reason `DIAGRAM_KEYS` are: a modifier that
|
|
59
59
|
* silently does nothing looks like a bug in the tool rather than a typo.
|
|
60
60
|
*/
|
|
61
61
|
export declare const PLACEMENT_KEYS: readonly ["gap"];
|
|
@@ -87,7 +87,7 @@ export declare function describeAxis(axis: Axis): string;
|
|
|
87
87
|
/** `key: value` pairs trailing a statement. Values are always strings here. */
|
|
88
88
|
export type Attrs = Record<string, string>;
|
|
89
89
|
/**
|
|
90
|
-
* What a label's brackets may say: `"Docker" (at: bottom, align:
|
|
90
|
+
* What a label's brackets may say: `"Docker" (at: bottom, align: center)`.
|
|
91
91
|
*
|
|
92
92
|
* They are bracketed onto the label rather than written among the node's
|
|
93
93
|
* attributes for the same reason a gap is bracketed onto its placement — they
|
|
@@ -148,6 +148,74 @@ export interface DiagramStmt {
|
|
|
148
148
|
}
|
|
149
149
|
/** The attributes a `diagram` statement understands. */
|
|
150
150
|
export declare const DIAGRAM_KEYS: readonly ["background"];
|
|
151
|
+
/**
|
|
152
|
+
* The attributes whose value is a color rather than text. A color is written
|
|
153
|
+
* as the viewer will receive it and the renderer keeps no list of color words
|
|
154
|
+
* of its own, so there is nothing to check a value *against* — but quoting is
|
|
155
|
+
* the author saying "this is text", and an unquoted value cannot hold a space,
|
|
156
|
+
* so prose has to be quoted to get in at all. Refusing a quoted color is
|
|
157
|
+
* therefore the whole of what can be checked here, and it happens to be the
|
|
158
|
+
* mistake people actually make: `subtext: "medium-fine"` reads as the text
|
|
159
|
+
* that goes underneath, and was accepted and dropped in silence.
|
|
160
|
+
*/
|
|
161
|
+
export declare const COLOR_KEYS: readonly ["fill", "border", "text", "line", "subtext", "background"];
|
|
162
|
+
/**
|
|
163
|
+
* A color attribute names the *part* it colors, and a part exists only on the
|
|
164
|
+
* kinds that have one. A box has a border and text; a note and a glyph body are
|
|
165
|
+
* text and nothing else; a link is a line and its label.
|
|
166
|
+
*
|
|
167
|
+
* This table is what makes the words checkable. `border:` on a note is refused
|
|
168
|
+
* by name rather than ignored — the same rule as an unknown `diagram` key, and
|
|
169
|
+
* for the same reason: an attribute that silently does nothing looks like the
|
|
170
|
+
* tool being broken.
|
|
171
|
+
*
|
|
172
|
+
* A style spanning kinds writes one key per kind — `border: #d2904e line:
|
|
173
|
+
* #d2904e` — since a style contributes a part only to the kinds that have it.
|
|
174
|
+
* That is what replaced `stroke:`, which named no part and so could never be
|
|
175
|
+
* wrong, and which is why a box's text had no word of its own until now.
|
|
176
|
+
*/
|
|
177
|
+
export declare const COLOR_PARTS: Record<Kind, readonly string[]>;
|
|
178
|
+
/**
|
|
179
|
+
* The four things an attribute can be written on. A glyph is a node whose
|
|
180
|
+
* `shape:` names an icon, so it is not a statement keyword — but it takes a
|
|
181
|
+
* different set of attributes from an ordinary box, which is what makes it a
|
|
182
|
+
* kind here.
|
|
183
|
+
*/
|
|
184
|
+
export type Kind = 'box' | 'note' | 'glyph' | 'link';
|
|
185
|
+
/**
|
|
186
|
+
* Every attribute each kind understands. An attribute a kind has no use for is
|
|
187
|
+
* refused by name rather than dropped, the same rule as an unknown `diagram`
|
|
188
|
+
* key, a `PLACEMENT_KEYS` modifier or a color part — and for the same reason,
|
|
189
|
+
* which the color parts only closed one level down: a key that silently does
|
|
190
|
+
* nothing looks like the tool being broken rather than like a typo.
|
|
191
|
+
*
|
|
192
|
+
* The color entries repeat `COLOR_PARTS` and must agree with it. They are
|
|
193
|
+
* written out rather than spliced in because this table is the answer to "what
|
|
194
|
+
* may I write here", and a reader of it should not have to assemble the list
|
|
195
|
+
* from two places.
|
|
196
|
+
*
|
|
197
|
+
* Three of the exclusions are the whole of what this table decides, and each is
|
|
198
|
+
* a place the old silence hid something:
|
|
199
|
+
*
|
|
200
|
+
* - A glyph takes no `icon:`. It is drawn *as* a picture and has no box for a
|
|
201
|
+
* second one to sit in; `sizeNode` returns before it would ever be read.
|
|
202
|
+
* - A glyph and a note take no `align:`, which widens a node's children, and
|
|
203
|
+
* neither may have any.
|
|
204
|
+
* - A link takes no `gap:` or `overlap:`. Those are about where a box sits, and
|
|
205
|
+
* a link is not placed — it joins two things that are.
|
|
206
|
+
*/
|
|
207
|
+
export declare const ATTR_KEYS: Record<Kind, readonly string[]>;
|
|
208
|
+
/**
|
|
209
|
+
* Every word that is an attribute *somewhere*, which is what separates a
|
|
210
|
+
* misspelling from a key written on the wrong kind of thing. The two deserve
|
|
211
|
+
* different errors: one has no remedy but the spelling, the other has a real
|
|
212
|
+
* meaning somewhere else in the file.
|
|
213
|
+
*
|
|
214
|
+
* `DIAGRAM_KEYS` is in here so that `background:` on a box is understood to be
|
|
215
|
+
* a real word in the wrong place — that mistake wants to be pointed at `fill:`,
|
|
216
|
+
* not told the word does not exist.
|
|
217
|
+
*/
|
|
218
|
+
export declare const ALL_ATTR_KEYS: readonly string[];
|
|
151
219
|
export interface StyleStmt {
|
|
152
220
|
kind: 'style';
|
|
153
221
|
name: string;
|