From 7e6a3d81324865e51be8b1bb2a1e79025aed6772 Mon Sep 17 00:00:00 2001 From: Alexander Wang Date: Mon, 18 Nov 2024 07:01:47 -0800 Subject: [PATCH] d2ir: replace substitutions in markdown --- ci/release/changelogs/next.md | 2 + d2compiler/compile_test.go | 19 +- d2ir/compile.go | 23 + .../var_in_markdown/dagre/board.exp.json | 306 +++++++ .../var_in_markdown/dagre/sketch.exp.svg | 855 ++++++++++++++++++ .../txtar/var_in_markdown/elk/board.exp.json | 288 ++++++ .../txtar/var_in_markdown/elk/sketch.exp.svg | 855 ++++++++++++++++++ e2etests/txtar.txt | 14 + lib/textmeasure/substitutions.go | 77 ++ .../TestCompile/var_in_markdown.exp.json | 53 +- 10 files changed, 2452 insertions(+), 40 deletions(-) create mode 100644 e2etests/testdata/txtar/var_in_markdown/dagre/board.exp.json create mode 100644 e2etests/testdata/txtar/var_in_markdown/dagre/sketch.exp.svg create mode 100644 e2etests/testdata/txtar/var_in_markdown/elk/board.exp.json create mode 100644 e2etests/testdata/txtar/var_in_markdown/elk/sketch.exp.svg create mode 100644 lib/textmeasure/substitutions.go 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 3fb75077f..6870938d0 100644 --- a/d2compiler/compile_test.go +++ b/d2compiler/compile_test.go @@ -2868,17 +2868,28 @@ x*: { { name: "var_in_markdown", text: `vars: { - v: { - ok - } + v: ok } x: |md m${v}y + + ` + "`hey ${v}`" + ` + + regular markdown + + ` + "```" + ` + bye ${v} + ` + "```" + ` | `, assertions: func(t *testing.T, g *d2graph.Graph) { - tassert.Equal(t, "moky", g.Objects[0].Attributes.Label.Value) + 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}")) }, }, { 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 @@ +Kube

Kube is a service

+
Let ${y} be ${x}
+
+
ba + + + + + +
\ 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 @@ +Kube

Kube 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 index aee91bb0d..415e4911d 100644 --- a/testdata/d2compiler/TestCompile/var_in_markdown.exp.json +++ b/testdata/d2compiler/TestCompile/var_in_markdown.exp.json @@ -3,11 +3,11 @@ "name": "", "isFolderOnly": false, "ast": { - "range": "d2/testdata/d2compiler/TestCompile/var_in_markdown.d2,0:0:0-9:0:47", + "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-4:1:27", + "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": [ @@ -27,11 +27,11 @@ "primary": {}, "value": { "map": { - "range": "d2/testdata/d2compiler/TestCompile/var_in_markdown.d2,0:6:6-4:1:27", + "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-3:3:25", + "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": [ @@ -50,31 +50,12 @@ }, "primary": {}, "value": { - "map": { - "range": "d2/testdata/d2compiler/TestCompile/var_in_markdown.d2,1:5:13-3:3:25", - "nodes": [ + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile/var_in_markdown.d2,1:5:13-1:7:15", + "value": [ { - "map_key": { - "range": "d2/testdata/d2compiler/TestCompile/var_in_markdown.d2,2:4:19-2:6:21", - "key": { - "range": "d2/testdata/d2compiler/TestCompile/var_in_markdown.d2,2:4:19-2:6:21", - "path": [ - { - "unquoted_string": { - "range": "d2/testdata/d2compiler/TestCompile/var_in_markdown.d2,2:4:19-2:6:21", - "value": [ - { - "string": "ok", - "raw_string": "ok" - } - ] - } - } - ] - }, - "primary": {}, - "value": {} - } + "string": "ok", + "raw_string": "ok" } ] } @@ -88,13 +69,13 @@ }, { "map_key": { - "range": "d2/testdata/d2compiler/TestCompile/var_in_markdown.d2,6:0:29-8:1:46", + "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,6:0:29-6:1:30", + "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,6:0:29-6:1:30", + "range": "d2/testdata/d2compiler/TestCompile/var_in_markdown.d2,4:0:19-4:1:20", "value": [ { "string": "x", @@ -108,10 +89,10 @@ "primary": {}, "value": { "block_string": { - "range": "d2/testdata/d2compiler/TestCompile/var_in_markdown.d2,6:3:32-8:1:46", + "range": "d2/testdata/d2compiler/TestCompile/var_in_markdown.d2,4:3:22-14:1:89", "quote": "", "tag": "md", - "value": "m${v}y" + "value": "moky\n\n`hey ${v}`\n\nregular markdown\n\n```\nbye ${v}\n```" } } } @@ -149,11 +130,11 @@ "references": [ { "key": { - "range": "d2/testdata/d2compiler/TestCompile/var_in_markdown.d2,6:0:29-6:1:30", + "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,6:0:29-6:1:30", + "range": "d2/testdata/d2compiler/TestCompile/var_in_markdown.d2,4:0:19-4:1:20", "value": [ { "string": "x", @@ -170,7 +151,7 @@ ], "attributes": { "label": { - "value": "m${v}y" + "value": "moky\n\n`hey ${v}`\n\nregular markdown\n\n```\nbye ${v}\n```" }, "labelDimensions": { "width": 0,