diff --git a/.github/workflows/daily.yml b/.github/workflows/daily.yml
index 3883537f4..1a7075a1f 100644
--- a/.github/workflows/daily.yml
+++ b/.github/workflows/daily.yml
@@ -20,7 +20,7 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets._GITHUB_TOKEN }}
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}
- - uses: actions/upload-artifact@v3
+ - uses: actions/upload-artifact@v4
if: always()
with:
name: d2chaos
diff --git a/README.md b/README.md
index e465f01a5..12133f941 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@
A modern diagram scripting language that turns text to diagrams.
-[Docs](https://d2lang.com) | [Cheat sheet](./docs/assets/cheat_sheet.pdf) | [Comparisons](https://text-to-diagram.com) | [Playground](https://play.d2lang.com)
+[Docs](https://d2lang.com) | [Cheat sheet](./docs/assets/cheat_sheet.pdf) | [Comparisons](https://text-to-diagram.com) | [Playground](https://play.d2lang.com) | [IDE](https://app.terrastruct.com)
[](https://github.com/terrastruct/d2/actions/workflows/ci.yml)
[](https://github.com/terrastruct/d2/actions/workflows/daily.yml)
@@ -16,6 +16,9 @@
+
+
+
https://user-images.githubusercontent.com/3120367/206125010-bd1fea8e-248a-43e7-8f85-0bbfca0c6e2a.mp4
@@ -263,11 +266,13 @@ let us know and we'll be happy to include it here!
- **Remark Plugin**: [https://github.com/mech-a/remark-d2](https://github.com/mech-a/remark-d2)
- **VitePress Plugin**: [https://github.com/BadgerHobbs/vitepress-plugin-d2](https://github.com/BadgerHobbs/vitepress-plugin-d2)
- **Zed extension**: [https://github.com/gabeidx/zed-d2](https://github.com/gabeidx/zed-d2)
+- **Hexo blog extension**: [https://github.com/leverimmy/hexo-d2](https://github.com/leverimmy/hexo-d2)
### Misc
- **Comparison site**: [https://github.com/terrastruct/text-to-diagram-site](https://github.com/terrastruct/text-to-diagram-site)
- **Playground**: [https://github.com/terrastruct/d2-playground](https://github.com/terrastruct/d2-playground)
+- **IDE (paid)**: [https://app.terrastruct.com](https://app.terrastruct.com)
- **Language docs**: [https://github.com/terrastruct/d2-docs](https://github.com/terrastruct/d2-docs)
- **Hosted icons**: [https://icons.terrastruct.com](https://icons.terrastruct.com)
diff --git a/ci/release/changelogs/next.md b/ci/release/changelogs/next.md
index f152d8600..c7fb38b55 100644
--- a/ci/release/changelogs/next.md
+++ b/ci/release/changelogs/next.md
@@ -1,7 +1,13 @@
#### Features ๐
+- Icons: connections can include icons [#12](https://github.com/terrastruct/d2/issues/12)
+
#### Improvements ๐งน
d2js: Support additional render options (`themeID`, `darkThemeID`, `center`, `pad`, `scale` and `forceAppendix`). Support `d2-config`. [#2343](https://github.com/terrastruct/d2/pull/2343)
#### Bugfixes โ๏ธ
+
+- Compiler:
+ - fixes panic when `sql_shape` shape value had mixed casing [#2349](https://github.com/terrastruct/d2/pull/2349)
+ - fixes support for `center` in `d2-config` [#2360](https://github.com/terrastruct/d2/pull/2360)
diff --git a/d2cli/main.go b/d2cli/main.go
index 3e9475149..dbf1ae2c4 100644
--- a/d2cli/main.go
+++ b/d2cli/main.go
@@ -50,7 +50,7 @@ import (
func Run(ctx context.Context, ms *xmain.State) (err error) {
ctx = log.WithDefault(ctx)
// These should be kept up-to-date with the d2 man page
- watchFlag, err := ms.Opts.Bool("D2_WATCH", "watch", "w", false, "watch for changes to input and live reload. Use $HOST and $PORT to specify the listening address.\n(default localhost:0, which is will open on a randomly available local port).")
+ watchFlag, err := ms.Opts.Bool("D2_WATCH", "watch", "w", false, "watch for changes to input and live reload. Use $HOST and $PORT to specify the listening address.\n(default localhost:0, which will open on a randomly available local port).")
if err != nil {
return err
}
diff --git a/d2compiler/compile.go b/d2compiler/compile.go
index 1fa28054e..e8af01b14 100644
--- a/d2compiler/compile.go
+++ b/d2compiler/compile.go
@@ -514,13 +514,14 @@ func (c *compiler) compileReserved(attrs *d2graph.Attributes, f *d2ir.Field) {
c.compileLabel(attrs, f)
c.compilePosition(attrs, f)
case "shape":
- in := d2target.IsShape(scalar.ScalarString())
- _, isArrowhead := d2target.Arrowheads[scalar.ScalarString()]
+ shapeVal := strings.ToLower(scalar.ScalarString())
+ in := d2target.IsShape(shapeVal)
+ _, isArrowhead := d2target.Arrowheads[shapeVal]
if !in && !isArrowhead {
c.errorf(scalar, "unknown shape %q", scalar.ScalarString())
return
}
- attrs.Shape.Value = scalar.ScalarString()
+ attrs.Shape.Value = shapeVal
if strings.EqualFold(attrs.Shape.Value, d2target.ShapeCode) {
// Explicit code shape is plaintext.
attrs.Language = d2target.ShapeText
@@ -596,11 +597,12 @@ func (c *compiler) compileReserved(attrs *d2graph.Attributes, f *d2ir.Field) {
attrs.Link.MapKey = f.LastPrimaryKey()
case "direction":
dirs := []string{"up", "down", "right", "left"}
- if !go2.Contains(dirs, scalar.ScalarString()) {
+ val := strings.ToLower(scalar.ScalarString())
+ if !go2.Contains(dirs, val) {
c.errorf(scalar, `direction must be one of %v, got %q`, strings.Join(dirs, ", "), scalar.ScalarString())
return
}
- attrs.Direction.Value = scalar.ScalarString()
+ attrs.Direction.Value = val
attrs.Direction.MapKey = f.LastPrimaryKey()
case "constraint":
if _, ok := scalar.(d2ast.String); !ok {
@@ -1453,6 +1455,12 @@ func compileConfig(ir *d2ir.Map) (*d2target.Config, error) {
config.LayoutEngine = go2.Pointer(f.Primary().Value.ScalarString())
}
+ f = configMap.GetField(d2ast.FlatUnquotedString("center"))
+ if f != nil {
+ val, _ := strconv.ParseBool(f.Primary().Value.ScalarString())
+ config.Center = &val
+ }
+
f = configMap.GetField(d2ast.FlatUnquotedString("theme-overrides"))
if f != nil {
overrides, err := compileThemeOverrides(f.Map())
diff --git a/d2exporter/export.go b/d2exporter/export.go
index 8b676e554..672bae188 100644
--- a/d2exporter/export.go
+++ b/d2exporter/export.go
@@ -8,6 +8,7 @@ import (
"oss.terrastruct.com/util-go/go2"
+ "oss.terrastruct.com/d2/d2ast"
"oss.terrastruct.com/d2/d2graph"
"oss.terrastruct.com/d2/d2parser"
"oss.terrastruct.com/d2/d2renderers/d2fonts"
@@ -15,6 +16,7 @@ import (
"oss.terrastruct.com/d2/d2themes"
"oss.terrastruct.com/d2/lib/color"
"oss.terrastruct.com/d2/lib/geo"
+ "oss.terrastruct.com/d2/lib/label"
)
func Export(ctx context.Context, g *d2graph.Graph, fontFamily *d2fonts.FontFamily) (*d2target.Diagram, error) {
@@ -335,7 +337,14 @@ func toConnection(edge *d2graph.Edge, theme *d2themes.Theme) d2target.Connection
if edge.Tooltip != nil {
connection.Tooltip = edge.Tooltip.Value
}
- connection.Icon = edge.Icon
+ if edge.Icon != nil {
+ connection.Icon = edge.Icon
+ if edge.IconPosition != nil {
+ connection.IconPosition = (d2ast.LabelPositionsMapping[edge.IconPosition.Value]).String()
+ } else {
+ connection.IconPosition = label.InsideMiddleCenter.String()
+ }
+ }
if edge.Style.Italic != nil {
connection.Italic, _ = strconv.ParseBool(edge.Style.Italic.Value)
diff --git a/d2ir/compile.go b/d2ir/compile.go
index 156f7a41c..60a35cf38 100644
--- a/d2ir/compile.go
+++ b/d2ir/compile.go
@@ -1070,11 +1070,6 @@ func (c *compiler) compileLink(f *Field, refctx *RefContext) {
return
}
- if linkIDA[0].ScalarString() == "root" && linkIDA[0].IsUnquoted() {
- c.errorf(refctx.Key.Key, "cannot refer to root in link")
- return
- }
-
if !linkIDA[0].IsUnquoted() {
return
}
diff --git a/d2oracle/edit_test.go b/d2oracle/edit_test.go
index 543440411..0c51d26da 100644
--- a/d2oracle/edit_test.go
+++ b/d2oracle/edit_test.go
@@ -6115,6 +6115,28 @@ y
exp: `y
a -> b
c -> d
+`,
+ },
+ {
+ name: "underscore_linked",
+ text: `k
+
+layers: {
+ x: {
+ a
+ b: {link: _}
+ }
+}
+`,
+ key: `b`,
+ boardPath: []string{"x"},
+ exp: `k
+
+layers: {
+ x: {
+ a
+ }
+}
`,
},
{
diff --git a/d2renderers/d2svg/d2svg.go b/d2renderers/d2svg/d2svg.go
index f4ece3b24..153b7b345 100644
--- a/d2renderers/d2svg/d2svg.go
+++ b/d2renderers/d2svg/d2svg.go
@@ -587,16 +587,51 @@ func drawConnection(writer io.Writer, diagramHash string, connection d2target.Co
markerEnd = fmt.Sprintf(`marker-end="url(#%s)" `, id)
}
+ if connection.Icon != nil {
+ iconPos := connection.GetIconPosition()
+ if iconPos != nil {
+ fmt.Fprintf(writer, ``,
+ html.EscapeString(connection.Icon.String()),
+ iconPos.X,
+ iconPos.Y,
+ d2target.DEFAULT_ICON_SIZE,
+ d2target.DEFAULT_ICON_SIZE,
+ )
+ }
+ }
+
var labelTL *geo.Point
if connection.Label != "" {
labelTL = connection.GetLabelTopLeft()
labelTL.X = math.Round(labelTL.X)
labelTL.Y = math.Round(labelTL.Y)
+ maskTL := labelTL.Copy()
+ width := connection.LabelWidth
+ height := connection.LabelHeight
+
+ if connection.Icon != nil {
+ width += d2target.CONNECTION_ICON_LABEL_GAP + d2target.DEFAULT_ICON_SIZE
+ maskTL.X -= float64(d2target.CONNECTION_ICON_LABEL_GAP + d2target.DEFAULT_ICON_SIZE)
+ }
+
if label.FromString(connection.LabelPosition).IsOnEdge() {
- labelMask = makeLabelMask(labelTL, connection.LabelWidth, connection.LabelHeight, 1)
+ labelMask = makeLabelMask(maskTL, width, height, 1)
} else {
- labelMask = makeLabelMask(labelTL, connection.LabelWidth, connection.LabelHeight, 0.75)
+ labelMask = makeLabelMask(maskTL, width, height, 0.75)
+ }
+ } else if connection.Icon != nil {
+ iconPos := connection.GetIconPosition()
+ if iconPos != nil {
+ maskTL := &geo.Point{
+ X: iconPos.X,
+ Y: iconPos.Y,
+ }
+ if label.FromString(connection.IconPosition).IsOnEdge() {
+ labelMask = makeLabelMask(maskTL, d2target.DEFAULT_ICON_SIZE, d2target.DEFAULT_ICON_SIZE, 1)
+ } else {
+ labelMask = makeLabelMask(maskTL, d2target.DEFAULT_ICON_SIZE, d2target.DEFAULT_ICON_SIZE, 0.75)
+ }
}
}
diff --git a/d2target/d2target.go b/d2target/d2target.go
index 54c1f2bb4..266e0f184 100644
--- a/d2target/d2target.go
+++ b/d2target/d2target.go
@@ -34,6 +34,8 @@ const (
MIN_ARROWHEAD_STROKE_WIDTH = 2
ARROWHEAD_PADDING = 2.
+
+ CONNECTION_ICON_LABEL_GAP = 8
)
var BorderOffset = geo.NewVector(5, 5)
@@ -609,13 +611,40 @@ type Connection struct {
Route []*geo.Point `json:"route"`
IsCurve bool `json:"isCurve,omitempty"`
- Animated bool `json:"animated"`
- Tooltip string `json:"tooltip"`
- Icon *url.URL `json:"icon"`
+ Animated bool `json:"animated"`
+ Tooltip string `json:"tooltip"`
+ Icon *url.URL `json:"icon"`
+ IconPosition string `json:"iconPosition,omitempty"`
ZIndex int `json:"zIndex"`
}
+func (c *Connection) GetIconPosition() *geo.Point {
+ if c.Icon == nil {
+ return nil
+ }
+
+ if c.Label != "" {
+ labelTL := c.GetLabelTopLeft()
+ if labelTL != nil {
+ // Position icon to the left of the label with a small gap
+ return &geo.Point{
+ X: labelTL.X - CONNECTION_ICON_LABEL_GAP - DEFAULT_ICON_SIZE,
+ Y: labelTL.Y + float64(c.LabelHeight)/2 - DEFAULT_ICON_SIZE/2,
+ }
+ }
+ }
+
+ point, _ := label.FromString(c.IconPosition).GetPointOnRoute(
+ c.Route,
+ float64(c.StrokeWidth),
+ -1,
+ float64(DEFAULT_ICON_SIZE),
+ float64(DEFAULT_ICON_SIZE),
+ )
+ return point
+}
+
func BaseConnection() *Connection {
return &Connection{
SrcArrow: NoArrowhead,
diff --git a/docs/assets/playground_button.png b/docs/assets/playground_button.png
index b08fe380d..a0cd9a212 100644
Binary files a/docs/assets/playground_button.png and b/docs/assets/playground_button.png differ
diff --git a/docs/assets/studio_button.png b/docs/assets/studio_button.png
new file mode 100644
index 000000000..12e65821a
Binary files /dev/null and b/docs/assets/studio_button.png differ
diff --git a/docs/examples/wcc/wcc.d2 b/docs/examples/wcc/wcc.d2
index f47593792..689f18b5b 100644
--- a/docs/examples/wcc/wcc.d2
+++ b/docs/examples/wcc/wcc.d2
@@ -34,7 +34,7 @@ layers: {
link: steps.1
style.font-size: 24
}
-
+
steps: {
1: {
titled: Earn pre-requisite titles (IM)
@@ -150,7 +150,7 @@ layers: {
best of 14 games -> tiebreaks: if needed
tiebreaks.link: layers.tiebreaks
-
+
layers: {
tiebreaks: {
description: |md
diff --git a/e2etests/testdata/txtar/connection-icons/dagre/board.exp.json b/e2etests/testdata/txtar/connection-icons/dagre/board.exp.json
new file mode 100644
index 000000000..96c74348f
--- /dev/null
+++ b/e2etests/testdata/txtar/connection-icons/dagre/board.exp.json
@@ -0,0 +1,306 @@
+{
+ "name": "",
+ "config": {
+ "sketch": false,
+ "themeID": 0,
+ "darkThemeID": null,
+ "pad": null,
+ "center": null,
+ "layoutEngine": null
+ },
+ "isFolderOnly": false,
+ "fontFamily": "SourceSansPro",
+ "shapes": [
+ {
+ "id": "a",
+ "type": "rectangle",
+ "pos": {
+ "x": 0,
+ "y": 0
+ },
+ "width": 53,
+ "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": "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
+ },
+ {
+ "id": "b",
+ "type": "rectangle",
+ "pos": {
+ "x": 186,
+ "y": 0
+ },
+ "width": 53,
+ "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": "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": "c",
+ "type": "rectangle",
+ "pos": {
+ "x": 339,
+ "y": 0
+ },
+ "width": 53,
+ "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": "c",
+ "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": "(a -> b)[0]",
+ "src": "a",
+ "srcArrow": "none",
+ "dst": "b",
+ "dstArrow": "triangle",
+ "opacity": 1,
+ "strokeDash": 0,
+ "strokeWidth": 2,
+ "stroke": "B1",
+ "borderRadius": 10,
+ "label": "hello",
+ "fontSize": 16,
+ "fontFamily": "DEFAULT",
+ "language": "",
+ "color": "N2",
+ "italic": true,
+ "bold": false,
+ "underline": false,
+ "labelWidth": 33,
+ "labelHeight": 21,
+ "labelPosition": "INSIDE_MIDDLE_CENTER",
+ "labelPercentage": 0,
+ "link": "",
+ "route": [
+ {
+ "x": 52.5,
+ "y": 33
+ },
+ {
+ "x": 106.0999984741211,
+ "y": 33
+ },
+ {
+ "x": 132.89999389648438,
+ "y": 33
+ },
+ {
+ "x": 186.5,
+ "y": 33
+ }
+ ],
+ "isCurve": true,
+ "animated": false,
+ "tooltip": "",
+ "icon": {
+ "Scheme": "https",
+ "Opaque": "",
+ "User": null,
+ "Host": "icons.terrastruct.com",
+ "Path": "/essentials/213-alarm.svg",
+ "RawPath": "/essentials%2F213-alarm.svg",
+ "OmitHost": false,
+ "ForceQuery": false,
+ "RawQuery": "",
+ "Fragment": "",
+ "RawFragment": ""
+ },
+ "iconPosition": "INSIDE_MIDDLE_CENTER",
+ "zIndex": 0
+ },
+ {
+ "id": "(b -> c)[0]",
+ "src": "b",
+ "srcArrow": "none",
+ "dst": "c",
+ "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": 239,
+ "y": 33
+ },
+ {
+ "x": 279,
+ "y": 33
+ },
+ {
+ "x": 299,
+ "y": 33
+ },
+ {
+ "x": 339,
+ "y": 33
+ }
+ ],
+ "isCurve": true,
+ "animated": false,
+ "tooltip": "",
+ "icon": {
+ "Scheme": "https",
+ "Opaque": "",
+ "User": null,
+ "Host": "icons.terrastruct.com",
+ "Path": "/essentials/213-alarm.svg",
+ "RawPath": "/essentials%2F213-alarm.svg",
+ "OmitHost": false,
+ "ForceQuery": false,
+ "RawQuery": "",
+ "Fragment": "",
+ "RawFragment": ""
+ },
+ "iconPosition": "INSIDE_MIDDLE_CENTER",
+ "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/connection-icons/dagre/sketch.exp.svg b/e2etests/testdata/txtar/connection-icons/dagre/sketch.exp.svg
new file mode 100644
index 000000000..5e302b9c2
--- /dev/null
+++ b/e2etests/testdata/txtar/connection-icons/dagre/sketch.exp.svg
@@ -0,0 +1,106 @@
+
\ No newline at end of file
diff --git a/e2etests/testdata/txtar/connection-icons/elk/board.exp.json b/e2etests/testdata/txtar/connection-icons/elk/board.exp.json
new file mode 100644
index 000000000..18f1b8346
--- /dev/null
+++ b/e2etests/testdata/txtar/connection-icons/elk/board.exp.json
@@ -0,0 +1,288 @@
+{
+ "name": "",
+ "config": {
+ "sketch": false,
+ "themeID": 0,
+ "darkThemeID": null,
+ "pad": null,
+ "center": null,
+ "layoutEngine": null
+ },
+ "isFolderOnly": false,
+ "fontFamily": "SourceSansPro",
+ "shapes": [
+ {
+ "id": "a",
+ "type": "rectangle",
+ "pos": {
+ "x": 12,
+ "y": 12
+ },
+ "width": 53,
+ "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": "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
+ },
+ {
+ "id": "b",
+ "type": "rectangle",
+ "pos": {
+ "x": 238,
+ "y": 12
+ },
+ "width": 53,
+ "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": "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": "c",
+ "type": "rectangle",
+ "pos": {
+ "x": 361,
+ "y": 12
+ },
+ "width": 53,
+ "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": "c",
+ "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": "(a -> b)[0]",
+ "src": "a",
+ "srcArrow": "none",
+ "dst": "b",
+ "dstArrow": "triangle",
+ "opacity": 1,
+ "strokeDash": 0,
+ "strokeWidth": 2,
+ "stroke": "B1",
+ "borderRadius": 10,
+ "label": "hello",
+ "fontSize": 16,
+ "fontFamily": "DEFAULT",
+ "language": "",
+ "color": "N2",
+ "italic": true,
+ "bold": false,
+ "underline": false,
+ "labelWidth": 33,
+ "labelHeight": 21,
+ "labelPosition": "INSIDE_MIDDLE_CENTER",
+ "labelPercentage": 0,
+ "link": "",
+ "route": [
+ {
+ "x": 65,
+ "y": 45
+ },
+ {
+ "x": 238,
+ "y": 45
+ }
+ ],
+ "animated": false,
+ "tooltip": "",
+ "icon": {
+ "Scheme": "https",
+ "Opaque": "",
+ "User": null,
+ "Host": "icons.terrastruct.com",
+ "Path": "/essentials/213-alarm.svg",
+ "RawPath": "/essentials%2F213-alarm.svg",
+ "OmitHost": false,
+ "ForceQuery": false,
+ "RawQuery": "",
+ "Fragment": "",
+ "RawFragment": ""
+ },
+ "iconPosition": "INSIDE_MIDDLE_CENTER",
+ "zIndex": 0
+ },
+ {
+ "id": "(b -> c)[0]",
+ "src": "b",
+ "srcArrow": "none",
+ "dst": "c",
+ "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": 291,
+ "y": 45
+ },
+ {
+ "x": 361,
+ "y": 45
+ }
+ ],
+ "animated": false,
+ "tooltip": "",
+ "icon": {
+ "Scheme": "https",
+ "Opaque": "",
+ "User": null,
+ "Host": "icons.terrastruct.com",
+ "Path": "/essentials/213-alarm.svg",
+ "RawPath": "/essentials%2F213-alarm.svg",
+ "OmitHost": false,
+ "ForceQuery": false,
+ "RawQuery": "",
+ "Fragment": "",
+ "RawFragment": ""
+ },
+ "iconPosition": "INSIDE_MIDDLE_CENTER",
+ "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/connection-icons/elk/sketch.exp.svg b/e2etests/testdata/txtar/connection-icons/elk/sketch.exp.svg
new file mode 100644
index 000000000..be28cc05d
--- /dev/null
+++ b/e2etests/testdata/txtar/connection-icons/elk/sketch.exp.svg
@@ -0,0 +1,106 @@
+abc hello
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/e2etests/testdata/txtar/sql-casing-panic/dagre/board.exp.json b/e2etests/testdata/txtar/sql-casing-panic/dagre/board.exp.json
new file mode 100644
index 000000000..4832c3200
--- /dev/null
+++ b/e2etests/testdata/txtar/sql-casing-panic/dagre/board.exp.json
@@ -0,0 +1,130 @@
+{
+ "name": "",
+ "config": {
+ "sketch": false,
+ "themeID": 0,
+ "darkThemeID": null,
+ "pad": null,
+ "center": null,
+ "layoutEngine": null
+ },
+ "isFolderOnly": false,
+ "fontFamily": "SourceSansPro",
+ "shapes": [
+ {
+ "id": "asdf",
+ "type": "sql_table",
+ "pos": {
+ "x": 0,
+ "y": 0
+ },
+ "width": 87,
+ "height": 72,
+ "opacity": 1,
+ "strokeDash": 0,
+ "strokeWidth": 2,
+ "borderRadius": 0,
+ "fill": "N1",
+ "stroke": "N7",
+ "animated": false,
+ "shadow": false,
+ "3d": false,
+ "multiple": false,
+ "double-border": false,
+ "tooltip": "",
+ "link": "",
+ "icon": null,
+ "iconPosition": "",
+ "blend": false,
+ "fields": null,
+ "methods": null,
+ "columns": [
+ {
+ "name": {
+ "label": "zxcv",
+ "fontSize": 0,
+ "fontFamily": "",
+ "language": "",
+ "color": "",
+ "italic": false,
+ "bold": false,
+ "underline": false,
+ "labelWidth": 37,
+ "labelHeight": 26
+ },
+ "type": {
+ "label": "",
+ "fontSize": 0,
+ "fontFamily": "",
+ "language": "",
+ "color": "",
+ "italic": false,
+ "bold": false,
+ "underline": false,
+ "labelWidth": 0,
+ "labelHeight": 0
+ },
+ "constraint": null,
+ "reference": ""
+ }
+ ],
+ "label": "asdf",
+ "fontSize": 20,
+ "fontFamily": "DEFAULT",
+ "language": "",
+ "color": "N1",
+ "italic": false,
+ "bold": true,
+ "underline": false,
+ "labelWidth": 46,
+ "labelHeight": 31,
+ "zIndex": 0,
+ "level": 1,
+ "primaryAccentColor": "B2",
+ "secondaryAccentColor": "AA2",
+ "neutralAccentColor": "N2"
+ }
+ ],
+ "connections": [],
+ "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/sql-casing-panic/dagre/sketch.exp.svg b/e2etests/testdata/txtar/sql-casing-panic/dagre/sketch.exp.svg
new file mode 100644
index 000000000..b0fe23859
--- /dev/null
+++ b/e2etests/testdata/txtar/sql-casing-panic/dagre/sketch.exp.svg
@@ -0,0 +1,95 @@
+asdfzxcv
+
+
+
\ No newline at end of file
diff --git a/e2etests/testdata/txtar/sql-casing-panic/elk/board.exp.json b/e2etests/testdata/txtar/sql-casing-panic/elk/board.exp.json
new file mode 100644
index 000000000..ea369be7b
--- /dev/null
+++ b/e2etests/testdata/txtar/sql-casing-panic/elk/board.exp.json
@@ -0,0 +1,130 @@
+{
+ "name": "",
+ "config": {
+ "sketch": false,
+ "themeID": 0,
+ "darkThemeID": null,
+ "pad": null,
+ "center": null,
+ "layoutEngine": null
+ },
+ "isFolderOnly": false,
+ "fontFamily": "SourceSansPro",
+ "shapes": [
+ {
+ "id": "asdf",
+ "type": "sql_table",
+ "pos": {
+ "x": 12,
+ "y": 12
+ },
+ "width": 87,
+ "height": 72,
+ "opacity": 1,
+ "strokeDash": 0,
+ "strokeWidth": 2,
+ "borderRadius": 0,
+ "fill": "N1",
+ "stroke": "N7",
+ "animated": false,
+ "shadow": false,
+ "3d": false,
+ "multiple": false,
+ "double-border": false,
+ "tooltip": "",
+ "link": "",
+ "icon": null,
+ "iconPosition": "",
+ "blend": false,
+ "fields": null,
+ "methods": null,
+ "columns": [
+ {
+ "name": {
+ "label": "zxcv",
+ "fontSize": 0,
+ "fontFamily": "",
+ "language": "",
+ "color": "",
+ "italic": false,
+ "bold": false,
+ "underline": false,
+ "labelWidth": 37,
+ "labelHeight": 26
+ },
+ "type": {
+ "label": "",
+ "fontSize": 0,
+ "fontFamily": "",
+ "language": "",
+ "color": "",
+ "italic": false,
+ "bold": false,
+ "underline": false,
+ "labelWidth": 0,
+ "labelHeight": 0
+ },
+ "constraint": null,
+ "reference": ""
+ }
+ ],
+ "label": "asdf",
+ "fontSize": 20,
+ "fontFamily": "DEFAULT",
+ "language": "",
+ "color": "N1",
+ "italic": false,
+ "bold": true,
+ "underline": false,
+ "labelWidth": 46,
+ "labelHeight": 31,
+ "zIndex": 0,
+ "level": 1,
+ "primaryAccentColor": "B2",
+ "secondaryAccentColor": "AA2",
+ "neutralAccentColor": "N2"
+ }
+ ],
+ "connections": [],
+ "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/sql-casing-panic/elk/sketch.exp.svg b/e2etests/testdata/txtar/sql-casing-panic/elk/sketch.exp.svg
new file mode 100644
index 000000000..0b3e91c06
--- /dev/null
+++ b/e2etests/testdata/txtar/sql-casing-panic/elk/sketch.exp.svg
@@ -0,0 +1,95 @@
+asdfzxcv
+
+
+
\ No newline at end of file
diff --git a/e2etests/txtar.txt b/e2etests/txtar.txt
index 46e7e9dfe..fcc6a7619 100644
--- a/e2etests/txtar.txt
+++ b/e2etests/txtar.txt
@@ -759,3 +759,19 @@ b: {
a.b -> b.c
b.c -> a.a: {style.font-color: red; style.stroke: red; style.fill: mistyrose}
+
+-- sql-casing-panic --
+
+asdf:{
+ shape:sQl_table
+ zxcv
+}
+
+-- connection-icons --
+direction: right
+a -> b: hello {
+ icon: https://icons.terrastruct.com/essentials%2F213-alarm.svg
+}
+b -> c: {
+ icon: https://icons.terrastruct.com/essentials%2F213-alarm.svg
+}
diff --git a/go.mod b/go.mod
index c1229c23d..2fa9a3620 100644
--- a/go.mod
+++ b/go.mod
@@ -29,7 +29,7 @@ require (
golang.org/x/tools v0.25.0
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da
gonum.org/v1/plot v0.14.0
- oss.terrastruct.com/util-go v0.0.0-20241005222610-44c011a04896
+ oss.terrastruct.com/util-go v0.0.0-20250213174338-243d8661088a
)
require (
diff --git a/go.sum b/go.sum
index 1cb060641..3aeb231d1 100644
--- a/go.sum
+++ b/go.sum
@@ -205,7 +205,7 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-oss.terrastruct.com/util-go v0.0.0-20241005222610-44c011a04896 h1:g752s1ECv9FD8GunFOZRGWzjeR0cr/TZdSsjAnFEmL8=
-oss.terrastruct.com/util-go v0.0.0-20241005222610-44c011a04896/go.mod h1:eMWv0sOtD9T2RUl90DLWfuShZCYp4NrsqNpI8eqO6U4=
+oss.terrastruct.com/util-go v0.0.0-20250213174338-243d8661088a h1:UXF/Z9i9tOx/wqGUOn/T12wZeez1Gg0sAVKKl7YUDwM=
+oss.terrastruct.com/util-go v0.0.0-20250213174338-243d8661088a/go.mod h1:eMWv0sOtD9T2RUl90DLWfuShZCYp4NrsqNpI8eqO6U4=
rsc.io/pdf v0.1.1 h1:k1MczvYDUvJBe93bYd7wrZLLUEcLZAuF824/I4e5Xr4=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
diff --git a/testdata/d2compiler/TestCompile2/vars/basic/double-border.exp.json b/testdata/d2compiler/TestCompile2/vars/basic/double-border.exp.json
index 8b7f30d5d..c2ef1281b 100644
--- a/testdata/d2compiler/TestCompile2/vars/basic/double-border.exp.json
+++ b/testdata/d2compiler/TestCompile2/vars/basic/double-border.exp.json
@@ -219,7 +219,7 @@
},
"near_key": null,
"shape": {
- "value": "Circle"
+ "value": "circle"
},
"direction": {
"value": ""
diff --git a/testdata/d2oracle/TestDelete/underscore_linked.exp.json b/testdata/d2oracle/TestDelete/underscore_linked.exp.json
new file mode 100644
index 000000000..5ff160e38
--- /dev/null
+++ b/testdata/d2oracle/TestDelete/underscore_linked.exp.json
@@ -0,0 +1,292 @@
+{
+ "graph": {
+ "name": "",
+ "isFolderOnly": false,
+ "ast": {
+ "range": "d2/testdata/d2oracle/TestDelete/underscore_linked.d2,0:0:0-7:0:32",
+ "nodes": [
+ {
+ "map_key": {
+ "range": "d2/testdata/d2oracle/TestDelete/underscore_linked.d2,0:0:0-0:1:1",
+ "key": {
+ "range": "d2/testdata/d2oracle/TestDelete/underscore_linked.d2,0:0:0-0:1:1",
+ "path": [
+ {
+ "unquoted_string": {
+ "range": "d2/testdata/d2oracle/TestDelete/underscore_linked.d2,0:0:0-0:1:1",
+ "value": [
+ {
+ "string": "k",
+ "raw_string": "k"
+ }
+ ]
+ }
+ }
+ ]
+ },
+ "primary": {},
+ "value": {}
+ }
+ },
+ {
+ "map_key": {
+ "range": "d2/testdata/d2oracle/TestDelete/underscore_linked.d2,2:0:3-6:1:31",
+ "key": {
+ "range": "d2/testdata/d2oracle/TestDelete/underscore_linked.d2,2:0:3-2:6:9",
+ "path": [
+ {
+ "unquoted_string": {
+ "range": "d2/testdata/d2oracle/TestDelete/underscore_linked.d2,2:0:3-2:6:9",
+ "value": [
+ {
+ "string": "layers",
+ "raw_string": "layers"
+ }
+ ]
+ }
+ }
+ ]
+ },
+ "primary": {},
+ "value": {
+ "map": {
+ "range": "d2/testdata/d2oracle/TestDelete/underscore_linked.d2,2:8:11-6:1:31",
+ "nodes": [
+ {
+ "map_key": {
+ "range": "d2/testdata/d2oracle/TestDelete/underscore_linked.d2,3:2:15-5:3:29",
+ "key": {
+ "range": "d2/testdata/d2oracle/TestDelete/underscore_linked.d2,3:2:15-3:3:16",
+ "path": [
+ {
+ "unquoted_string": {
+ "range": "d2/testdata/d2oracle/TestDelete/underscore_linked.d2,3:2:15-3:3:16",
+ "value": [
+ {
+ "string": "x",
+ "raw_string": "x"
+ }
+ ]
+ }
+ }
+ ]
+ },
+ "primary": {},
+ "value": {
+ "map": {
+ "range": "d2/testdata/d2oracle/TestDelete/underscore_linked.d2,3:5:18-5:3:29",
+ "nodes": [
+ {
+ "map_key": {
+ "range": "d2/testdata/d2oracle/TestDelete/underscore_linked.d2,4:4:24-4:5:25",
+ "key": {
+ "range": "d2/testdata/d2oracle/TestDelete/underscore_linked.d2,4:4:24-4:5:25",
+ "path": [
+ {
+ "unquoted_string": {
+ "range": "d2/testdata/d2oracle/TestDelete/underscore_linked.d2,4:4:24-4:5:25",
+ "value": [
+ {
+ "string": "a",
+ "raw_string": "a"
+ }
+ ]
+ }
+ }
+ ]
+ },
+ "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": "k",
+ "id_val": "k",
+ "references": [
+ {
+ "key": {
+ "range": "d2/testdata/d2oracle/TestDelete/underscore_linked.d2,0:0:0-0:1:1",
+ "path": [
+ {
+ "unquoted_string": {
+ "range": "d2/testdata/d2oracle/TestDelete/underscore_linked.d2,0:0:0-0:1:1",
+ "value": [
+ {
+ "string": "k",
+ "raw_string": "k"
+ }
+ ]
+ }
+ }
+ ]
+ },
+ "key_path_index": 0,
+ "map_key_edge_index": -1
+ }
+ ],
+ "attributes": {
+ "label": {
+ "value": "k"
+ },
+ "labelDimensions": {
+ "width": 0,
+ "height": 0
+ },
+ "style": {},
+ "near_key": null,
+ "shape": {
+ "value": "rectangle"
+ },
+ "direction": {
+ "value": ""
+ },
+ "constraint": null
+ },
+ "zIndex": 0
+ }
+ ],
+ "layers": [
+ {
+ "name": "x",
+ "isFolderOnly": false,
+ "ast": {
+ "range": "d2/testdata/d2oracle/TestDelete/underscore_linked.d2,0:0:0-1:0:0",
+ "nodes": [
+ {
+ "map_key": {
+ "range": ",0:0:0-0:0:0",
+ "key": {
+ "range": ",0:0:0-0:0:0",
+ "path": [
+ {
+ "unquoted_string": {
+ "range": "d2/testdata/d2oracle/TestDelete/underscore_linked.d2,4:4:24-4:5:25",
+ "value": [
+ {
+ "string": "a",
+ "raw_string": "a"
+ }
+ ]
+ }
+ }
+ ]
+ },
+ "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": "a",
+ "id_val": "a",
+ "references": [
+ {
+ "key": {
+ "range": "d2/testdata/d2oracle/TestDelete/underscore_linked.d2,4:4:24-4:5:25",
+ "path": [
+ {
+ "unquoted_string": {
+ "range": "d2/testdata/d2oracle/TestDelete/underscore_linked.d2,4:4:24-4:5:25",
+ "value": [
+ {
+ "string": "a",
+ "raw_string": "a"
+ }
+ ]
+ }
+ }
+ ]
+ },
+ "key_path_index": 0,
+ "map_key_edge_index": -1
+ }
+ ],
+ "attributes": {
+ "label": {
+ "value": "a"
+ },
+ "labelDimensions": {
+ "width": 0,
+ "height": 0
+ },
+ "style": {},
+ "near_key": null,
+ "shape": {
+ "value": "rectangle"
+ },
+ "direction": {
+ "value": ""
+ },
+ "constraint": null
+ },
+ "zIndex": 0
+ }
+ ]
+ }
+ ]
+ },
+ "err": ""
+}