reladraw 0.4.0 → 0.6.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/NOTICE +37 -0
- package/README.md +2 -2
- package/SYNTAX.md +164 -7
- package/dist/ast.d.ts +40 -3
- package/dist/ast.js +24 -2
- package/dist/cli.js +18 -2
- package/dist/grammar.d.ts +1 -1
- package/dist/grammar.js +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/model.d.ts +14 -1
- package/dist/parser.js +93 -4
- package/dist/render.d.ts +1 -22
- package/dist/render.js +806 -34
- package/dist/resolve.js +123 -9
- package/dist/themes.d.ts +46 -0
- package/dist/themes.js +190 -0
- package/package.json +1 -1
package/NOTICE
CHANGED
|
@@ -12,3 +12,40 @@ any other trade names, trademarks, service marks, or product names of the
|
|
|
12
12
|
project, except as required for reasonable and customary use in describing the
|
|
13
13
|
origin of the work (see section 6 of the License). Forks and derived works are
|
|
14
14
|
welcome and must be distributed under a different name.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
Color palettes
|
|
19
|
+
|
|
20
|
+
Several of reladraw's themes (src/themes.ts) are adapted from the color
|
|
21
|
+
palettes below. Each is distributed under the MIT License, whose text follows
|
|
22
|
+
the list of copyright notices and applies to each of them.
|
|
23
|
+
|
|
24
|
+
Solarized Copyright (c) 2011 Ethan Schoonover
|
|
25
|
+
https://github.com/altercation/solarized
|
|
26
|
+
Gruvbox Copyright (c) 2018 Pavel Pertsev
|
|
27
|
+
https://github.com/morhetz/gruvbox
|
|
28
|
+
Catppuccin Copyright (c) 2021 Catppuccin
|
|
29
|
+
https://github.com/catppuccin/catppuccin
|
|
30
|
+
Nord Copyright (c) 2016-present Sven Greb
|
|
31
|
+
https://github.com/nordtheme/nord
|
|
32
|
+
Dracula Copyright (c) 2023 Dracula Theme
|
|
33
|
+
https://github.com/dracula/dracula-theme
|
|
34
|
+
|
|
35
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
36
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
37
|
+
in the Software without restriction, including without limitation the rights
|
|
38
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
39
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
40
|
+
furnished to do so, subject to the following conditions:
|
|
41
|
+
|
|
42
|
+
The above copyright notice and this permission notice shall be included in
|
|
43
|
+
all copies or substantial portions of the Software.
|
|
44
|
+
|
|
45
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
46
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
47
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
48
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
49
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
50
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
51
|
+
THE SOFTWARE.
|
package/README.md
CHANGED
|
@@ -68,7 +68,7 @@ It is [plain Markdown](.claude/skills/reladraw/SKILL.md) with the syntax referen
|
|
|
68
68
|
|
|
69
69
|
## Status
|
|
70
70
|
|
|
71
|
-
Version 0.
|
|
71
|
+
Version 0.6.0. Early, but it runs: a parser, resolver and SVG renderer in TypeScript with no runtime dependencies, and a command-line tool that takes a text file and writes a standalone SVG. The comparison at the top of this page is that pipeline run on [`examples/arch.reladraw`](examples/arch.reladraw). What is still visibly off there is typography, not placement.
|
|
72
72
|
|
|
73
73
|
```
|
|
74
74
|
npm install -g reladraw
|
|
@@ -143,4 +143,4 @@ The license covers the code, not the name: it grants no rights to use "reladraw"
|
|
|
143
143
|
|
|
144
144
|
## Contributing
|
|
145
145
|
|
|
146
|
-
Issues are wanted — especially a diagram you tried to write and could not.
|
|
146
|
+
Issues are wanted — especially a diagram you tried to write and could not. While the language is changing quickly, an issue is more useful than a pull request; [CONTRIBUTING.md](CONTRIBUTING.md) says why.
|
package/SYNTAX.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Syntax reference — 0.
|
|
1
|
+
# Syntax reference — 0.6.0
|
|
2
2
|
|
|
3
3
|
What the language accepts. The parser, resolver and SVG renderer implement all of it; the sections at the end record what is defective, unchecked or undecided.
|
|
4
4
|
|
|
@@ -422,6 +422,23 @@ You name a side and never a point on it. Alone on a side, an edge lands at its c
|
|
|
422
422
|
|
|
423
423
|
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 texts still come apart, because the lines bow in the middle to make up what the side could not give them, but the arrowheads themselves land on one point and nothing warns you — so a small node with several edges arriving on one side is worth a look.
|
|
424
424
|
|
|
425
|
+
#### Ends that face away from each other
|
|
426
|
+
|
|
427
|
+
```
|
|
428
|
+
node parser "Parser"
|
|
429
|
+
node resolver "Resolver" right of parser
|
|
430
|
+
node renderer "Renderer" right of resolver
|
|
431
|
+
edge parser -> renderer "test edge" from: left to: right
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
That line leaves Parser heading left and has to arrive at Renderer from the right, so it must turn back on itself somewhere. It turns back in the gap between its two nodes when there is one wide enough to hold the line and its text, and otherwise it goes over the top: out of its side, along the top of the row clear of the tallest node between its ends, and down into the far side. The text rides on that top stretch, so it never sits on a node. The same goes for `from: top to: bottom` in a column, which goes round the right, and for an edge from a node back to itself.
|
|
435
|
+
|
|
436
|
+
Several such lines over the same row take a lane each, the shorter inside the longer, so they nest instead of drawing on top of one another.
|
|
437
|
+
|
|
438
|
+
Sides at right angles can face away too. `from: left to: top` with the far node to the right leaves heading away from it, so the line steps out of the left, goes up past both nodes, runs along above them and comes straight down into the top. The side named at the far end says which way round: `to: bottom` takes it underneath instead.
|
|
439
|
+
|
|
440
|
+
Over the top is the rule, not a guess the tool makes each time — it does not look for the shorter way round. To send the line underneath or round the left, say which side of a node it passes: `below resolver`. See "Passing on one side of a node".
|
|
441
|
+
|
|
425
442
|
#### Several edges between the same two sides
|
|
426
443
|
|
|
427
444
|
Edges that run between the *same* pair of sides are a case of their own, because "where the far ends sit" cannot order them: every one of them goes to the same node.
|
|
@@ -487,6 +504,78 @@ Several edges may share one channel, and they take a lane each. As with attachme
|
|
|
487
504
|
|
|
488
505
|
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 nodes apart. That is the difference between this and a text making room for itself, above: there, the corridor is the gap between the edge's own two ends, and opening it moves them apart exactly as anything else put between them would. Here the pair is named by an edge merely passing through, and nothing yet lets an edge bid into a gap it is only a visitor in. It is the remaining half and it is not built.
|
|
489
506
|
|
|
507
|
+
### Passing on one side of a node
|
|
508
|
+
|
|
509
|
+
`above`, `below`, `left of` and `right of`, written on an edge, say which side of a node the line passes.
|
|
510
|
+
|
|
511
|
+
```
|
|
512
|
+
node parser "Parser"
|
|
513
|
+
node resolver "Resolver" right of parser
|
|
514
|
+
node renderer "Renderer" right of resolver
|
|
515
|
+
edge parser -> renderer "underneath" from: left to: right below resolver
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
They are the placement words, and they mean what `between` means: only where the line passes that node. `below resolver` says the line is below Resolver where it goes by Resolver, not that the whole line is. That edge would go over the top of the row on its own; with the clause it leaves Parser, drops under the row and comes up into Renderer.
|
|
519
|
+
|
|
520
|
+
Write as many as you need, one for each node you have something to say about. The line meets them in the order they sit along its way, so no clause says an order:
|
|
521
|
+
|
|
522
|
+
```
|
|
523
|
+
node a "A"
|
|
524
|
+
node b "B" below a
|
|
525
|
+
edge a -> b from: top to: bottom left of a right of b
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
That line leaves the top of A, goes round A's left, crosses over in the gap between the two, passes B on the right, and turns up into B's bottom. The line is drawn as straight stretches joined by rounded corners, each stretch as close to the two ends as its clauses allow, and pushed further out, the way its clause already points, by any node sitting on it.
|
|
529
|
+
|
|
530
|
+
One clause may name several nodes, and it is not shorthand for several clauses. `below a and b` is below the box bounding both, so it covers the stretch between them too:
|
|
531
|
+
|
|
532
|
+
```
|
|
533
|
+
node t1 "T1"
|
|
534
|
+
node t2 "T2" right of t1 (gap: wide)
|
|
535
|
+
node peak "Peak" right of t1 left of t2 above t1
|
|
536
|
+
edge t1 -> t2 from: bottom to: bottom below t1 above peak below t2
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
Those three clauses let the line rise over Peak in the gap. Write `below t1 and t2 above peak` instead and it is refused, because Peak sits over the stretch `below t1 and t2` covers, and the line cannot be below the one and above the other at the same point.
|
|
540
|
+
|
|
541
|
+
One edge may pass some nodes above or below and others left or right. Passing above or below is done travelling across the page, and passing left or right travelling up or down it, so the line turns between the two, as few times as it can:
|
|
542
|
+
|
|
543
|
+
```
|
|
544
|
+
node a "A"
|
|
545
|
+
node m "M" right of a
|
|
546
|
+
node n "N" below m right of m
|
|
547
|
+
node b "B" below n right of n
|
|
548
|
+
edge a -> b below m left of n
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
That line leaves the bottom of A, runs across just under M, turns down on N's left, and runs across into B. The clauses read as before: `left of n` binds only where the line is level with N, so the last stretch passing under N is not held to it.
|
|
552
|
+
|
|
553
|
+
A side named at an end says which way the line goes first: `from: right` starts it across, `from: bottom` starts it down. With no side named at either end it goes across first, then down.
|
|
554
|
+
|
|
555
|
+
Clauses of one kind may sit on different legs. Here the line goes over P on the way out, down past N, and under M on the way in, turning twice:
|
|
556
|
+
|
|
557
|
+
```
|
|
558
|
+
node a "A"
|
|
559
|
+
node p "P" right of a
|
|
560
|
+
node n "N" below p right of p
|
|
561
|
+
node m "M" below n right of n
|
|
562
|
+
node b "B" right of m
|
|
563
|
+
edge a -> b from: right to: bottom above p right of n below m
|
|
564
|
+
```
|
|
565
|
+
|
|
566
|
+
Each clause binds whichever legs pass its node, so a node wide enough to sit under two legs holds both of them to its side.
|
|
567
|
+
|
|
568
|
+
Refused, each by name:
|
|
569
|
+
|
|
570
|
+
- **Clauses that cannot all hold** at one point, as above.
|
|
571
|
+
- **A crossing with no room.** Two clauses on opposite sides need a gap between their nodes to cross over in. Placed flush — `gap: 0` — there is none, and the error says to give the placement a gap or drop a clause. The gap is never opened for the line: you wrote how far apart those two are.
|
|
572
|
+
- **A node the line never passes.** `below z` on a line that never goes by Z says nothing.
|
|
573
|
+
- **Both kinds where no way of turning keeps them all.** The error names the clauses, or the one reason if every way fails for the same one.
|
|
574
|
+
- **`between` with a side.** An edge passing between two things already has a side of each.
|
|
575
|
+
- **A part of a node, or a gap.** A line passes a whole box, and passes it as close as reads clearly.
|
|
576
|
+
|
|
577
|
+
An end with no side named leaves from the side facing where the line is going.
|
|
578
|
+
|
|
490
579
|
## Against a part of a node
|
|
491
580
|
|
|
492
581
|
```
|
|
@@ -630,7 +719,7 @@ Every attribute, and what takes one. The kinds here are what a node's **body** i
|
|
|
630
719
|
| `line` | | | | ✓ | color |
|
|
631
720
|
| `url` | ✓ | ✓ | ✓ | ✓ | a destination to open when the thing is clicked |
|
|
632
721
|
|
|
633
|
-
The `diagram` statement has a vocabulary of its own — `
|
|
722
|
+
The `diagram` statement has a vocabulary of its own — `theme`, `background` and `text` — which is checked the same way. Writing `background:` on a node is an error that points at `fill:`, and `theme:` on one points at `diagram theme:`. A `default` takes a narrower list for each kind; see "Defaults".
|
|
634
723
|
|
|
635
724
|
**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:
|
|
636
725
|
|
|
@@ -762,12 +851,66 @@ diagram <attributes>
|
|
|
762
851
|
Settings that belong to the drawing as a whole rather than to anything in it. There is no name, because a file holds one diagram, and a second `diagram` statement is an error rather than a second opinion.
|
|
763
852
|
|
|
764
853
|
```
|
|
765
|
-
diagram background: #
|
|
854
|
+
diagram theme: nord background: #1e2229 text: (color: #e0e0e0)
|
|
766
855
|
```
|
|
767
856
|
|
|
768
|
-
|
|
857
|
+
Three attributes:
|
|
858
|
+
|
|
859
|
+
- `theme` names the theme that supplies every color the file does not state. See "Themes" below.
|
|
860
|
+
- `background` takes a color the same way `fill` does, and it colors the page behind everything, including the strip an edge text knocks out of whatever it crosses. Say nothing and the theme's own background stands.
|
|
861
|
+
- `text: (color: …)` sets the color of every text, on nodes and edges alike. It takes a color and nothing else; a text size is about one kind of text and belongs on a `default`.
|
|
862
|
+
|
|
863
|
+
An unknown key is refused by name — `diagram has no "backround" — it takes theme, background, text` — rather than quietly ignored, the same as every other attribute. See "Attributes".
|
|
864
|
+
|
|
865
|
+
### Themes
|
|
866
|
+
|
|
867
|
+
```
|
|
868
|
+
diagram theme: solarized-dark
|
|
869
|
+
```
|
|
870
|
+
|
|
871
|
+
| theme | |
|
|
872
|
+
|---|---|
|
|
873
|
+
| `dark` | the default: the palette of the benchmark drawing |
|
|
874
|
+
| `light` | the same roles on a white page |
|
|
875
|
+
| `solarized-dark`, `solarized-light` | [Solarized](https://ethanschoonover.com/solarized/) |
|
|
876
|
+
| `gruvbox-dark`, `gruvbox-light` | [Gruvbox](https://github.com/morhetz/gruvbox) |
|
|
877
|
+
| `catppuccin-mocha`, `catppuccin-latte` | [Catppuccin](https://catppuccin.com) |
|
|
878
|
+
| `nord` | [Nord](https://www.nordtheme.com) |
|
|
879
|
+
| `dracula` | [Dracula](https://draculatheme.com) |
|
|
880
|
+
| `high-contrast-dark`, `high-contrast-light` | no fills to lean on, every line at full strength |
|
|
881
|
+
| `print` | a white page, black lines and no fills, for paper |
|
|
882
|
+
|
|
883
|
+
The borrowed palettes were made for code, where a color marks a keyword or a string. A diagram needs a page, a box, a border, text and a line, so each theme is its scheme read as a diagram rather than a copy of it. Their licenses are in `NOTICE`.
|
|
769
884
|
|
|
770
|
-
|
|
885
|
+
A theme only ever supplies a color, so changing it never moves anything. A color you write yourself — `fill: #14532d`, or a style's — stays that color under every theme, which means a hand-picked color can clash with a theme it was not chosen against. `muted` is the one word that follows the theme.
|
|
886
|
+
|
|
887
|
+
An unknown name is an error that lists the themes.
|
|
888
|
+
|
|
889
|
+
The command line can render a file in another theme without editing it — `reladraw arch.reladraw --theme light` — and the flag wins over the file's `theme:`. That is how one source becomes a light and a dark image for a README that shows whichever matches the reader's settings. The file's own `background:` and `text:` still win over the flag's theme, as a hand-written color wins over any theme.
|
|
890
|
+
|
|
891
|
+
### Defaults
|
|
892
|
+
|
|
893
|
+
```
|
|
894
|
+
default node border: #4a8a5c
|
|
895
|
+
default leaf fill: #2e5d3a
|
|
896
|
+
default container fill: #1a2620 border: #2a3a30
|
|
897
|
+
default edge line: #7a8a99 text: (color: #9aa5b1, size: small)
|
|
898
|
+
```
|
|
899
|
+
|
|
900
|
+
A default is a style that every thing of one kind wears without naming it. `node` covers every node; `leaf` a node with no children, and `container` a node with some — the same "has children" the theme's quieter backdrop colors follow, so setting a leaf's fill leaves containers looking like containers. `edge` covers every edge.
|
|
901
|
+
|
|
902
|
+
The more specific word wins, all the way down:
|
|
903
|
+
|
|
904
|
+
1. the theme
|
|
905
|
+
2. `diagram`
|
|
906
|
+
3. `default node`
|
|
907
|
+
4. `default leaf` or `default container`, or `default edge`
|
|
908
|
+
5. the thing's styles, a later one winning where two set the same key
|
|
909
|
+
6. what the thing's own line says
|
|
910
|
+
|
|
911
|
+
A default takes the words a style does, and may name a style of its own with `style:`, which it lays beneath its own words. Unlike a style, it is strict about kind: it names the kind it is for, so a word that kind has no use for can only be a mistake. `default edge fill: red` is refused and points at `line:`. `badge:` is refused on `default leaf` and `default node`, since a badge is a child and would make every leaf a container, and `icon:` is a leaf's alone, since a picture cannot hold children. A `url:` belongs to one thing, never to a kind.
|
|
912
|
+
|
|
913
|
+
Each kind is written at most once. `default leaf` twice is an error naming both lines, and `default leaf node` is refused with `default leaf` — a leaf is already a node.
|
|
771
914
|
|
|
772
915
|
## What the language refuses
|
|
773
916
|
|
|
@@ -777,7 +920,7 @@ Deliberate omissions. What they protect is that the renderer never *chooses* an
|
|
|
777
920
|
- **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 node shares, not how far it sits from something.
|
|
778
921
|
- **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.
|
|
779
922
|
- **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.
|
|
780
|
-
- **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`
|
|
923
|
+
- **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` and `below <node>` are how you say it.
|
|
781
924
|
- **Set-level placement.** Four siblings around a hub are four statements today. Whether a durable group that reflows when a member is added is worth the same-axis conflict it introduces is undecided.
|
|
782
925
|
|
|
783
926
|
Note what is *not* on this list: saying more about where something goes. A statement that lets you be more precise is not a step toward auto-layout, and the first version was short enough of them to render the benchmark wrong.
|
|
@@ -786,7 +929,7 @@ Note what is *not* on this list: saying more about where something goes. A state
|
|
|
786
929
|
|
|
787
930
|
Designed, decided, and absent from the code. Written down so the next version has somewhere to start.
|
|
788
931
|
|
|
789
|
-
**Nothing keeps an edge clear of a node on its own.** Non-overlap applies to nodes only. A line may still cut across a node it has nothing to do with, and an edge text may still land on top of one. `between`
|
|
932
|
+
**Nothing keeps an edge clear of a node on its own.** Non-overlap applies to nodes only. A line may still cut across a node it has nothing to do with, and an edge text may still land on top of one. `between` and `below <node>` are how you say where a line goes when that matters, and nothing checks the ones where you have not said. A check belongs on the diagnostics list, but finding a route by itself does not — see "What the language refuses".
|
|
790
933
|
|
|
791
934
|
**An icon outside the built-in seven.** The set is closed, and a diagram wanting a picture 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.
|
|
792
935
|
|
|
@@ -828,10 +971,23 @@ That one was found by testing the lexer, not by rendering — and it could not h
|
|
|
828
971
|
|
|
829
972
|
~~An edge text ignored the line break.~~ Fixed. ` / ` split a node's text and was never applied to an edge'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 text already occupied, so a one-line text sits exactly where it did.
|
|
830
973
|
|
|
974
|
+
~~An edge whose ends faced away from each other was drawn through its own nodes.~~ Fixed. `from: left to: right` with the far node further right was one curve bending out at both ends, and with the two nodes in a row it flattened into a straight line through both of them and anything between, its text landing on whatever was in the middle. It was not a matter of exact alignment: a node stepped down a little drew a tilted version of the same line. Such an edge now turns back in the gap between its nodes if there is room, and goes over the top of the row if not. See "Ends that face away from each other".
|
|
975
|
+
|
|
831
976
|
## Changelog
|
|
832
977
|
|
|
833
978
|
Pre-1.0, so the minor number is where a breaking change goes. Every removal below is refused by name with the replacement quoted, rather than dropped in silence — an older file stops with an error saying what to write instead.
|
|
834
979
|
|
|
980
|
+
**0.6.0**
|
|
981
|
+
|
|
982
|
+
- An edge whose ends face away from each other, such as `from: left to: right` with the far node to the right, goes around instead of through its own nodes: through the gap between them if there is room, over the top of the row if not. Several over one row take a lane each. Sides at right angles, such as `from: left to: top`, go around the same way when one faces away.
|
|
983
|
+
- `above`, `below`, `left of` and `right of` on an edge say which side of a node the line passes: `edge a -> b below c`. Any number, one per node; one naming several nodes covers the stretch between them too. One edge may mix the two kinds, `below m left of n`, and the line turns between them as often as they need. Clauses that cannot all be drawn are refused with the reason.
|
|
984
|
+
|
|
985
|
+
**0.5.0**
|
|
986
|
+
|
|
987
|
+
- Named themes: `diagram theme: nord`, one of thirteen, and `--theme` on the command line to render a file in another without editing it. The playground has a theme picker, which writes the `theme:` line into the source.
|
|
988
|
+
- `diagram text: (color: …)` sets every text's color at once.
|
|
989
|
+
- `default node | leaf | container | edge` sets what every thing of that kind looks like unless it says otherwise.
|
|
990
|
+
|
|
835
991
|
**0.4.0**
|
|
836
992
|
|
|
837
993
|
- A node with any children takes the theme's container colors, however the children are placed. A node with a `badge:` or a single child in a corner now draws as a container too; in 0.3.0 only a node with a title band did. Its text still sits where it did. Say `fill:` and `border:` to keep one looking like a leaf.
|
|
@@ -860,6 +1016,7 @@ Pre-1.0, so the minor number is where a breaking change goes. Every removal belo
|
|
|
860
1016
|
|
|
861
1017
|
Open questions the benchmark raised, recorded so a later session does not rediscover them.
|
|
862
1018
|
|
|
1019
|
+
- One edge passing some nodes above or below and others left or right — an L-shaped line, across under one node and then down past another — is refused today. Often the picture leaves only one order for the two legs; where two orders both fit, the tool must not pick one, and whether it asks for another clause or follows a standing rule is not decided.
|
|
863
1020
|
- 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.
|
|
864
1021
|
- Four machines each holding a `files` child with the same text means writing the same line four times. This is the strongest case for a set-level declaration, for terseness rather than for placement.
|
|
865
1022
|
- 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.
|
package/dist/ast.d.ts
CHANGED
|
@@ -269,6 +269,13 @@ export interface EdgeStmt {
|
|
|
269
269
|
textAttrs: Attrs;
|
|
270
270
|
/** `between desktop1 and laptop1` — the gap the line passes through. */
|
|
271
271
|
between?: Passage;
|
|
272
|
+
/**
|
|
273
|
+
* `below resolver`, `left of a and b` — which side of a node the line is on
|
|
274
|
+
* where it passes that node. The placement words, because it is the same
|
|
275
|
+
* statement about the picture; on an edge it binds only the stretch where the
|
|
276
|
+
* line is passing, as `between` does. Always `kind: 'offset'` with no gap.
|
|
277
|
+
*/
|
|
278
|
+
passes?: OffsetPlacement[];
|
|
272
279
|
attrs: Attrs;
|
|
273
280
|
line: number;
|
|
274
281
|
}
|
|
@@ -282,8 +289,38 @@ export interface DiagramStmt {
|
|
|
282
289
|
attrs: Attrs;
|
|
283
290
|
line: number;
|
|
284
291
|
}
|
|
285
|
-
/**
|
|
286
|
-
|
|
292
|
+
/**
|
|
293
|
+
* The attributes a `diagram` statement understands. `text` takes a bracket,
|
|
294
|
+
* `text: (color: …)`, and sets the text color of everything at once — the
|
|
295
|
+
* theme has one text color, shared by nodes and edges, and this is that.
|
|
296
|
+
*/
|
|
297
|
+
export declare const DIAGRAM_KEYS: readonly ["theme", "background", "text"];
|
|
298
|
+
/**
|
|
299
|
+
* `default leaf fill: #2e5d3a` — a style that applies to every thing of one
|
|
300
|
+
* kind without being named. The more specific wins: `node` covers every node,
|
|
301
|
+
* `leaf` and `container` beat it for the nodes they cover, and a thing's own
|
|
302
|
+
* styles and words beat any default.
|
|
303
|
+
*/
|
|
304
|
+
export interface DefaultStmt {
|
|
305
|
+
kind: 'default';
|
|
306
|
+
target: DefaultTarget;
|
|
307
|
+
attrs: Attrs;
|
|
308
|
+
line: number;
|
|
309
|
+
}
|
|
310
|
+
export declare const DEFAULT_TARGETS: readonly ["node", "leaf", "container", "edge"];
|
|
311
|
+
export type DefaultTarget = (typeof DEFAULT_TARGETS)[number];
|
|
312
|
+
/**
|
|
313
|
+
* What each default may say. A default carries a style's vocabulary, less the
|
|
314
|
+
* words that would contradict the kind it is written for:
|
|
315
|
+
*
|
|
316
|
+
* - A default names its kind, so it is strict where a style is permissive —
|
|
317
|
+
* `default edge fill:` can only be a mistake.
|
|
318
|
+
* - `badge:` gives a box a child, which makes it a container, so every leaf
|
|
319
|
+
* given one by `default leaf` or `default node` would stop being a leaf.
|
|
320
|
+
* - `icon:` draws a picture, and a picture cannot hold children, so it is a
|
|
321
|
+
* leaf's word and not a container's or every node's.
|
|
322
|
+
*/
|
|
323
|
+
export declare const DEFAULT_KEYS: Record<DefaultTarget, readonly string[]>;
|
|
287
324
|
/**
|
|
288
325
|
* The attributes whose value is a color rather than text. A color is written
|
|
289
326
|
* as the viewer will receive it and the renderer keeps no list of color words
|
|
@@ -365,7 +402,7 @@ export interface StyleStmt {
|
|
|
365
402
|
attrs: Attrs;
|
|
366
403
|
line: number;
|
|
367
404
|
}
|
|
368
|
-
export type Stmt = NodeStmt | EdgeStmt | StyleStmt | DiagramStmt;
|
|
405
|
+
export type Stmt = NodeStmt | EdgeStmt | StyleStmt | DiagramStmt | DefaultStmt;
|
|
369
406
|
export interface Document {
|
|
370
407
|
statements: Stmt[];
|
|
371
408
|
}
|
package/dist/ast.js
CHANGED
|
@@ -222,8 +222,30 @@ export function describeAxis(axis) {
|
|
|
222
222
|
* `align` is how its lines range against each other once it is there.
|
|
223
223
|
*/
|
|
224
224
|
export const TEXT_KEYS = ['color', 'size', 'wrap', 'align', 'at'];
|
|
225
|
-
/**
|
|
226
|
-
|
|
225
|
+
/**
|
|
226
|
+
* The attributes a `diagram` statement understands. `text` takes a bracket,
|
|
227
|
+
* `text: (color: …)`, and sets the text color of everything at once — the
|
|
228
|
+
* theme has one text color, shared by nodes and edges, and this is that.
|
|
229
|
+
*/
|
|
230
|
+
export const DIAGRAM_KEYS = ['theme', 'background', 'text'];
|
|
231
|
+
export const DEFAULT_TARGETS = ['node', 'leaf', 'container', 'edge'];
|
|
232
|
+
/**
|
|
233
|
+
* What each default may say. A default carries a style's vocabulary, less the
|
|
234
|
+
* words that would contradict the kind it is written for:
|
|
235
|
+
*
|
|
236
|
+
* - A default names its kind, so it is strict where a style is permissive —
|
|
237
|
+
* `default edge fill:` can only be a mistake.
|
|
238
|
+
* - `badge:` gives a box a child, which makes it a container, so every leaf
|
|
239
|
+
* given one by `default leaf` or `default node` would stop being a leaf.
|
|
240
|
+
* - `icon:` draws a picture, and a picture cannot hold children, so it is a
|
|
241
|
+
* leaf's word and not a container's or every node's.
|
|
242
|
+
*/
|
|
243
|
+
export const DEFAULT_KEYS = {
|
|
244
|
+
node: ['style', 'shape', 'fill', 'border', 'text'],
|
|
245
|
+
leaf: ['style', 'shape', 'icon', 'fill', 'border', 'text'],
|
|
246
|
+
container: ['style', 'shape', 'badge', 'fill', 'border', 'text'],
|
|
247
|
+
edge: ['style', 'line', 'text'],
|
|
248
|
+
};
|
|
227
249
|
/**
|
|
228
250
|
* The attributes whose value is a color rather than text. A color is written
|
|
229
251
|
* as the viewer will receive it and the renderer keeps no list of color words
|
package/dist/cli.js
CHANGED
|
@@ -3,12 +3,15 @@ import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
|
3
3
|
import { dirname, resolve as resolvePath } from 'node:path';
|
|
4
4
|
import { SourceError } from './errors.js';
|
|
5
5
|
import { compile } from './index.js';
|
|
6
|
+
import { THEME_NAMES, THEMES } from './themes.js';
|
|
6
7
|
const USAGE = `reladraw — render a diagram from stated placement
|
|
7
8
|
|
|
8
|
-
reladraw <input.reladraw> [-o <output.svg>]
|
|
9
|
+
reladraw <input.reladraw> [-o <output.svg>] [--theme <name>]
|
|
9
10
|
|
|
10
11
|
-o, --out where to write the SVG. Defaults to the input path with
|
|
11
12
|
its extension replaced by .svg. Use - for standard output.
|
|
13
|
+
--theme render in this theme, whatever the file's \`diagram theme:\`
|
|
14
|
+
says. One of ${THEME_NAMES.join(', ')}.
|
|
12
15
|
-h, --help print this.
|
|
13
16
|
`;
|
|
14
17
|
async function main(argv) {
|
|
@@ -18,6 +21,7 @@ async function main(argv) {
|
|
|
18
21
|
}
|
|
19
22
|
let input;
|
|
20
23
|
let out;
|
|
24
|
+
let theme;
|
|
21
25
|
for (let i = 0; i < argv.length; i += 1) {
|
|
22
26
|
const arg = argv[i];
|
|
23
27
|
if (arg === '-o' || arg === '--out') {
|
|
@@ -28,6 +32,18 @@ async function main(argv) {
|
|
|
28
32
|
}
|
|
29
33
|
i += 1;
|
|
30
34
|
}
|
|
35
|
+
else if (arg === '--theme') {
|
|
36
|
+
theme = argv[i + 1];
|
|
37
|
+
if (theme === undefined) {
|
|
38
|
+
process.stderr.write(`reladraw: --theme needs a name: ${THEME_NAMES.join(', ')}\n`);
|
|
39
|
+
return 1;
|
|
40
|
+
}
|
|
41
|
+
if (THEMES[theme] === undefined) {
|
|
42
|
+
process.stderr.write(`reladraw: there is no theme called "${theme}" — the themes are ${THEME_NAMES.join(', ')}\n`);
|
|
43
|
+
return 1;
|
|
44
|
+
}
|
|
45
|
+
i += 1;
|
|
46
|
+
}
|
|
31
47
|
else if (arg.startsWith('-') && arg !== '-') {
|
|
32
48
|
process.stderr.write(`reladraw: unknown option ${arg}\n`);
|
|
33
49
|
return 1;
|
|
@@ -47,7 +63,7 @@ async function main(argv) {
|
|
|
47
63
|
const source = await readFile(input, 'utf8');
|
|
48
64
|
let svg;
|
|
49
65
|
try {
|
|
50
|
-
svg = compile(source);
|
|
66
|
+
svg = compile(source, theme === undefined ? {} : { theme: THEMES[theme] });
|
|
51
67
|
}
|
|
52
68
|
catch (error) {
|
|
53
69
|
if (error instanceof SourceError) {
|
package/dist/grammar.d.ts
CHANGED
|
@@ -61,7 +61,7 @@ export interface Span {
|
|
|
61
61
|
* it. A word missing here is a word that draws in the plain color, which is a
|
|
62
62
|
* dull page rather than a wrong one.
|
|
63
63
|
*/
|
|
64
|
-
export declare const STATEMENT_KEYWORDS: readonly ["node", "edge", "style", "diagram"];
|
|
64
|
+
export declare const STATEMENT_KEYWORDS: readonly ["node", "edge", "style", "diagram", "default"];
|
|
65
65
|
/**
|
|
66
66
|
* Every word that says something about where a thing goes. Assembled from the
|
|
67
67
|
* lists the parser itself reads, so a direction or a passage axis added there
|
package/dist/grammar.js
CHANGED
|
@@ -30,7 +30,7 @@ import { DIRECTIONS, POSITIONS, SIDES, PASSAGE_AXES } from './ast.js';
|
|
|
30
30
|
* it. A word missing here is a word that draws in the plain color, which is a
|
|
31
31
|
* dull page rather than a wrong one.
|
|
32
32
|
*/
|
|
33
|
-
export const STATEMENT_KEYWORDS = ['node', 'edge', 'style', 'diagram'];
|
|
33
|
+
export const STATEMENT_KEYWORDS = ['node', 'edge', 'style', 'diagram', 'default'];
|
|
34
34
|
/** Statements whose second word declares a name. `diagram` has none. */
|
|
35
35
|
const DECLARES_NAME = ['node', 'style'];
|
|
36
36
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -6,7 +6,8 @@ export * from './measure.js';
|
|
|
6
6
|
export * from './model.js';
|
|
7
7
|
export { parse } from './parser.js';
|
|
8
8
|
export { resolve, type ResolveOptions } from './resolve.js';
|
|
9
|
-
export { render,
|
|
9
|
+
export { render, type RenderOptions } from './render.js';
|
|
10
|
+
export { DARK_THEME, DEFAULT_THEME, THEMES, THEME_NAMES, type Theme } from './themes.js';
|
|
10
11
|
import { type RenderOptions } from './render.js';
|
|
11
12
|
import { type ResolveOptions } from './resolve.js';
|
|
12
13
|
/** Source text in, SVG out. The whole pipeline in one call. */
|
package/dist/index.js
CHANGED
|
@@ -6,7 +6,8 @@ export * from './measure.js';
|
|
|
6
6
|
export * from './model.js';
|
|
7
7
|
export { parse } from './parser.js';
|
|
8
8
|
export { resolve } from './resolve.js';
|
|
9
|
-
export { render
|
|
9
|
+
export { render } from './render.js';
|
|
10
|
+
export { DARK_THEME, DEFAULT_THEME, THEMES, THEME_NAMES } from './themes.js';
|
|
10
11
|
import { parse } from './parser.js';
|
|
11
12
|
import { render } from './render.js';
|
|
12
13
|
import { resolve } from './resolve.js';
|
package/dist/model.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Attrs, Axis, Kind, Placement } from './ast.js';
|
|
1
|
+
import type { Attrs, Axis, Direction, Kind, Placement } from './ast.js';
|
|
2
2
|
import type { Line } from './text.js';
|
|
3
3
|
import type { Body } from './icons.js';
|
|
4
4
|
/** A `between` clause with its targets resolved. Mirrors `Passage` in `ast.ts`. */
|
|
@@ -7,6 +7,17 @@ export interface LayoutPassage {
|
|
|
7
7
|
/** Which gap, where the pair has two. Absent when the pair leaves no doubt. */
|
|
8
8
|
axis?: Axis;
|
|
9
9
|
}
|
|
10
|
+
/**
|
|
11
|
+
* `below resolver` on an edge with its targets resolved: the line is on that
|
|
12
|
+
* side of the box bounding `nodes`, where it passes them. Mirrors the edge's
|
|
13
|
+
* `passes` in `ast.ts`.
|
|
14
|
+
*/
|
|
15
|
+
export interface LayoutPass {
|
|
16
|
+
direction: Direction;
|
|
17
|
+
nodes: LayoutNode[];
|
|
18
|
+
/** As the author wrote it, for error messages. */
|
|
19
|
+
written: string;
|
|
20
|
+
}
|
|
10
21
|
/** A node with its geometry solved. Coordinates are absolute, origin top-left. */
|
|
11
22
|
/** A distance past each side of a box. */
|
|
12
23
|
export interface Reach {
|
|
@@ -116,6 +127,8 @@ export interface LayoutEdge {
|
|
|
116
127
|
* rather than solved for, so edges stay out of the constraint system entirely.
|
|
117
128
|
*/
|
|
118
129
|
between?: LayoutPassage;
|
|
130
|
+
/** Which side of which nodes the line passes. Measured in the renderer, like `between`. */
|
|
131
|
+
passes?: LayoutPass[];
|
|
119
132
|
attrs: Attrs;
|
|
120
133
|
appearance: Attrs;
|
|
121
134
|
line: number;
|