From 92cfc5c40a6e0924ab825934e642a7f38b2edc15 Mon Sep 17 00:00:00 2001 From: Alexander Wang Date: Sat, 1 Jul 2023 16:36:33 -0700 Subject: [PATCH] portal keyword --- d2compiler/compile.go | 33 ++ d2compiler/compile_test.go | 19 + d2graph/d2graph.go | 3 + .../d2compiler/TestCompile/portal.exp.json | 370 ++++++++++++++++++ 4 files changed, 425 insertions(+) create mode 100644 testdata/d2compiler/TestCompile/portal.exp.json diff --git a/d2compiler/compile.go b/d2compiler/compile.go index fdb24e551..2dce20ae6 100644 --- a/d2compiler/compile.go +++ b/d2compiler/compile.go @@ -420,6 +420,36 @@ func (c *compiler) compilePosition(attrs *d2graph.Attributes, f *d2ir.Field) { } } +func (c *compiler) compilePortal(attrs *d2graph.Attributes, f *d2ir.Field) { + if f.Map() != nil { + for _, f := range f.Map().Fields { + if f.Name == "portal" { + if f.Primary() == nil { + c.errorf(f.LastPrimaryKey(), `invalid "portal" field`) + } else { + scalar := f.Primary().Value + b, err := strconv.ParseBool(strings.ToLower(scalar.ScalarString())) + if err != nil { + c.errorf(f.LastPrimaryKey(), `invalid "portal" field`) + } + if b { + attrs.Portal = &d2graph.Scalar{} + attrs.Portal.Value = scalar.ScalarString() + attrs.Portal.MapKey = f.LastPrimaryKey() + } + } + } else { + if f.LastPrimaryKey() != nil { + c.errorf(f.LastPrimaryKey(), `unexpected field %s`, f.Name) + } + } + } + if len(f.Map().Edges) > 0 { + c.errorf(f.LastPrimaryKey(), "unexpected edges in map") + } + } +} + func (c *compiler) compileReserved(attrs *d2graph.Attributes, f *d2ir.Field) { if f.Primary() == nil { if f.Composite != nil { @@ -442,6 +472,8 @@ func (c *compiler) compileReserved(attrs *d2graph.Attributes, f *d2ir.Field) { } case "label", "icon": c.compilePosition(attrs, f) + case "link": + c.compilePortal(attrs, f) default: c.errorf(f.LastPrimaryKey(), "reserved field %v does not accept composite", f.Name) } @@ -534,6 +566,7 @@ func (c *compiler) compileReserved(attrs *d2graph.Attributes, f *d2ir.Field) { attrs.Link = &d2graph.Scalar{} attrs.Link.Value = scalar.ScalarString() attrs.Link.MapKey = f.LastPrimaryKey() + c.compilePortal(attrs, f) case "direction": dirs := []string{"up", "down", "right", "left"} if !go2.Contains(dirs, scalar.ScalarString()) { diff --git a/d2compiler/compile_test.go b/d2compiler/compile_test.go index a283166be..9e329c174 100644 --- a/d2compiler/compile_test.go +++ b/d2compiler/compile_test.go @@ -2694,6 +2694,25 @@ object: { `, expErr: `d2/testdata/d2compiler/TestCompile/reserved-composite.d2:1:1: reserved field shape does not accept composite`, }, + { + name: "portal", + + text: `x: { + link: layers.y { + portal: true + } +} + +layers: { + y: { + a + } +} +`, + assertions: func(t *testing.T, g *d2graph.Graph) { + tassert.Equal(t, "true", g.Objects[0].Attributes.Portal.Value) + }, + }, } for _, tc := range testCases { diff --git a/d2graph/d2graph.go b/d2graph/d2graph.go index 26413dcb3..afe2277a1 100644 --- a/d2graph/d2graph.go +++ b/d2graph/d2graph.go @@ -147,6 +147,8 @@ type Attributes struct { LabelPosition *Scalar `json:"labelPosition,omitempty"` IconPosition *Scalar `json:"iconPosition,omitempty"` + Portal *Scalar `json:"portal,omitempty"` + // These names are attached to the rendered elements in SVG // so that users can target them however they like outside of D2 Classes []string `json:"classes,omitempty"` @@ -1666,6 +1668,7 @@ var CompositeReservedKeywords = map[string]struct{}{ "constraint": {}, "label": {}, "icon": {}, + "link": {}, } // StyleKeywords are reserved keywords which cannot exist outside of the "style" keyword diff --git a/testdata/d2compiler/TestCompile/portal.exp.json b/testdata/d2compiler/TestCompile/portal.exp.json new file mode 100644 index 000000000..7f7ae82fc --- /dev/null +++ b/testdata/d2compiler/TestCompile/portal.exp.json @@ -0,0 +1,370 @@ +{ + "graph": { + "name": "", + "isFolderOnly": false, + "ast": { + "range": "d2/testdata/d2compiler/TestCompile/portal.d2,0:0:0-11:0:77", + "nodes": [ + { + "map_key": { + "range": "d2/testdata/d2compiler/TestCompile/portal.d2,0:0:0-4:1:46", + "key": { + "range": "d2/testdata/d2compiler/TestCompile/portal.d2,0:0:0-0:1:1", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile/portal.d2,0:0:0-0:1:1", + "value": [ + { + "string": "x", + "raw_string": "x" + } + ] + } + } + ] + }, + "primary": {}, + "value": { + "map": { + "range": "d2/testdata/d2compiler/TestCompile/portal.d2,0:3:3-4:1:46", + "nodes": [ + { + "map_key": { + "range": "d2/testdata/d2compiler/TestCompile/portal.d2,1:2:7-3:3:44", + "key": { + "range": "d2/testdata/d2compiler/TestCompile/portal.d2,1:2:7-1:6:11", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile/portal.d2,1:2:7-1:6:11", + "value": [ + { + "string": "link", + "raw_string": "link" + } + ] + } + } + ] + }, + "primary": { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile/portal.d2,1:8:13-1:16:21", + "value": [ + { + "string": "layers.y", + "raw_string": "layers.y" + } + ] + } + }, + "value": { + "map": { + "range": "d2/testdata/d2compiler/TestCompile/portal.d2,1:17:22-3:3:44", + "nodes": [ + { + "map_key": { + "range": "d2/testdata/d2compiler/TestCompile/portal.d2,2:4:28-2:16:40", + "key": { + "range": "d2/testdata/d2compiler/TestCompile/portal.d2,2:4:28-2:10:34", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile/portal.d2,2:4:28-2:10:34", + "value": [ + { + "string": "portal", + "raw_string": "portal" + } + ] + } + } + ] + }, + "primary": {}, + "value": { + "boolean": { + "range": "d2/testdata/d2compiler/TestCompile/portal.d2,2:12:36-2:16:40", + "value": true + } + } + } + } + ] + } + } + } + } + ] + } + } + } + }, + { + "map_key": { + "range": "d2/testdata/d2compiler/TestCompile/portal.d2,6:0:48-10:1:76", + "key": { + "range": "d2/testdata/d2compiler/TestCompile/portal.d2,6:0:48-6:6:54", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile/portal.d2,6:0:48-6:6:54", + "value": [ + { + "string": "layers", + "raw_string": "layers" + } + ] + } + } + ] + }, + "primary": {}, + "value": { + "map": { + "range": "d2/testdata/d2compiler/TestCompile/portal.d2,6:8:56-10:1:76", + "nodes": [ + { + "map_key": { + "range": "d2/testdata/d2compiler/TestCompile/portal.d2,7:2:60-9:3:74", + "key": { + "range": "d2/testdata/d2compiler/TestCompile/portal.d2,7:2:60-7:3:61", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile/portal.d2,7:2:60-7:3:61", + "value": [ + { + "string": "y", + "raw_string": "y" + } + ] + } + } + ] + }, + "primary": {}, + "value": { + "map": { + "range": "d2/testdata/d2compiler/TestCompile/portal.d2,7:5:63-9:3:74", + "nodes": [ + { + "map_key": { + "range": "d2/testdata/d2compiler/TestCompile/portal.d2,8:4:69-8:5:70", + "key": { + "range": "d2/testdata/d2compiler/TestCompile/portal.d2,8:4:69-8:5:70", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile/portal.d2,8:4:69-8:5:70", + "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": "x", + "id_val": "x", + "references": [ + { + "key": { + "range": "d2/testdata/d2compiler/TestCompile/portal.d2,0:0:0-0:1:1", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile/portal.d2,0:0:0-0:1:1", + "value": [ + { + "string": "x", + "raw_string": "x" + } + ] + } + } + ] + }, + "key_path_index": 0, + "map_key_edge_index": -1 + } + ], + "attributes": { + "label": { + "value": "x" + }, + "labelDimensions": { + "width": 0, + "height": 0 + }, + "style": {}, + "link": { + "value": "layers.y" + }, + "near_key": null, + "shape": { + "value": "rectangle" + }, + "direction": { + "value": "" + }, + "constraint": null, + "portal": { + "value": "true" + } + }, + "zIndex": 0 + } + ], + "layers": [ + { + "name": "y", + "isFolderOnly": false, + "ast": { + "range": ",1:0:0-2:0:0", + "nodes": [ + { + "map_key": { + "range": ",0:0:0-0:0:0", + "key": { + "range": ",0:0:0-0:0:0", + "path": [ + { + "unquoted_string": { + "range": ",0:0:0-0:0:0", + "value": [ + { + "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/d2compiler/TestCompile/portal.d2,8:4:69-8:5:70", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile/portal.d2,8:4:69-8:5:70", + "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": null +}