diff --git a/ci/release/changelogs/next.md b/ci/release/changelogs/next.md
index 63cd026c6..9092a7ff6 100644
--- a/ci/release/changelogs/next.md
+++ b/ci/release/changelogs/next.md
@@ -1,6 +1,7 @@
#### Features ๐
- Icons: connections can include icons [#12](https://github.com/terrastruct/d2/issues/12)
+- Syntax: `suspend`/`unsuspend` to define models and instantiate them [#2394](https://github.com/terrastruct/d2/pull/2394)
#### Improvements ๐งน
diff --git a/d2ast/d2ast.go b/d2ast/d2ast.go
index b0e9386e9..807b7c93c 100644
--- a/d2ast/d2ast.go
+++ b/d2ast/d2ast.go
@@ -56,6 +56,7 @@ var _ Node = &Comment{}
var _ Node = &BlockComment{}
var _ Node = &Null{}
+var _ Node = &Suspension{}
var _ Node = &Boolean{}
var _ Node = &Number{}
var _ Node = &UnquotedString{}
@@ -329,6 +330,7 @@ type Scalar interface {
// See String for rest.
var _ Scalar = &Null{}
+var _ Scalar = &Suspension{}
var _ Scalar = &Boolean{}
var _ Scalar = &Number{}
@@ -349,6 +351,7 @@ var _ String = &BlockString{}
func (c *Comment) node() {}
func (c *BlockComment) node() {}
func (n *Null) node() {}
+func (n *Suspension) node() {}
func (b *Boolean) node() {}
func (n *Number) node() {}
func (s *UnquotedString) node() {}
@@ -367,6 +370,7 @@ func (i *EdgeIndex) node() {}
func (c *Comment) Type() string { return "comment" }
func (c *BlockComment) Type() string { return "block comment" }
func (n *Null) Type() string { return "null" }
+func (n *Suspension) Type() string { return "suspension" }
func (b *Boolean) Type() string { return "boolean" }
func (n *Number) Type() string { return "number" }
func (s *UnquotedString) Type() string { return "unquoted string" }
@@ -385,6 +389,7 @@ func (i *EdgeIndex) Type() string { return "edge index" }
func (c *Comment) GetRange() Range { return c.Range }
func (c *BlockComment) GetRange() Range { return c.Range }
func (n *Null) GetRange() Range { return n.Range }
+func (n *Suspension) GetRange() Range { return n.Range }
func (b *Boolean) GetRange() Range { return b.Range }
func (n *Number) GetRange() Range { return n.Range }
func (s *UnquotedString) GetRange() Range { return s.Range }
@@ -409,6 +414,7 @@ func (i *Import) mapNode() {}
func (c *Comment) arrayNode() {}
func (c *BlockComment) arrayNode() {}
func (n *Null) arrayNode() {}
+func (n *Suspension) arrayNode() {}
func (b *Boolean) arrayNode() {}
func (n *Number) arrayNode() {}
func (s *UnquotedString) arrayNode() {}
@@ -421,6 +427,7 @@ func (a *Array) arrayNode() {}
func (m *Map) arrayNode() {}
func (n *Null) value() {}
+func (n *Suspension) value() {}
func (b *Boolean) value() {}
func (n *Number) value() {}
func (s *UnquotedString) value() {}
@@ -432,6 +439,7 @@ func (m *Map) value() {}
func (i *Import) value() {}
func (n *Null) scalar() {}
+func (n *Suspension) scalar() {}
func (b *Boolean) scalar() {}
func (n *Number) scalar() {}
func (s *UnquotedString) scalar() {}
@@ -442,6 +450,7 @@ func (s *BlockString) scalar() {}
func (c *Comment) Children() []Node { return nil }
func (c *BlockComment) Children() []Node { return nil }
func (n *Null) Children() []Node { return nil }
+func (n *Suspension) Children() []Node { return nil }
func (b *Boolean) Children() []Node { return nil }
func (n *Number) Children() []Node { return nil }
func (s *SingleQuotedString) Children() []Node { return nil }
@@ -573,9 +582,10 @@ func Walk(node Node, fn func(Node) bool) {
}
// TODO: mistake, move into parse.go
-func (n *Null) ScalarString() string { return "" }
-func (b *Boolean) ScalarString() string { return strconv.FormatBool(b.Value) }
-func (n *Number) ScalarString() string { return n.Raw }
+func (n *Null) ScalarString() string { return "" }
+func (n *Suspension) ScalarString() string { return "" }
+func (b *Boolean) ScalarString() string { return strconv.FormatBool(b.Value) }
+func (n *Number) ScalarString() string { return n.Raw }
func (s *UnquotedString) ScalarString() string {
if len(s.Value) == 0 {
return ""
@@ -631,6 +641,11 @@ type Null struct {
Range Range `json:"range"`
}
+type Suspension struct {
+ Range Range `json:"range"`
+ Value bool `json:"value"`
+}
+
type Boolean struct {
Range Range `json:"range"`
Value bool `json:"value"`
@@ -1369,6 +1384,7 @@ func (ab ArrayNodeBox) Unbox() ArrayNode {
// ValueBox is used to box Value for JSON persistence.
type ValueBox struct {
Null *Null `json:"null,omitempty"`
+ Suspension *Suspension `json:"suspension,omitempty"`
Boolean *Boolean `json:"boolean,omitempty"`
Number *Number `json:"number,omitempty"`
UnquotedString *UnquotedString `json:"unquoted_string,omitempty"`
@@ -1384,6 +1400,8 @@ func (vb ValueBox) Unbox() Value {
switch {
case vb.Null != nil:
return vb.Null
+ case vb.Suspension != nil:
+ return vb.Suspension
case vb.Boolean != nil:
return vb.Boolean
case vb.Number != nil:
@@ -1412,6 +1430,8 @@ func MakeValueBox(v Value) ValueBox {
switch v := v.(type) {
case *Null:
vb.Null = v
+ case *Suspension:
+ vb.Suspension = v
case *Boolean:
vb.Boolean = v
case *Number:
@@ -1437,6 +1457,7 @@ func MakeValueBox(v Value) ValueBox {
func (vb ValueBox) ScalarBox() ScalarBox {
var sb ScalarBox
sb.Null = vb.Null
+ sb.Suspension = vb.Suspension
sb.Boolean = vb.Boolean
sb.Number = vb.Number
sb.UnquotedString = vb.UnquotedString
@@ -1459,6 +1480,7 @@ func (vb ValueBox) StringBox() *StringBox {
// TODO: implement ScalarString()
type ScalarBox struct {
Null *Null `json:"null,omitempty"`
+ Suspension *Suspension `json:"suspension,omitempty"`
Boolean *Boolean `json:"boolean,omitempty"`
Number *Number `json:"number,omitempty"`
UnquotedString *UnquotedString `json:"unquoted_string,omitempty"`
@@ -1471,6 +1493,8 @@ func (sb ScalarBox) Unbox() Scalar {
switch {
case sb.Null != nil:
return sb.Null
+ case sb.Suspension != nil:
+ return sb.Suspension
case sb.Boolean != nil:
return sb.Boolean
case sb.Number != nil:
@@ -1559,7 +1583,7 @@ func RawString(s string, inKey bool) String {
return &SingleQuotedString{Value: s}
}
}
- } else if s == "null" || strings.ContainsAny(s, UnquotedValueSpecials) {
+ } else if s == "null" || s == "suspend" || s == "unsuspend" || strings.ContainsAny(s, UnquotedValueSpecials) {
if !strings.ContainsRune(s, '"') && !strings.ContainsRune(s, '$') {
return FlatDoubleQuotedString(s)
}
diff --git a/d2compiler/compile_test.go b/d2compiler/compile_test.go
index 5928cd164..cabe05031 100644
--- a/d2compiler/compile_test.go
+++ b/d2compiler/compile_test.go
@@ -5471,6 +5471,34 @@ a.width: 339
assert.Equal(t, 5, len(g.Objects))
},
},
+ {
+ name: "suspension-lazy",
+ run: func(t *testing.T) {
+ g, _ := assertCompile(t, `
+a -> b
+c
+**: suspend
+(** -> **)[*]: suspend
+d
+`, ``)
+ assert.Equal(t, 1, len(g.Objects))
+ },
+ },
+ {
+ name: "suspension-quotes",
+ run: func(t *testing.T) {
+ g, _ := assertCompile(t, `
+a -> b
+c
+**: suspend
+(** -> **)[*]: suspend
+d: "suspend"
+d -> d: "suspend"
+`, ``)
+ assert.Equal(t, 1, len(g.Objects))
+ assert.Equal(t, 1, len(g.Edges))
+ },
+ },
}
for _, tc := range tca {
diff --git a/d2ir/compile.go b/d2ir/compile.go
index 668bc9190..5e1647ea0 100644
--- a/d2ir/compile.go
+++ b/d2ir/compile.go
@@ -81,6 +81,7 @@ func Compile(ast *d2ast.Map, opts *CompileOptions) (*Map, []string, error) {
c.compileMap(m, ast, ast)
c.compileSubstitutions(m, nil)
c.overlayClasses(m)
+ m.removeSuspendedFields()
if !c.err.Empty() {
return nil, nil, c.err
}
@@ -866,6 +867,17 @@ func (c *compiler) _compileField(f *Field, refctx *RefContext) {
}
}
+ if len(refctx.Key.Edges) == 0 && (refctx.Key.Primary.Suspension != nil || refctx.Key.Value.Suspension != nil) {
+ if !c.lazyGlobBeingApplied {
+ if refctx.Key.Primary.Suspension != nil {
+ f.suspended = refctx.Key.Primary.Suspension.Value
+ } else {
+ f.suspended = refctx.Key.Value.Suspension.Value
+ }
+ }
+ return
+ }
+
if refctx.Key.Primary.Unbox() != nil {
if c.ignoreLazyGlob(f) {
return
@@ -1164,6 +1176,17 @@ func (c *compiler) _compileEdges(refctx *RefContext) {
refctx.ScopeMap.DeleteEdge(e.ID)
continue
}
+
+ if refctx.Key.Primary.Suspension != nil || refctx.Key.Value.Suspension != nil {
+ if !c.lazyGlobBeingApplied {
+ if refctx.Key.Primary.Suspension != nil {
+ e.suspended = refctx.Key.Primary.Suspension.Value
+ } else {
+ e.suspended = refctx.Key.Value.Suspension.Value
+ }
+ }
+ }
+
e.References = append(e.References, &EdgeReference{
Context_: refctx,
DueToGlob_: len(c.globRefContextStack) > 0,
@@ -1289,3 +1312,39 @@ func (c *compiler) compileArray(dst *Array, a *d2ast.Array, scopeAST *d2ast.Map)
dst.Values = append(dst.Values, irv)
}
}
+
+func (m *Map) removeSuspendedFields() {
+ if m == nil {
+ return
+ }
+
+ for _, f := range m.Fields {
+ if f.Map() != nil {
+ f.Map().removeSuspendedFields()
+ }
+ }
+
+ for i := len(m.Fields) - 1; i >= 0; i-- {
+ if m.Fields[i].Name == nil {
+ continue
+ }
+ _, isReserved := d2ast.ReservedKeywords[m.Fields[i].Name.ScalarString()]
+ if isReserved {
+ continue
+ }
+ if m.Fields[i].suspended {
+ m.DeleteField(m.Fields[i].Name.ScalarString())
+ }
+ }
+
+ for _, e := range m.Edges {
+ if e.Map() != nil {
+ e.Map().removeSuspendedFields()
+ }
+ }
+ for i := len(m.Edges) - 1; i >= 0; i-- {
+ if m.Edges[i].suspended {
+ m.DeleteEdge(m.Edges[i].ID)
+ }
+ }
+}
diff --git a/d2ir/d2ir.go b/d2ir/d2ir.go
index d5d014515..4ea360b92 100644
--- a/d2ir/d2ir.go
+++ b/d2ir/d2ir.go
@@ -318,6 +318,7 @@ type Field struct {
// *Map.
parent Node
importAST d2ast.Node
+ suspended bool
Name d2ast.String `json:"name"`
@@ -488,6 +489,7 @@ type Edge struct {
// *Map
parent Node
importAST d2ast.Node
+ suspended bool
ID *EdgeID `json:"edge_id"`
diff --git a/d2parser/parse.go b/d2parser/parse.go
index 3eb500484..e9d57b0ec 100644
--- a/d2parser/parse.go
+++ b/d2parser/parse.go
@@ -1668,6 +1668,20 @@ func (p *parser) parseValue() d2ast.ValueBox {
}
return box
}
+ if strings.EqualFold(s.ScalarString(), "suspend") {
+ box.Suspension = &d2ast.Suspension{
+ Range: s.Range,
+ Value: true,
+ }
+ return box
+ }
+ if strings.EqualFold(s.ScalarString(), "unsuspend") {
+ box.Suspension = &d2ast.Suspension{
+ Range: s.Range,
+ Value: false,
+ }
+ return box
+ }
if strings.EqualFold(s.ScalarString(), "true") {
box.Boolean = &d2ast.Boolean{
diff --git a/e2etests/testdata/txtar/model-view/dagre/board.exp.json b/e2etests/testdata/txtar/model-view/dagre/board.exp.json
new file mode 100644
index 000000000..901a987f6
--- /dev/null
+++ b/e2etests/testdata/txtar/model-view/dagre/board.exp.json
@@ -0,0 +1,322 @@
+{
+ "name": "",
+ "config": {
+ "sketch": false,
+ "themeID": 0,
+ "darkThemeID": null,
+ "pad": null,
+ "center": null,
+ "layoutEngine": null
+ },
+ "isFolderOnly": false,
+ "fontFamily": "SourceSansPro",
+ "shapes": [
+ {
+ "id": "user",
+ "type": "rectangle",
+ "pos": {
+ "x": 0,
+ "y": 50
+ },
+ "width": 77,
+ "height": 66,
+ "opacity": 1,
+ "strokeDash": 0,
+ "strokeWidth": 2,
+ "borderRadius": 0,
+ "fill": "blue",
+ "stroke": "B1",
+ "animated": false,
+ "shadow": false,
+ "3d": false,
+ "multiple": false,
+ "double-border": false,
+ "tooltip": "",
+ "link": "",
+ "icon": null,
+ "iconPosition": "",
+ "blend": false,
+ "fields": null,
+ "methods": null,
+ "columns": null,
+ "label": "user",
+ "fontSize": 16,
+ "fontFamily": "DEFAULT",
+ "language": "",
+ "color": "N1",
+ "italic": false,
+ "bold": true,
+ "underline": false,
+ "labelWidth": 32,
+ "labelHeight": 21,
+ "labelPosition": "INSIDE_MIDDLE_CENTER",
+ "zIndex": 0,
+ "level": 1
+ },
+ {
+ "id": "softwareSystem",
+ "type": "rectangle",
+ "pos": {
+ "x": 127,
+ "y": 20
+ },
+ "width": 331,
+ "height": 126,
+ "opacity": 1,
+ "strokeDash": 0,
+ "strokeWidth": 2,
+ "borderRadius": 0,
+ "fill": "B4",
+ "stroke": "B1",
+ "animated": false,
+ "shadow": false,
+ "3d": false,
+ "multiple": false,
+ "double-border": false,
+ "tooltip": "",
+ "link": "",
+ "icon": null,
+ "iconPosition": "",
+ "blend": false,
+ "fields": null,
+ "methods": null,
+ "columns": null,
+ "label": "softwareSystem",
+ "fontSize": 28,
+ "fontFamily": "DEFAULT",
+ "language": "",
+ "color": "N1",
+ "italic": false,
+ "bold": false,
+ "underline": false,
+ "labelWidth": 188,
+ "labelHeight": 36,
+ "labelPosition": "OUTSIDE_TOP_CENTER",
+ "zIndex": 0,
+ "level": 1
+ },
+ {
+ "id": "softwareSystem.serviceA",
+ "type": "rectangle",
+ "classes": [
+ "ok"
+ ],
+ "pos": {
+ "x": 157,
+ "y": 50
+ },
+ "width": 106,
+ "height": 66,
+ "opacity": 1,
+ "strokeDash": 0,
+ "strokeWidth": 2,
+ "borderRadius": 0,
+ "fill": "B5",
+ "stroke": "B1",
+ "animated": false,
+ "shadow": false,
+ "3d": false,
+ "multiple": false,
+ "double-border": false,
+ "tooltip": "",
+ "link": "",
+ "icon": null,
+ "iconPosition": "",
+ "blend": false,
+ "fields": null,
+ "methods": null,
+ "columns": null,
+ "label": "serviceA",
+ "fontSize": 16,
+ "fontFamily": "DEFAULT",
+ "language": "",
+ "color": "N1",
+ "italic": false,
+ "bold": true,
+ "underline": false,
+ "labelWidth": 61,
+ "labelHeight": 21,
+ "labelPosition": "INSIDE_MIDDLE_CENTER",
+ "zIndex": 0,
+ "level": 2
+ },
+ {
+ "id": "softwareSystem.serviceC",
+ "type": "rectangle",
+ "classes": [
+ "ok"
+ ],
+ "pos": {
+ "x": 323,
+ "y": 50
+ },
+ "width": 105,
+ "height": 66,
+ "opacity": 1,
+ "strokeDash": 0,
+ "strokeWidth": 2,
+ "borderRadius": 0,
+ "fill": "B5",
+ "stroke": "B1",
+ "animated": false,
+ "shadow": false,
+ "3d": false,
+ "multiple": false,
+ "double-border": false,
+ "tooltip": "",
+ "link": "",
+ "icon": null,
+ "iconPosition": "",
+ "blend": false,
+ "fields": null,
+ "methods": null,
+ "columns": null,
+ "label": "serviceC",
+ "fontSize": 16,
+ "fontFamily": "DEFAULT",
+ "language": "",
+ "color": "N1",
+ "italic": false,
+ "bold": true,
+ "underline": false,
+ "labelWidth": 60,
+ "labelHeight": 21,
+ "labelPosition": "INSIDE_MIDDLE_CENTER",
+ "zIndex": 0,
+ "level": 2
+ },
+ {
+ "id": "externalSystem",
+ "type": "rectangle",
+ "pos": {
+ "x": 297,
+ "y": 266
+ },
+ "width": 157,
+ "height": 66,
+ "opacity": 1,
+ "strokeDash": 0,
+ "strokeWidth": 2,
+ "borderRadius": 0,
+ "fill": "B6",
+ "stroke": "B1",
+ "animated": false,
+ "shadow": false,
+ "3d": false,
+ "multiple": false,
+ "double-border": false,
+ "tooltip": "",
+ "link": "",
+ "icon": null,
+ "iconPosition": "",
+ "blend": false,
+ "fields": null,
+ "methods": null,
+ "columns": null,
+ "label": "externalSystem",
+ "fontSize": 16,
+ "fontFamily": "DEFAULT",
+ "language": "",
+ "color": "N1",
+ "italic": false,
+ "bold": true,
+ "underline": false,
+ "labelWidth": 112,
+ "labelHeight": 21,
+ "labelPosition": "INSIDE_MIDDLE_CENTER",
+ "zIndex": 0,
+ "level": 1
+ }
+ ],
+ "connections": [
+ {
+ "id": "(softwareSystem -> externalSystem)[0]",
+ "src": "softwareSystem",
+ "srcArrow": "none",
+ "dst": "externalSystem",
+ "dstArrow": "triangle",
+ "opacity": 1,
+ "strokeDash": 0,
+ "strokeWidth": 2,
+ "stroke": "B1",
+ "borderRadius": 10,
+ "label": "",
+ "fontSize": 16,
+ "fontFamily": "DEFAULT",
+ "language": "",
+ "color": "N2",
+ "italic": true,
+ "bold": false,
+ "underline": false,
+ "labelWidth": 0,
+ "labelHeight": 0,
+ "labelPosition": "",
+ "labelPercentage": 0,
+ "link": "",
+ "route": [
+ {
+ "x": 375.5,
+ "y": 146
+ },
+ {
+ "x": 375.5,
+ "y": 202
+ },
+ {
+ "x": 375.5,
+ "y": 226
+ },
+ {
+ "x": 375.5,
+ "y": 266
+ }
+ ],
+ "isCurve": true,
+ "animated": false,
+ "tooltip": "",
+ "icon": null,
+ "zIndex": 0
+ }
+ ],
+ "root": {
+ "id": "",
+ "type": "",
+ "pos": {
+ "x": 0,
+ "y": 0
+ },
+ "width": 0,
+ "height": 0,
+ "opacity": 0,
+ "strokeDash": 0,
+ "strokeWidth": 0,
+ "borderRadius": 0,
+ "fill": "N7",
+ "stroke": "",
+ "animated": false,
+ "shadow": false,
+ "3d": false,
+ "multiple": false,
+ "double-border": false,
+ "tooltip": "",
+ "link": "",
+ "icon": null,
+ "iconPosition": "",
+ "blend": false,
+ "fields": null,
+ "methods": null,
+ "columns": null,
+ "label": "",
+ "fontSize": 0,
+ "fontFamily": "",
+ "language": "",
+ "color": "",
+ "italic": false,
+ "bold": false,
+ "underline": false,
+ "labelWidth": 0,
+ "labelHeight": 0,
+ "zIndex": 0,
+ "level": 0
+ }
+}
diff --git a/e2etests/testdata/txtar/model-view/dagre/sketch.exp.svg b/e2etests/testdata/txtar/model-view/dagre/sketch.exp.svg
new file mode 100644
index 000000000..8773ebf93
--- /dev/null
+++ b/e2etests/testdata/txtar/model-view/dagre/sketch.exp.svg
@@ -0,0 +1,106 @@
+
\ No newline at end of file
diff --git a/e2etests/testdata/txtar/model-view/elk/board.exp.json b/e2etests/testdata/txtar/model-view/elk/board.exp.json
new file mode 100644
index 000000000..7a2d1c3a2
--- /dev/null
+++ b/e2etests/testdata/txtar/model-view/elk/board.exp.json
@@ -0,0 +1,313 @@
+{
+ "name": "",
+ "config": {
+ "sketch": false,
+ "themeID": 0,
+ "darkThemeID": null,
+ "pad": null,
+ "center": null,
+ "layoutEngine": null
+ },
+ "isFolderOnly": false,
+ "fontFamily": "SourceSansPro",
+ "shapes": [
+ {
+ "id": "user",
+ "type": "rectangle",
+ "pos": {
+ "x": 12,
+ "y": 62
+ },
+ "width": 77,
+ "height": 66,
+ "opacity": 1,
+ "strokeDash": 0,
+ "strokeWidth": 2,
+ "borderRadius": 0,
+ "fill": "blue",
+ "stroke": "B1",
+ "animated": false,
+ "shadow": false,
+ "3d": false,
+ "multiple": false,
+ "double-border": false,
+ "tooltip": "",
+ "link": "",
+ "icon": null,
+ "iconPosition": "",
+ "blend": false,
+ "fields": null,
+ "methods": null,
+ "columns": null,
+ "label": "user",
+ "fontSize": 16,
+ "fontFamily": "DEFAULT",
+ "language": "",
+ "color": "N1",
+ "italic": false,
+ "bold": true,
+ "underline": false,
+ "labelWidth": 32,
+ "labelHeight": 21,
+ "labelPosition": "INSIDE_MIDDLE_CENTER",
+ "zIndex": 0,
+ "level": 1
+ },
+ {
+ "id": "softwareSystem",
+ "type": "rectangle",
+ "pos": {
+ "x": 109,
+ "y": 12
+ },
+ "width": 331,
+ "height": 166,
+ "opacity": 1,
+ "strokeDash": 0,
+ "strokeWidth": 2,
+ "borderRadius": 0,
+ "fill": "B4",
+ "stroke": "B1",
+ "animated": false,
+ "shadow": false,
+ "3d": false,
+ "multiple": false,
+ "double-border": false,
+ "tooltip": "",
+ "link": "",
+ "icon": null,
+ "iconPosition": "",
+ "blend": false,
+ "fields": null,
+ "methods": null,
+ "columns": null,
+ "label": "softwareSystem",
+ "fontSize": 28,
+ "fontFamily": "DEFAULT",
+ "language": "",
+ "color": "N1",
+ "italic": false,
+ "bold": false,
+ "underline": false,
+ "labelWidth": 188,
+ "labelHeight": 36,
+ "labelPosition": "INSIDE_TOP_CENTER",
+ "zIndex": 0,
+ "level": 1
+ },
+ {
+ "id": "softwareSystem.serviceA",
+ "type": "rectangle",
+ "classes": [
+ "ok"
+ ],
+ "pos": {
+ "x": 159,
+ "y": 62
+ },
+ "width": 106,
+ "height": 66,
+ "opacity": 1,
+ "strokeDash": 0,
+ "strokeWidth": 2,
+ "borderRadius": 0,
+ "fill": "B5",
+ "stroke": "B1",
+ "animated": false,
+ "shadow": false,
+ "3d": false,
+ "multiple": false,
+ "double-border": false,
+ "tooltip": "",
+ "link": "",
+ "icon": null,
+ "iconPosition": "",
+ "blend": false,
+ "fields": null,
+ "methods": null,
+ "columns": null,
+ "label": "serviceA",
+ "fontSize": 16,
+ "fontFamily": "DEFAULT",
+ "language": "",
+ "color": "N1",
+ "italic": false,
+ "bold": true,
+ "underline": false,
+ "labelWidth": 61,
+ "labelHeight": 21,
+ "labelPosition": "INSIDE_MIDDLE_CENTER",
+ "zIndex": 0,
+ "level": 2
+ },
+ {
+ "id": "softwareSystem.serviceC",
+ "type": "rectangle",
+ "classes": [
+ "ok"
+ ],
+ "pos": {
+ "x": 285,
+ "y": 62
+ },
+ "width": 105,
+ "height": 66,
+ "opacity": 1,
+ "strokeDash": 0,
+ "strokeWidth": 2,
+ "borderRadius": 0,
+ "fill": "B5",
+ "stroke": "B1",
+ "animated": false,
+ "shadow": false,
+ "3d": false,
+ "multiple": false,
+ "double-border": false,
+ "tooltip": "",
+ "link": "",
+ "icon": null,
+ "iconPosition": "",
+ "blend": false,
+ "fields": null,
+ "methods": null,
+ "columns": null,
+ "label": "serviceC",
+ "fontSize": 16,
+ "fontFamily": "DEFAULT",
+ "language": "",
+ "color": "N1",
+ "italic": false,
+ "bold": true,
+ "underline": false,
+ "labelWidth": 60,
+ "labelHeight": 21,
+ "labelPosition": "INSIDE_MIDDLE_CENTER",
+ "zIndex": 0,
+ "level": 2
+ },
+ {
+ "id": "externalSystem",
+ "type": "rectangle",
+ "pos": {
+ "x": 196,
+ "y": 248
+ },
+ "width": 157,
+ "height": 66,
+ "opacity": 1,
+ "strokeDash": 0,
+ "strokeWidth": 2,
+ "borderRadius": 0,
+ "fill": "B6",
+ "stroke": "B1",
+ "animated": false,
+ "shadow": false,
+ "3d": false,
+ "multiple": false,
+ "double-border": false,
+ "tooltip": "",
+ "link": "",
+ "icon": null,
+ "iconPosition": "",
+ "blend": false,
+ "fields": null,
+ "methods": null,
+ "columns": null,
+ "label": "externalSystem",
+ "fontSize": 16,
+ "fontFamily": "DEFAULT",
+ "language": "",
+ "color": "N1",
+ "italic": false,
+ "bold": true,
+ "underline": false,
+ "labelWidth": 112,
+ "labelHeight": 21,
+ "labelPosition": "INSIDE_MIDDLE_CENTER",
+ "zIndex": 0,
+ "level": 1
+ }
+ ],
+ "connections": [
+ {
+ "id": "(softwareSystem -> externalSystem)[0]",
+ "src": "softwareSystem",
+ "srcArrow": "none",
+ "dst": "externalSystem",
+ "dstArrow": "triangle",
+ "opacity": 1,
+ "strokeDash": 0,
+ "strokeWidth": 2,
+ "stroke": "B1",
+ "borderRadius": 10,
+ "label": "",
+ "fontSize": 16,
+ "fontFamily": "DEFAULT",
+ "language": "",
+ "color": "N2",
+ "italic": true,
+ "bold": false,
+ "underline": false,
+ "labelWidth": 0,
+ "labelHeight": 0,
+ "labelPosition": "",
+ "labelPercentage": 0,
+ "link": "",
+ "route": [
+ {
+ "x": 274.5,
+ "y": 178
+ },
+ {
+ "x": 274.5,
+ "y": 248
+ }
+ ],
+ "animated": false,
+ "tooltip": "",
+ "icon": null,
+ "zIndex": 0
+ }
+ ],
+ "root": {
+ "id": "",
+ "type": "",
+ "pos": {
+ "x": 0,
+ "y": 0
+ },
+ "width": 0,
+ "height": 0,
+ "opacity": 0,
+ "strokeDash": 0,
+ "strokeWidth": 0,
+ "borderRadius": 0,
+ "fill": "N7",
+ "stroke": "",
+ "animated": false,
+ "shadow": false,
+ "3d": false,
+ "multiple": false,
+ "double-border": false,
+ "tooltip": "",
+ "link": "",
+ "icon": null,
+ "iconPosition": "",
+ "blend": false,
+ "fields": null,
+ "methods": null,
+ "columns": null,
+ "label": "",
+ "fontSize": 0,
+ "fontFamily": "",
+ "language": "",
+ "color": "",
+ "italic": false,
+ "bold": false,
+ "underline": false,
+ "labelWidth": 0,
+ "labelHeight": 0,
+ "zIndex": 0,
+ "level": 0
+ }
+}
diff --git a/e2etests/testdata/txtar/model-view/elk/sketch.exp.svg b/e2etests/testdata/txtar/model-view/elk/sketch.exp.svg
new file mode 100644
index 000000000..890eccd04
--- /dev/null
+++ b/e2etests/testdata/txtar/model-view/elk/sketch.exp.svg
@@ -0,0 +1,106 @@
+usersoftwareSystemexternalSystemserviceAserviceC
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/e2etests/txtar.txt b/e2etests/txtar.txt
index fcc6a7619..c4be32876 100644
--- a/e2etests/txtar.txt
+++ b/e2etests/txtar.txt
@@ -775,3 +775,33 @@ a -> b: hello {
b -> c: {
icon: https://icons.terrastruct.com/essentials%2F213-alarm.svg
}
+
+-- model-view --
+# Models
+user.style.fill: blue
+softwareSystem: {
+ serviceA.class: ok
+ serviceB
+ serviceC.class: ok
+ serviceD
+
+ serviceA -> serviceB
+ serviceA -> serviceD
+ serviceC -> serviceB
+}
+externalSystem
+user -> softwareSystem
+softwareSystem -> externalSystem
+
+# Clear models
+**: suspend
+(** -> **)[*]: suspend
+
+# Include all top-level objects
+*: unsuspend
+# Include all objects with a certain class
+**: unsuspend {
+ &class: ok
+}
+# Include all connections/objects connected to an object
+(** -> externalSystem)[*]: unsuspend
diff --git a/testdata/d2compiler/TestCompile2/globs/suspension-lazy.exp.json b/testdata/d2compiler/TestCompile2/globs/suspension-lazy.exp.json
new file mode 100644
index 000000000..78d2710a9
--- /dev/null
+++ b/testdata/d2compiler/TestCompile2/globs/suspension-lazy.exp.json
@@ -0,0 +1,274 @@
+{
+ "graph": {
+ "name": "",
+ "isFolderOnly": false,
+ "ast": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-lazy.d2,0:0:0-6:0:47",
+ "nodes": [
+ {
+ "map_key": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-lazy.d2,1:0:1-1:6:7",
+ "edges": [
+ {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-lazy.d2,1:0:1-1:6:7",
+ "src": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-lazy.d2,1:0:1-1:1:2",
+ "path": [
+ {
+ "unquoted_string": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-lazy.d2,1:0:1-1:1:2",
+ "value": [
+ {
+ "string": "a",
+ "raw_string": "a"
+ }
+ ]
+ }
+ }
+ ]
+ },
+ "src_arrow": "",
+ "dst": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-lazy.d2,1:5:6-1:6:7",
+ "path": [
+ {
+ "unquoted_string": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-lazy.d2,1:5:6-1:6:7",
+ "value": [
+ {
+ "string": "b",
+ "raw_string": "b"
+ }
+ ]
+ }
+ }
+ ]
+ },
+ "dst_arrow": ">"
+ }
+ ],
+ "primary": {},
+ "value": {}
+ }
+ },
+ {
+ "map_key": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-lazy.d2,2:0:8-2:1:9",
+ "key": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-lazy.d2,2:0:8-2:1:9",
+ "path": [
+ {
+ "unquoted_string": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-lazy.d2,2:0:8-2:1:9",
+ "value": [
+ {
+ "string": "c",
+ "raw_string": "c"
+ }
+ ]
+ }
+ }
+ ]
+ },
+ "primary": {},
+ "value": {}
+ }
+ },
+ {
+ "map_key": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-lazy.d2,3:0:10-3:11:21",
+ "key": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-lazy.d2,3:0:10-3:2:12",
+ "path": [
+ {
+ "unquoted_string": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-lazy.d2,3:0:10-3:2:12",
+ "value": [
+ {
+ "string": "**",
+ "raw_string": "**"
+ }
+ ],
+ "pattern": [
+ "*",
+ "",
+ "*"
+ ]
+ }
+ }
+ ]
+ },
+ "primary": {},
+ "value": {
+ "suspension": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-lazy.d2,3:4:14-3:11:21",
+ "value": true
+ }
+ }
+ }
+ },
+ {
+ "map_key": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-lazy.d2,4:0:22-4:22:44",
+ "edges": [
+ {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-lazy.d2,4:1:23-4:9:31",
+ "src": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-lazy.d2,4:1:23-4:3:25",
+ "path": [
+ {
+ "unquoted_string": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-lazy.d2,4:1:23-4:3:25",
+ "value": [
+ {
+ "string": "**",
+ "raw_string": "**"
+ }
+ ],
+ "pattern": [
+ "*",
+ "",
+ "*"
+ ]
+ }
+ }
+ ]
+ },
+ "src_arrow": "",
+ "dst": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-lazy.d2,4:7:29-4:9:31",
+ "path": [
+ {
+ "unquoted_string": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-lazy.d2,4:7:29-4:9:31",
+ "value": [
+ {
+ "string": "**",
+ "raw_string": "**"
+ }
+ ],
+ "pattern": [
+ "*",
+ "",
+ "*"
+ ]
+ }
+ }
+ ]
+ },
+ "dst_arrow": ">"
+ }
+ ],
+ "edge_index": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-lazy.d2,4:10:32-4:13:35",
+ "int": null,
+ "glob": true
+ },
+ "primary": {},
+ "value": {
+ "suspension": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-lazy.d2,4:15:37-4:22:44",
+ "value": true
+ }
+ }
+ }
+ },
+ {
+ "map_key": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-lazy.d2,5:0:45-5:1:46",
+ "key": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-lazy.d2,5:0:45-5:1:46",
+ "path": [
+ {
+ "unquoted_string": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-lazy.d2,5:0:45-5:1:46",
+ "value": [
+ {
+ "string": "d",
+ "raw_string": "d"
+ }
+ ]
+ }
+ }
+ ]
+ },
+ "primary": {},
+ "value": {}
+ }
+ }
+ ]
+ },
+ "root": {
+ "id": "",
+ "id_val": "",
+ "attributes": {
+ "label": {
+ "value": ""
+ },
+ "labelDimensions": {
+ "width": 0,
+ "height": 0
+ },
+ "style": {},
+ "near_key": null,
+ "shape": {
+ "value": ""
+ },
+ "direction": {
+ "value": ""
+ },
+ "constraint": null
+ },
+ "zIndex": 0
+ },
+ "edges": null,
+ "objects": [
+ {
+ "id": "d",
+ "id_val": "d",
+ "references": [
+ {
+ "key": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-lazy.d2,5:0:45-5:1:46",
+ "path": [
+ {
+ "unquoted_string": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-lazy.d2,5:0:45-5:1:46",
+ "value": [
+ {
+ "string": "d",
+ "raw_string": "d"
+ }
+ ]
+ }
+ }
+ ]
+ },
+ "key_path_index": 0,
+ "map_key_edge_index": -1
+ }
+ ],
+ "attributes": {
+ "label": {
+ "value": "d"
+ },
+ "labelDimensions": {
+ "width": 0,
+ "height": 0
+ },
+ "style": {},
+ "near_key": null,
+ "shape": {
+ "value": "rectangle"
+ },
+ "direction": {
+ "value": ""
+ },
+ "constraint": null
+ },
+ "zIndex": 0
+ }
+ ]
+ },
+ "err": null
+}
diff --git a/testdata/d2compiler/TestCompile2/globs/suspension-quotes.exp.json b/testdata/d2compiler/TestCompile2/globs/suspension-quotes.exp.json
new file mode 100644
index 000000000..f025e00fa
--- /dev/null
+++ b/testdata/d2compiler/TestCompile2/globs/suspension-quotes.exp.json
@@ -0,0 +1,414 @@
+{
+ "graph": {
+ "name": "",
+ "isFolderOnly": false,
+ "ast": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-quotes.d2,0:0:0-7:0:76",
+ "nodes": [
+ {
+ "map_key": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-quotes.d2,1:0:1-1:6:7",
+ "edges": [
+ {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-quotes.d2,1:0:1-1:6:7",
+ "src": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-quotes.d2,1:0:1-1:1:2",
+ "path": [
+ {
+ "unquoted_string": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-quotes.d2,1:0:1-1:1:2",
+ "value": [
+ {
+ "string": "a",
+ "raw_string": "a"
+ }
+ ]
+ }
+ }
+ ]
+ },
+ "src_arrow": "",
+ "dst": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-quotes.d2,1:5:6-1:6:7",
+ "path": [
+ {
+ "unquoted_string": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-quotes.d2,1:5:6-1:6:7",
+ "value": [
+ {
+ "string": "b",
+ "raw_string": "b"
+ }
+ ]
+ }
+ }
+ ]
+ },
+ "dst_arrow": ">"
+ }
+ ],
+ "primary": {},
+ "value": {}
+ }
+ },
+ {
+ "map_key": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-quotes.d2,2:0:8-2:1:9",
+ "key": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-quotes.d2,2:0:8-2:1:9",
+ "path": [
+ {
+ "unquoted_string": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-quotes.d2,2:0:8-2:1:9",
+ "value": [
+ {
+ "string": "c",
+ "raw_string": "c"
+ }
+ ]
+ }
+ }
+ ]
+ },
+ "primary": {},
+ "value": {}
+ }
+ },
+ {
+ "map_key": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-quotes.d2,3:0:10-3:11:21",
+ "key": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-quotes.d2,3:0:10-3:2:12",
+ "path": [
+ {
+ "unquoted_string": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-quotes.d2,3:0:10-3:2:12",
+ "value": [
+ {
+ "string": "**",
+ "raw_string": "**"
+ }
+ ],
+ "pattern": [
+ "*",
+ "",
+ "*"
+ ]
+ }
+ }
+ ]
+ },
+ "primary": {},
+ "value": {
+ "suspension": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-quotes.d2,3:4:14-3:11:21",
+ "value": true
+ }
+ }
+ }
+ },
+ {
+ "map_key": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-quotes.d2,4:0:22-4:22:44",
+ "edges": [
+ {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-quotes.d2,4:1:23-4:9:31",
+ "src": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-quotes.d2,4:1:23-4:3:25",
+ "path": [
+ {
+ "unquoted_string": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-quotes.d2,4:1:23-4:3:25",
+ "value": [
+ {
+ "string": "**",
+ "raw_string": "**"
+ }
+ ],
+ "pattern": [
+ "*",
+ "",
+ "*"
+ ]
+ }
+ }
+ ]
+ },
+ "src_arrow": "",
+ "dst": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-quotes.d2,4:7:29-4:9:31",
+ "path": [
+ {
+ "unquoted_string": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-quotes.d2,4:7:29-4:9:31",
+ "value": [
+ {
+ "string": "**",
+ "raw_string": "**"
+ }
+ ],
+ "pattern": [
+ "*",
+ "",
+ "*"
+ ]
+ }
+ }
+ ]
+ },
+ "dst_arrow": ">"
+ }
+ ],
+ "edge_index": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-quotes.d2,4:10:32-4:13:35",
+ "int": null,
+ "glob": true
+ },
+ "primary": {},
+ "value": {
+ "suspension": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-quotes.d2,4:15:37-4:22:44",
+ "value": true
+ }
+ }
+ }
+ },
+ {
+ "map_key": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-quotes.d2,5:0:45-5:12:57",
+ "key": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-quotes.d2,5:0:45-5:1:46",
+ "path": [
+ {
+ "unquoted_string": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-quotes.d2,5:0:45-5:1:46",
+ "value": [
+ {
+ "string": "d",
+ "raw_string": "d"
+ }
+ ]
+ }
+ }
+ ]
+ },
+ "primary": {},
+ "value": {
+ "double_quoted_string": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-quotes.d2,5:3:48-5:12:57",
+ "value": [
+ {
+ "string": "suspend",
+ "raw_string": "suspend"
+ }
+ ]
+ }
+ }
+ }
+ },
+ {
+ "map_key": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-quotes.d2,6:0:58-6:17:75",
+ "edges": [
+ {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-quotes.d2,6:0:58-6:6:64",
+ "src": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-quotes.d2,6:0:58-6:1:59",
+ "path": [
+ {
+ "unquoted_string": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-quotes.d2,6:0:58-6:1:59",
+ "value": [
+ {
+ "string": "d",
+ "raw_string": "d"
+ }
+ ]
+ }
+ }
+ ]
+ },
+ "src_arrow": "",
+ "dst": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-quotes.d2,6:5:63-6:6:64",
+ "path": [
+ {
+ "unquoted_string": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-quotes.d2,6:5:63-6:6:64",
+ "value": [
+ {
+ "string": "d",
+ "raw_string": "d"
+ }
+ ]
+ }
+ }
+ ]
+ },
+ "dst_arrow": ">"
+ }
+ ],
+ "primary": {},
+ "value": {
+ "double_quoted_string": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-quotes.d2,6:8:66-6:17:75",
+ "value": [
+ {
+ "string": "suspend",
+ "raw_string": "suspend"
+ }
+ ]
+ }
+ }
+ }
+ }
+ ]
+ },
+ "root": {
+ "id": "",
+ "id_val": "",
+ "attributes": {
+ "label": {
+ "value": ""
+ },
+ "labelDimensions": {
+ "width": 0,
+ "height": 0
+ },
+ "style": {},
+ "near_key": null,
+ "shape": {
+ "value": ""
+ },
+ "direction": {
+ "value": ""
+ },
+ "constraint": null
+ },
+ "zIndex": 0
+ },
+ "edges": [
+ {
+ "index": 0,
+ "isCurve": false,
+ "src_arrow": false,
+ "dst_arrow": true,
+ "references": [
+ {
+ "map_key_edge_index": 0
+ },
+ {
+ "map_key_edge_index": 0
+ }
+ ],
+ "attributes": {
+ "label": {
+ "value": "suspend"
+ },
+ "labelDimensions": {
+ "width": 0,
+ "height": 0
+ },
+ "style": {},
+ "near_key": null,
+ "shape": {
+ "value": ""
+ },
+ "direction": {
+ "value": ""
+ },
+ "constraint": null
+ },
+ "zIndex": 0
+ }
+ ],
+ "objects": [
+ {
+ "id": "d",
+ "id_val": "d",
+ "references": [
+ {
+ "key": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-quotes.d2,5:0:45-5:1:46",
+ "path": [
+ {
+ "unquoted_string": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-quotes.d2,5:0:45-5:1:46",
+ "value": [
+ {
+ "string": "d",
+ "raw_string": "d"
+ }
+ ]
+ }
+ }
+ ]
+ },
+ "key_path_index": 0,
+ "map_key_edge_index": -1
+ },
+ {
+ "key": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-quotes.d2,6:0:58-6:1:59",
+ "path": [
+ {
+ "unquoted_string": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-quotes.d2,6:0:58-6:1:59",
+ "value": [
+ {
+ "string": "d",
+ "raw_string": "d"
+ }
+ ]
+ }
+ }
+ ]
+ },
+ "key_path_index": 0,
+ "map_key_edge_index": 0
+ },
+ {
+ "key": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-quotes.d2,6:5:63-6:6:64",
+ "path": [
+ {
+ "unquoted_string": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-quotes.d2,6:5:63-6:6:64",
+ "value": [
+ {
+ "string": "d",
+ "raw_string": "d"
+ }
+ ]
+ }
+ }
+ ]
+ },
+ "key_path_index": 0,
+ "map_key_edge_index": 0
+ }
+ ],
+ "attributes": {
+ "label": {
+ "value": "suspend"
+ },
+ "labelDimensions": {
+ "width": 0,
+ "height": 0
+ },
+ "style": {},
+ "near_key": null,
+ "shape": {
+ "value": "rectangle"
+ },
+ "direction": {
+ "value": ""
+ },
+ "constraint": null
+ },
+ "zIndex": 0
+ }
+ ]
+ },
+ "err": null
+}
diff --git a/testdata/d2compiler/TestCompile2/globs/suspension-restore-edge.exp.json b/testdata/d2compiler/TestCompile2/globs/suspension-restore-edge.exp.json
new file mode 100644
index 000000000..a312d2fa8
--- /dev/null
+++ b/testdata/d2compiler/TestCompile2/globs/suspension-restore-edge.exp.json
@@ -0,0 +1,341 @@
+{
+ "graph": {
+ "name": "",
+ "isFolderOnly": false,
+ "ast": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-restore-edge.d2,0:0:0-9:0:108",
+ "nodes": [
+ {
+ "map_key": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-restore-edge.d2,1:0:1-1:6:7",
+ "edges": [
+ {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-restore-edge.d2,1:0:1-1:6:7",
+ "src": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-restore-edge.d2,1:0:1-1:1:2",
+ "path": [
+ {
+ "unquoted_string": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-restore-edge.d2,1:0:1-1:1:2",
+ "value": [
+ {
+ "string": "a",
+ "raw_string": "a"
+ }
+ ]
+ }
+ }
+ ]
+ },
+ "src_arrow": "",
+ "dst": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-restore-edge.d2,1:5:6-1:6:7",
+ "path": [
+ {
+ "unquoted_string": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-restore-edge.d2,1:5:6-1:6:7",
+ "value": [
+ {
+ "string": "b",
+ "raw_string": "b"
+ }
+ ]
+ }
+ }
+ ]
+ },
+ "dst_arrow": ">"
+ }
+ ],
+ "primary": {},
+ "value": {}
+ }
+ },
+ {
+ "map_key": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-restore-edge.d2,2:0:8-2:1:9",
+ "key": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-restore-edge.d2,2:0:8-2:1:9",
+ "path": [
+ {
+ "unquoted_string": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-restore-edge.d2,2:0:8-2:1:9",
+ "value": [
+ {
+ "string": "c",
+ "raw_string": "c"
+ }
+ ]
+ }
+ }
+ ]
+ },
+ "primary": {},
+ "value": {}
+ }
+ },
+ {
+ "map_key": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-restore-edge.d2,3:0:10-3:11:21",
+ "key": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-restore-edge.d2,3:0:10-3:2:12",
+ "path": [
+ {
+ "unquoted_string": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-restore-edge.d2,3:0:10-3:2:12",
+ "value": [
+ {
+ "string": "**",
+ "raw_string": "**"
+ }
+ ],
+ "pattern": [
+ "*",
+ "",
+ "*"
+ ]
+ }
+ }
+ ]
+ },
+ "primary": {},
+ "value": {
+ "suspension": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-restore-edge.d2,3:4:14-3:11:21",
+ "value": true
+ }
+ }
+ }
+ },
+ {
+ "map_key": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-restore-edge.d2,4:0:22-4:22:44",
+ "edges": [
+ {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-restore-edge.d2,4:1:23-4:9:31",
+ "src": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-restore-edge.d2,4:1:23-4:3:25",
+ "path": [
+ {
+ "unquoted_string": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-restore-edge.d2,4:1:23-4:3:25",
+ "value": [
+ {
+ "string": "**",
+ "raw_string": "**"
+ }
+ ],
+ "pattern": [
+ "*",
+ "",
+ "*"
+ ]
+ }
+ }
+ ]
+ },
+ "src_arrow": "",
+ "dst": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-restore-edge.d2,4:7:29-4:9:31",
+ "path": [
+ {
+ "unquoted_string": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-restore-edge.d2,4:7:29-4:9:31",
+ "value": [
+ {
+ "string": "**",
+ "raw_string": "**"
+ }
+ ],
+ "pattern": [
+ "*",
+ "",
+ "*"
+ ]
+ }
+ }
+ ]
+ },
+ "dst_arrow": ">"
+ }
+ ],
+ "edge_index": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-restore-edge.d2,4:10:32-4:13:35",
+ "int": null,
+ "glob": true
+ },
+ "primary": {},
+ "value": {
+ "suspension": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-restore-edge.d2,4:15:37-4:22:44",
+ "value": true
+ }
+ }
+ }
+ },
+ {
+ "map_key": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-restore-edge.d2,5:0:45-5:1:46",
+ "key": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-restore-edge.d2,5:0:45-5:1:46",
+ "path": [
+ {
+ "unquoted_string": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-restore-edge.d2,5:0:45-5:1:46",
+ "value": [
+ {
+ "string": "d",
+ "raw_string": "d"
+ }
+ ]
+ }
+ }
+ ]
+ },
+ "primary": {},
+ "value": {}
+ }
+ },
+ {
+ "comment": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-restore-edge.d2,7:0:48-7:37:85",
+ "value": "Also restores the objects connected"
+ }
+ },
+ {
+ "map_key": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-restore-edge.d2,8:0:86-8:21:107",
+ "edges": [
+ {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-restore-edge.d2,8:1:87-8:8:94",
+ "src": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-restore-edge.d2,8:1:87-8:3:89",
+ "path": [
+ {
+ "unquoted_string": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-restore-edge.d2,8:1:87-8:3:89",
+ "value": [
+ {
+ "string": "**",
+ "raw_string": "**"
+ }
+ ],
+ "pattern": [
+ "*",
+ "",
+ "*"
+ ]
+ }
+ }
+ ]
+ },
+ "src_arrow": "",
+ "dst": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-restore-edge.d2,8:7:93-8:8:94",
+ "path": [
+ {
+ "unquoted_string": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-restore-edge.d2,8:7:93-8:8:94",
+ "value": [
+ {
+ "string": "b",
+ "raw_string": "b"
+ }
+ ]
+ }
+ }
+ ]
+ },
+ "dst_arrow": ">"
+ }
+ ],
+ "edge_index": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-restore-edge.d2,8:9:95-8:12:98",
+ "int": null,
+ "glob": true
+ },
+ "primary": {},
+ "value": {
+ "suspension": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-restore-edge.d2,8:14:100-8:21:107",
+ "value": false
+ }
+ }
+ }
+ }
+ ]
+ },
+ "root": {
+ "id": "",
+ "id_val": "",
+ "attributes": {
+ "label": {
+ "value": ""
+ },
+ "labelDimensions": {
+ "width": 0,
+ "height": 0
+ },
+ "style": {},
+ "near_key": null,
+ "shape": {
+ "value": ""
+ },
+ "direction": {
+ "value": ""
+ },
+ "constraint": null
+ },
+ "zIndex": 0
+ },
+ "edges": null,
+ "objects": [
+ {
+ "id": "d",
+ "id_val": "d",
+ "references": [
+ {
+ "key": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-restore-edge.d2,5:0:45-5:1:46",
+ "path": [
+ {
+ "unquoted_string": {
+ "range": "d2/testdata/d2compiler/TestCompile2/globs/suspension-restore-edge.d2,5:0:45-5:1:46",
+ "value": [
+ {
+ "string": "d",
+ "raw_string": "d"
+ }
+ ]
+ }
+ }
+ ]
+ },
+ "key_path_index": 0,
+ "map_key_edge_index": -1
+ }
+ ],
+ "attributes": {
+ "label": {
+ "value": "d"
+ },
+ "labelDimensions": {
+ "width": 0,
+ "height": 0
+ },
+ "style": {},
+ "near_key": null,
+ "shape": {
+ "value": "rectangle"
+ },
+ "direction": {
+ "value": ""
+ },
+ "constraint": null
+ },
+ "zIndex": 0
+ }
+ ]
+ },
+ "err": null
+}