diff --git a/ci/release/changelogs/next.md b/ci/release/changelogs/next.md
index 526a08b78..d4b30fab6 100644
--- a/ci/release/changelogs/next.md
+++ b/ci/release/changelogs/next.md
@@ -1,5 +1,7 @@
#### Features ๐
+- Vars: vars in markdown blocks are substituted [#2218](https://github.com/terrastruct/d2/pull/2218)
+
#### Improvements ๐งน
- Composition: links pointing to own board are purged [#2203](https://github.com/terrastruct/d2/pull/2203)
diff --git a/d2compiler/compile_test.go b/d2compiler/compile_test.go
index 379c6bfb1..6870938d0 100644
--- a/d2compiler/compile_test.go
+++ b/d2compiler/compile_test.go
@@ -2865,6 +2865,33 @@ x*: {
tassert.Equal(t, "x2.ok", g.Objects[3].AbsID())
},
},
+ {
+ name: "var_in_markdown",
+ text: `vars: {
+ v: ok
+}
+
+x: |md
+ m${v}y
+
+ ` + "`hey ${v}`" + `
+
+ regular markdown
+
+ ` + "```" + `
+ bye ${v}
+ ` + "```" + `
+|
+`,
+ assertions: func(t *testing.T, g *d2graph.Graph) {
+ tassert.True(t, strings.Contains(g.Objects[0].Attributes.Label.Value, "moky"))
+ tassert.False(t, strings.Contains(g.Objects[0].Attributes.Label.Value, "m${v}y"))
+ // Code spans untouched
+ tassert.True(t, strings.Contains(g.Objects[0].Attributes.Label.Value, "hey ${v}"))
+ // Code blocks untouched
+ tassert.True(t, strings.Contains(g.Objects[0].Attributes.Label.Value, "bye ${v}"))
+ },
+ },
{
name: "var_in_vars",
text: `vars: {
diff --git a/d2ir/compile.go b/d2ir/compile.go
index fbd0b00ca..ecc13a32b 100644
--- a/d2ir/compile.go
+++ b/d2ir/compile.go
@@ -15,6 +15,7 @@ import (
"oss.terrastruct.com/d2/d2parser"
"oss.terrastruct.com/d2/d2themes"
"oss.terrastruct.com/d2/d2themes/d2themescatalog"
+ "oss.terrastruct.com/d2/lib/textmeasure"
)
type globContext struct {
@@ -342,10 +343,32 @@ func (c *compiler) resolveSubstitutions(varsStack []*Map, node Node) (removedFie
if subbed {
s.Coalesce()
}
+ case *d2ast.BlockString:
+ variables := make(map[string]string)
+ for _, vars := range varsStack {
+ c.collectVariables(vars, variables)
+ }
+ preprocessedValue := textmeasure.ReplaceSubstitutionsMarkdown(s.Value, variables)
+
+ // Update the block string value
+ s.Value = preprocessedValue
}
return removedField
}
+func (c *compiler) collectVariables(vars *Map, variables map[string]string) {
+ if vars == nil {
+ return
+ }
+ for _, f := range vars.Fields {
+ if f.Primary() != nil {
+ variables[f.Name] = f.Primary().Value.ScalarString()
+ } else if f.Map() != nil {
+ c.collectVariables(f.Map(), variables)
+ }
+ }
+}
+
func (c *compiler) resolveSubstitution(vars *Map, node Node, substitution *d2ast.Substitution, isCurrentScopeVars bool) *Field {
if vars == nil {
return nil
diff --git a/e2etests/testdata/txtar/var_in_markdown/dagre/board.exp.json b/e2etests/testdata/txtar/var_in_markdown/dagre/board.exp.json
new file mode 100644
index 000000000..9310e8460
--- /dev/null
+++ b/e2etests/testdata/txtar/var_in_markdown/dagre/board.exp.json
@@ -0,0 +1,306 @@
+{
+ "name": "",
+ "isFolderOnly": false,
+ "fontFamily": "SourceSansPro",
+ "shapes": [
+ {
+ "id": "x",
+ "type": "rectangle",
+ "pos": {
+ "x": 0,
+ "y": 0
+ },
+ "width": 81,
+ "height": 66,
+ "opacity": 1,
+ "strokeDash": 0,
+ "strokeWidth": 2,
+ "borderRadius": 0,
+ "fill": "B6",
+ "stroke": "B1",
+ "shadow": false,
+ "3d": false,
+ "multiple": false,
+ "double-border": false,
+ "tooltip": "",
+ "link": "",
+ "icon": null,
+ "iconPosition": "",
+ "blend": false,
+ "fields": null,
+ "methods": null,
+ "columns": null,
+ "label": "Kube",
+ "fontSize": 16,
+ "fontFamily": "DEFAULT",
+ "language": "",
+ "color": "N1",
+ "italic": false,
+ "bold": true,
+ "underline": false,
+ "labelWidth": 36,
+ "labelHeight": 21,
+ "labelPosition": "INSIDE_MIDDLE_CENTER",
+ "zIndex": 0,
+ "level": 1
+ },
+ {
+ "id": "description",
+ "type": "text",
+ "pos": {
+ "x": 86,
+ "y": 166
+ },
+ "width": 163,
+ "height": 96,
+ "opacity": 1,
+ "strokeDash": 0,
+ "strokeWidth": 2,
+ "borderRadius": 0,
+ "fill": "transparent",
+ "stroke": "N1",
+ "shadow": false,
+ "3d": false,
+ "multiple": false,
+ "double-border": false,
+ "tooltip": "",
+ "link": "",
+ "icon": null,
+ "iconPosition": "",
+ "blend": false,
+ "fields": null,
+ "methods": null,
+ "columns": null,
+ "label": "Kube is a service\n\n```\nLet ${y} be ${x}\n```",
+ "fontSize": 16,
+ "fontFamily": "DEFAULT",
+ "language": "markdown",
+ "color": "N1",
+ "italic": false,
+ "bold": false,
+ "underline": false,
+ "labelWidth": 163,
+ "labelHeight": 96,
+ "zIndex": 0,
+ "level": 1
+ },
+ {
+ "id": "b",
+ "type": "rectangle",
+ "pos": {
+ "x": 141,
+ "y": 0
+ },
+ "width": 53,
+ "height": 66,
+ "opacity": 1,
+ "strokeDash": 0,
+ "strokeWidth": 2,
+ "borderRadius": 0,
+ "fill": "B6",
+ "stroke": "B1",
+ "shadow": false,
+ "3d": false,
+ "multiple": false,
+ "double-border": false,
+ "tooltip": "",
+ "link": "",
+ "icon": null,
+ "iconPosition": "",
+ "blend": false,
+ "fields": null,
+ "methods": null,
+ "columns": null,
+ "label": "b",
+ "fontSize": 16,
+ "fontFamily": "DEFAULT",
+ "language": "",
+ "color": "N1",
+ "italic": false,
+ "bold": true,
+ "underline": false,
+ "labelWidth": 8,
+ "labelHeight": 21,
+ "labelPosition": "INSIDE_MIDDLE_CENTER",
+ "zIndex": 0,
+ "level": 1
+ },
+ {
+ "id": "a",
+ "type": "rectangle",
+ "pos": {
+ "x": 141,
+ "y": 362
+ },
+ "width": 53,
+ "height": 66,
+ "opacity": 1,
+ "strokeDash": 0,
+ "strokeWidth": 2,
+ "borderRadius": 0,
+ "fill": "B6",
+ "stroke": "B1",
+ "shadow": false,
+ "3d": false,
+ "multiple": false,
+ "double-border": false,
+ "tooltip": "",
+ "link": "",
+ "icon": null,
+ "iconPosition": "",
+ "blend": false,
+ "fields": null,
+ "methods": null,
+ "columns": null,
+ "label": "a",
+ "fontSize": 16,
+ "fontFamily": "DEFAULT",
+ "language": "",
+ "color": "N1",
+ "italic": false,
+ "bold": true,
+ "underline": false,
+ "labelWidth": 8,
+ "labelHeight": 21,
+ "labelPosition": "INSIDE_MIDDLE_CENTER",
+ "zIndex": 0,
+ "level": 1
+ }
+ ],
+ "connections": [
+ {
+ "id": "(b -> description)[0]",
+ "src": "b",
+ "srcArrow": "none",
+ "dst": "description",
+ "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,
+ "route": [
+ {
+ "x": 167.5,
+ "y": 66
+ },
+ {
+ "x": 167.5,
+ "y": 106
+ },
+ {
+ "x": 167.5,
+ "y": 126
+ },
+ {
+ "x": 167.5,
+ "y": 166
+ }
+ ],
+ "isCurve": true,
+ "animated": false,
+ "tooltip": "",
+ "icon": null,
+ "zIndex": 0
+ },
+ {
+ "id": "(description -> a)[0]",
+ "src": "description",
+ "srcArrow": "none",
+ "dst": "a",
+ "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,
+ "route": [
+ {
+ "x": 167.5,
+ "y": 262
+ },
+ {
+ "x": 167.5,
+ "y": 302
+ },
+ {
+ "x": 167.5,
+ "y": 322
+ },
+ {
+ "x": 167.5,
+ "y": 362
+ }
+ ],
+ "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": "",
+ "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/var_in_markdown/dagre/sketch.exp.svg b/e2etests/testdata/txtar/var_in_markdown/dagre/sketch.exp.svg
new file mode 100644
index 000000000..49f57ba74
--- /dev/null
+++ b/e2etests/testdata/txtar/var_in_markdown/dagre/sketch.exp.svg
@@ -0,0 +1,855 @@
+
\ No newline at end of file
diff --git a/e2etests/testdata/txtar/var_in_markdown/elk/board.exp.json b/e2etests/testdata/txtar/var_in_markdown/elk/board.exp.json
new file mode 100644
index 000000000..431f493e9
--- /dev/null
+++ b/e2etests/testdata/txtar/var_in_markdown/elk/board.exp.json
@@ -0,0 +1,288 @@
+{
+ "name": "",
+ "isFolderOnly": false,
+ "fontFamily": "SourceSansPro",
+ "shapes": [
+ {
+ "id": "x",
+ "type": "rectangle",
+ "pos": {
+ "x": 12,
+ "y": 12
+ },
+ "width": 81,
+ "height": 66,
+ "opacity": 1,
+ "strokeDash": 0,
+ "strokeWidth": 2,
+ "borderRadius": 0,
+ "fill": "B6",
+ "stroke": "B1",
+ "shadow": false,
+ "3d": false,
+ "multiple": false,
+ "double-border": false,
+ "tooltip": "",
+ "link": "",
+ "icon": null,
+ "iconPosition": "",
+ "blend": false,
+ "fields": null,
+ "methods": null,
+ "columns": null,
+ "label": "Kube",
+ "fontSize": 16,
+ "fontFamily": "DEFAULT",
+ "language": "",
+ "color": "N1",
+ "italic": false,
+ "bold": true,
+ "underline": false,
+ "labelWidth": 36,
+ "labelHeight": 21,
+ "labelPosition": "INSIDE_MIDDLE_CENTER",
+ "zIndex": 0,
+ "level": 1
+ },
+ {
+ "id": "description",
+ "type": "text",
+ "pos": {
+ "x": 58,
+ "y": 148
+ },
+ "width": 163,
+ "height": 96,
+ "opacity": 1,
+ "strokeDash": 0,
+ "strokeWidth": 2,
+ "borderRadius": 0,
+ "fill": "transparent",
+ "stroke": "N1",
+ "shadow": false,
+ "3d": false,
+ "multiple": false,
+ "double-border": false,
+ "tooltip": "",
+ "link": "",
+ "icon": null,
+ "iconPosition": "",
+ "blend": false,
+ "fields": null,
+ "methods": null,
+ "columns": null,
+ "label": "Kube is a service\n\n```\nLet ${y} be ${x}\n```",
+ "fontSize": 16,
+ "fontFamily": "DEFAULT",
+ "language": "markdown",
+ "color": "N1",
+ "italic": false,
+ "bold": false,
+ "underline": false,
+ "labelWidth": 163,
+ "labelHeight": 96,
+ "zIndex": 0,
+ "level": 1
+ },
+ {
+ "id": "b",
+ "type": "rectangle",
+ "pos": {
+ "x": 113,
+ "y": 12
+ },
+ "width": 53,
+ "height": 66,
+ "opacity": 1,
+ "strokeDash": 0,
+ "strokeWidth": 2,
+ "borderRadius": 0,
+ "fill": "B6",
+ "stroke": "B1",
+ "shadow": false,
+ "3d": false,
+ "multiple": false,
+ "double-border": false,
+ "tooltip": "",
+ "link": "",
+ "icon": null,
+ "iconPosition": "",
+ "blend": false,
+ "fields": null,
+ "methods": null,
+ "columns": null,
+ "label": "b",
+ "fontSize": 16,
+ "fontFamily": "DEFAULT",
+ "language": "",
+ "color": "N1",
+ "italic": false,
+ "bold": true,
+ "underline": false,
+ "labelWidth": 8,
+ "labelHeight": 21,
+ "labelPosition": "INSIDE_MIDDLE_CENTER",
+ "zIndex": 0,
+ "level": 1
+ },
+ {
+ "id": "a",
+ "type": "rectangle",
+ "pos": {
+ "x": 113,
+ "y": 314
+ },
+ "width": 53,
+ "height": 66,
+ "opacity": 1,
+ "strokeDash": 0,
+ "strokeWidth": 2,
+ "borderRadius": 0,
+ "fill": "B6",
+ "stroke": "B1",
+ "shadow": false,
+ "3d": false,
+ "multiple": false,
+ "double-border": false,
+ "tooltip": "",
+ "link": "",
+ "icon": null,
+ "iconPosition": "",
+ "blend": false,
+ "fields": null,
+ "methods": null,
+ "columns": null,
+ "label": "a",
+ "fontSize": 16,
+ "fontFamily": "DEFAULT",
+ "language": "",
+ "color": "N1",
+ "italic": false,
+ "bold": true,
+ "underline": false,
+ "labelWidth": 8,
+ "labelHeight": 21,
+ "labelPosition": "INSIDE_MIDDLE_CENTER",
+ "zIndex": 0,
+ "level": 1
+ }
+ ],
+ "connections": [
+ {
+ "id": "(b -> description)[0]",
+ "src": "b",
+ "srcArrow": "none",
+ "dst": "description",
+ "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,
+ "route": [
+ {
+ "x": 139.5,
+ "y": 78
+ },
+ {
+ "x": 139.5,
+ "y": 148
+ }
+ ],
+ "animated": false,
+ "tooltip": "",
+ "icon": null,
+ "zIndex": 0
+ },
+ {
+ "id": "(description -> a)[0]",
+ "src": "description",
+ "srcArrow": "none",
+ "dst": "a",
+ "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,
+ "route": [
+ {
+ "x": 139.5,
+ "y": 244
+ },
+ {
+ "x": 139.5,
+ "y": 314
+ }
+ ],
+ "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": "",
+ "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/var_in_markdown/elk/sketch.exp.svg b/e2etests/testdata/txtar/var_in_markdown/elk/sketch.exp.svg
new file mode 100644
index 000000000..80041bd46
--- /dev/null
+++ b/e2etests/testdata/txtar/var_in_markdown/elk/sketch.exp.svg
@@ -0,0 +1,855 @@
+KubeKube is a service
+
Let ${y} be ${x}
+
+
ba
+
+
+
+
+
+
\ No newline at end of file
diff --git a/e2etests/txtar.txt b/e2etests/txtar.txt
index d669dcc18..193acb48e 100644
--- a/e2etests/txtar.txt
+++ b/e2etests/txtar.txt
@@ -469,3 +469,17 @@ colors: {
style.font-color: "linear-gradient(to bottom right, red 0%, yellow 25%, green 50%, cyan 75%, blue 100%)"
}
gradient -> colors
+
+-- var_in_markdown --
+vars: {
+ service-x: Kube
+}
+x: ${service-x}
+description: |md
+ ${service-x} is a service
+
+ ```
+ Let ${y} be ${x}
+ ```
+|
+b -> description -> a
diff --git a/lib/textmeasure/substitutions.go b/lib/textmeasure/substitutions.go
new file mode 100644
index 000000000..a141e7385
--- /dev/null
+++ b/lib/textmeasure/substitutions.go
@@ -0,0 +1,77 @@
+package textmeasure
+
+import (
+ "sort"
+ "strings"
+
+ "github.com/yuin/goldmark/ast"
+ "github.com/yuin/goldmark/text"
+)
+
+func ReplaceSubstitutionsMarkdown(mdText string, variables map[string]string) string {
+ source := []byte(mdText)
+ reader := text.NewReader(source)
+ doc := markdownRenderer.Parser().Parse(reader)
+
+ type substitution struct {
+ start int
+ stop int
+ newVal string
+ }
+ var substitutions []substitution
+
+ ast.Walk(doc, func(n ast.Node, entering bool) (ast.WalkStatus, error) {
+ if !entering {
+ return ast.WalkContinue, nil
+ }
+
+ if isCodeNode(n) {
+ return ast.WalkSkipChildren, nil
+ }
+
+ if textNode, ok := n.(*ast.Text); ok {
+ segment := textNode.Segment
+ originalText := string(segment.Value(source))
+ newText := replaceVariables(originalText, variables)
+
+ if originalText != newText {
+ substitutions = append(substitutions, substitution{
+ start: segment.Start,
+ stop: segment.Stop,
+ newVal: newText,
+ })
+ }
+ }
+ return ast.WalkContinue, nil
+ })
+
+ if len(substitutions) == 0 {
+ return mdText
+ }
+
+ sort.Slice(substitutions, func(i, j int) bool {
+ return substitutions[i].start > substitutions[j].start
+ })
+
+ result := string(source)
+ for _, sub := range substitutions {
+ result = result[:sub.start] + sub.newVal + result[sub.stop:]
+ }
+
+ return result
+}
+
+func isCodeNode(n ast.Node) bool {
+ switch n.Kind() {
+ case ast.KindCodeBlock, ast.KindFencedCodeBlock, ast.KindCodeSpan:
+ return true
+ }
+ return false
+}
+
+func replaceVariables(s string, vars map[string]string) string {
+ for k, v := range vars {
+ s = strings.ReplaceAll(s, "${"+k+"}", v)
+ }
+ return s
+}
diff --git a/testdata/d2compiler/TestCompile/var_in_markdown.exp.json b/testdata/d2compiler/TestCompile/var_in_markdown.exp.json
new file mode 100644
index 000000000..415e4911d
--- /dev/null
+++ b/testdata/d2compiler/TestCompile/var_in_markdown.exp.json
@@ -0,0 +1,176 @@
+{
+ "graph": {
+ "name": "",
+ "isFolderOnly": false,
+ "ast": {
+ "range": "d2/testdata/d2compiler/TestCompile/var_in_markdown.d2,0:0:0-15:0:90",
+ "nodes": [
+ {
+ "map_key": {
+ "range": "d2/testdata/d2compiler/TestCompile/var_in_markdown.d2,0:0:0-2:1:17",
+ "key": {
+ "range": "d2/testdata/d2compiler/TestCompile/var_in_markdown.d2,0:0:0-0:4:4",
+ "path": [
+ {
+ "unquoted_string": {
+ "range": "d2/testdata/d2compiler/TestCompile/var_in_markdown.d2,0:0:0-0:4:4",
+ "value": [
+ {
+ "string": "vars",
+ "raw_string": "vars"
+ }
+ ]
+ }
+ }
+ ]
+ },
+ "primary": {},
+ "value": {
+ "map": {
+ "range": "d2/testdata/d2compiler/TestCompile/var_in_markdown.d2,0:6:6-2:1:17",
+ "nodes": [
+ {
+ "map_key": {
+ "range": "d2/testdata/d2compiler/TestCompile/var_in_markdown.d2,1:2:10-1:7:15",
+ "key": {
+ "range": "d2/testdata/d2compiler/TestCompile/var_in_markdown.d2,1:2:10-1:3:11",
+ "path": [
+ {
+ "unquoted_string": {
+ "range": "d2/testdata/d2compiler/TestCompile/var_in_markdown.d2,1:2:10-1:3:11",
+ "value": [
+ {
+ "string": "v",
+ "raw_string": "v"
+ }
+ ]
+ }
+ }
+ ]
+ },
+ "primary": {},
+ "value": {
+ "unquoted_string": {
+ "range": "d2/testdata/d2compiler/TestCompile/var_in_markdown.d2,1:5:13-1:7:15",
+ "value": [
+ {
+ "string": "ok",
+ "raw_string": "ok"
+ }
+ ]
+ }
+ }
+ }
+ }
+ ]
+ }
+ }
+ }
+ },
+ {
+ "map_key": {
+ "range": "d2/testdata/d2compiler/TestCompile/var_in_markdown.d2,4:0:19-14:1:89",
+ "key": {
+ "range": "d2/testdata/d2compiler/TestCompile/var_in_markdown.d2,4:0:19-4:1:20",
+ "path": [
+ {
+ "unquoted_string": {
+ "range": "d2/testdata/d2compiler/TestCompile/var_in_markdown.d2,4:0:19-4:1:20",
+ "value": [
+ {
+ "string": "x",
+ "raw_string": "x"
+ }
+ ]
+ }
+ }
+ ]
+ },
+ "primary": {},
+ "value": {
+ "block_string": {
+ "range": "d2/testdata/d2compiler/TestCompile/var_in_markdown.d2,4:3:22-14:1:89",
+ "quote": "",
+ "tag": "md",
+ "value": "moky\n\n`hey ${v}`\n\nregular markdown\n\n```\nbye ${v}\n```"
+ }
+ }
+ }
+ }
+ ]
+ },
+ "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": "x",
+ "id_val": "x",
+ "references": [
+ {
+ "key": {
+ "range": "d2/testdata/d2compiler/TestCompile/var_in_markdown.d2,4:0:19-4:1:20",
+ "path": [
+ {
+ "unquoted_string": {
+ "range": "d2/testdata/d2compiler/TestCompile/var_in_markdown.d2,4:0:19-4:1:20",
+ "value": [
+ {
+ "string": "x",
+ "raw_string": "x"
+ }
+ ]
+ }
+ }
+ ]
+ },
+ "key_path_index": 0,
+ "map_key_edge_index": -1
+ }
+ ],
+ "attributes": {
+ "label": {
+ "value": "moky\n\n`hey ${v}`\n\nregular markdown\n\n```\nbye ${v}\n```"
+ },
+ "labelDimensions": {
+ "width": 0,
+ "height": 0
+ },
+ "style": {},
+ "near_key": null,
+ "language": "markdown",
+ "shape": {
+ "value": "text"
+ },
+ "direction": {
+ "value": ""
+ },
+ "constraint": null
+ },
+ "zIndex": 0
+ }
+ ]
+ },
+ "err": null
+}