From 8f4f4303823ddced5b2b7becae2e4e96552fcf3c Mon Sep 17 00:00:00 2001 From: Alexander Wang Date: Mon, 20 Jan 2025 14:54:07 -0800 Subject: [PATCH 1/4] add test --- d2compiler/compile_test.go | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/d2compiler/compile_test.go b/d2compiler/compile_test.go index 35055cb56e..520d7ca04f 100644 --- a/d2compiler/compile_test.go +++ b/d2compiler/compile_test.go @@ -5197,6 +5197,23 @@ y.link: https://google.com assert.Equal(t, "true", g.Objects[1].Attributes.Style.Underline.Value) }, }, + { + name: "exists-filter", + run: func(t *testing.T) { + g, _ := assertCompile(t, ` +**.style.fill: red { + &leaf: false +} +a.b.c +`, ``) + assert.Equal(t, "a", g.Objects[0].ID) + assert.Equal(t, "red", g.Objects[0].Attributes.Style.Fill.Value) + assert.Equal(t, "b", g.Objects[1].Attributes.Style.Fill.Value) + assert.Equal(t, "red", g.Objects[1].Attributes.Style.Fill.Value) + assert.Equal(t, "c", g.Objects[2].Attributes.Style.Fill.Value) + assert.NotEqual(t, "red", g.Objects[2].Attributes.Style.Fill.Value) + }, + }, { name: "glob-filter", run: func(t *testing.T) { From 8bfb5d73c80f9d25be6609dada12b63310b9e821 Mon Sep 17 00:00:00 2001 From: Alexander Wang Date: Mon, 20 Jan 2025 18:47:26 -0800 Subject: [PATCH 2/4] implement leaf filter --- d2compiler/compile_test.go | 11 +- d2ir/compile.go | 11 + .../TestCompile2/globs/leaf-filter.exp.json | 399 ++++++++++++++++++ 3 files changed, 416 insertions(+), 5 deletions(-) create mode 100644 testdata/d2compiler/TestCompile2/globs/leaf-filter.exp.json diff --git a/d2compiler/compile_test.go b/d2compiler/compile_test.go index 520d7ca04f..640c949b40 100644 --- a/d2compiler/compile_test.go +++ b/d2compiler/compile_test.go @@ -5198,20 +5198,21 @@ y.link: https://google.com }, }, { - name: "exists-filter", + name: "leaf-filter", run: func(t *testing.T) { g, _ := assertCompile(t, ` -**.style.fill: red { +**: { &leaf: false + style.fill: red } a.b.c `, ``) assert.Equal(t, "a", g.Objects[0].ID) assert.Equal(t, "red", g.Objects[0].Attributes.Style.Fill.Value) - assert.Equal(t, "b", g.Objects[1].Attributes.Style.Fill.Value) + assert.Equal(t, "b", g.Objects[1].ID) assert.Equal(t, "red", g.Objects[1].Attributes.Style.Fill.Value) - assert.Equal(t, "c", g.Objects[2].Attributes.Style.Fill.Value) - assert.NotEqual(t, "red", g.Objects[2].Attributes.Style.Fill.Value) + assert.Equal(t, "c", g.Objects[2].ID) + assert.Equal(t, (*d2graph.Scalar)(nil), g.Objects[2].Attributes.Style.Fill) }, }, { diff --git a/d2ir/compile.go b/d2ir/compile.go index fe28a1c1bd..57acd3bb20 100644 --- a/d2ir/compile.go +++ b/d2ir/compile.go @@ -750,6 +750,17 @@ func (c *compiler) ampersandFilter(refctx *RefContext) bool { }, } return c._ampersandFilter(f, refctx) + case "leaf": + raw := refctx.Key.Value.ScalarBox().Unbox().ScalarString() + boolVal, err := strconv.ParseBool(raw) + if err != nil { + c.errorf(refctx.Key, `&leaf must be "true" or "false", got %q`, raw) + return false + } + + f := refctx.ScopeMap.Parent().(*Field) + isLeaf := f.Map() == nil || !f.Map().IsContainer() + return isLeaf == boolVal case "label": f := &Field{} n := refctx.ScopeMap.Parent() diff --git a/testdata/d2compiler/TestCompile2/globs/leaf-filter.exp.json b/testdata/d2compiler/TestCompile2/globs/leaf-filter.exp.json new file mode 100644 index 0000000000..cfb7857726 --- /dev/null +++ b/testdata/d2compiler/TestCompile2/globs/leaf-filter.exp.json @@ -0,0 +1,399 @@ +{ + "graph": { + "name": "", + "isFolderOnly": false, + "ast": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/leaf-filter.d2,0:0:0-6:0:48", + "nodes": [ + { + "map_key": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/leaf-filter.d2,1:0:1-4:1:41", + "key": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/leaf-filter.d2,1:0:1-1:2:3", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/leaf-filter.d2,1:0:1-1:2:3", + "value": [ + { + "string": "**", + "raw_string": "**" + } + ], + "pattern": [ + "*", + "", + "*" + ] + } + } + ] + }, + "primary": {}, + "value": { + "map": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/leaf-filter.d2,1:4:5-4:1:41", + "nodes": [ + { + "map_key": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/leaf-filter.d2,2:2:9-2:14:21", + "ampersand": true, + "key": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/leaf-filter.d2,2:3:10-2:7:14", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/leaf-filter.d2,2:3:10-2:7:14", + "value": [ + { + "string": "leaf", + "raw_string": "leaf" + } + ] + } + } + ] + }, + "primary": {}, + "value": { + "boolean": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/leaf-filter.d2,2:9:16-2:14:21", + "value": false + } + } + } + }, + { + "map_key": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/leaf-filter.d2,3:2:24-3:17:39", + "key": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/leaf-filter.d2,3:2:24-3:12:34", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/leaf-filter.d2,3:2:24-3:7:29", + "value": [ + { + "string": "style", + "raw_string": "style" + } + ] + } + }, + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/leaf-filter.d2,3:8:30-3:12:34", + "value": [ + { + "string": "fill", + "raw_string": "fill" + } + ] + } + } + ] + }, + "primary": {}, + "value": { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/leaf-filter.d2,3:14:36-3:17:39", + "value": [ + { + "string": "red", + "raw_string": "red" + } + ] + } + } + } + } + ] + } + } + } + }, + { + "map_key": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/leaf-filter.d2,5:0:42-5:5:47", + "key": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/leaf-filter.d2,5:0:42-5:5:47", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/leaf-filter.d2,5:0:42-5:1:43", + "value": [ + { + "string": "a", + "raw_string": "a" + } + ] + } + }, + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/leaf-filter.d2,5:2:44-5:3:45", + "value": [ + { + "string": "b", + "raw_string": "b" + } + ] + } + }, + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/leaf-filter.d2,5:4:46-5:5:47", + "value": [ + { + "string": "c", + "raw_string": "c" + } + ] + } + } + ] + }, + "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/TestCompile2/globs/leaf-filter.d2,5:0:42-5:5:47", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/leaf-filter.d2,5:0:42-5:1:43", + "value": [ + { + "string": "a", + "raw_string": "a" + } + ] + } + }, + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/leaf-filter.d2,5:2:44-5:3:45", + "value": [ + { + "string": "b", + "raw_string": "b" + } + ] + } + }, + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/leaf-filter.d2,5:4:46-5:5:47", + "value": [ + { + "string": "c", + "raw_string": "c" + } + ] + } + } + ] + }, + "key_path_index": 0, + "map_key_edge_index": -1 + } + ], + "attributes": { + "label": { + "value": "a" + }, + "labelDimensions": { + "width": 0, + "height": 0 + }, + "style": { + "fill": { + "value": "red" + } + }, + "near_key": null, + "shape": { + "value": "rectangle" + }, + "direction": { + "value": "" + }, + "constraint": null + }, + "zIndex": 0 + }, + { + "id": "b", + "id_val": "b", + "references": [ + { + "key": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/leaf-filter.d2,5:0:42-5:5:47", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/leaf-filter.d2,5:0:42-5:1:43", + "value": [ + { + "string": "a", + "raw_string": "a" + } + ] + } + }, + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/leaf-filter.d2,5:2:44-5:3:45", + "value": [ + { + "string": "b", + "raw_string": "b" + } + ] + } + }, + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/leaf-filter.d2,5:4:46-5:5:47", + "value": [ + { + "string": "c", + "raw_string": "c" + } + ] + } + } + ] + }, + "key_path_index": 1, + "map_key_edge_index": -1 + } + ], + "attributes": { + "label": { + "value": "b" + }, + "labelDimensions": { + "width": 0, + "height": 0 + }, + "style": { + "fill": { + "value": "red" + } + }, + "near_key": null, + "shape": { + "value": "rectangle" + }, + "direction": { + "value": "" + }, + "constraint": null + }, + "zIndex": 0 + }, + { + "id": "c", + "id_val": "c", + "references": [ + { + "key": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/leaf-filter.d2,5:0:42-5:5:47", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/leaf-filter.d2,5:0:42-5:1:43", + "value": [ + { + "string": "a", + "raw_string": "a" + } + ] + } + }, + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/leaf-filter.d2,5:2:44-5:3:45", + "value": [ + { + "string": "b", + "raw_string": "b" + } + ] + } + }, + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/leaf-filter.d2,5:4:46-5:5:47", + "value": [ + { + "string": "c", + "raw_string": "c" + } + ] + } + } + ] + }, + "key_path_index": 2, + "map_key_edge_index": -1 + } + ], + "attributes": { + "label": { + "value": "c" + }, + "labelDimensions": { + "width": 0, + "height": 0 + }, + "style": {}, + "near_key": null, + "shape": { + "value": "rectangle" + }, + "direction": { + "value": "" + }, + "constraint": null + }, + "zIndex": 0 + } + ] + }, + "err": null +} From c12a4f6db0f42247d378ce0a66a7da2f759294e8 Mon Sep 17 00:00:00 2001 From: Alexander Wang Date: Mon, 20 Jan 2025 18:55:23 -0800 Subject: [PATCH 3/4] implement connected filter --- d2compiler/compile_test.go | 19 + d2ir/compile.go | 16 + .../globs/connected-filter.exp.json | 386 ++++++++++++++++++ 3 files changed, 421 insertions(+) create mode 100644 testdata/d2compiler/TestCompile2/globs/connected-filter.exp.json diff --git a/d2compiler/compile_test.go b/d2compiler/compile_test.go index 640c949b40..a9b1c8c1ef 100644 --- a/d2compiler/compile_test.go +++ b/d2compiler/compile_test.go @@ -5206,6 +5206,25 @@ y.link: https://google.com style.fill: red } a.b.c +`, ``) + assert.Equal(t, "a", g.Objects[0].ID) + assert.Equal(t, "red", g.Objects[0].Attributes.Style.Fill.Value) + assert.Equal(t, "b", g.Objects[1].ID) + assert.Equal(t, "red", g.Objects[1].Attributes.Style.Fill.Value) + assert.Equal(t, "c", g.Objects[2].ID) + assert.Equal(t, (*d2graph.Scalar)(nil), g.Objects[2].Attributes.Style.Fill) + }, + }, + { + name: "connected-filter", + run: func(t *testing.T) { + g, _ := assertCompile(t, ` +*: { + &connected: true + style.fill: red +} +a -> b +c `, ``) assert.Equal(t, "a", g.Objects[0].ID) assert.Equal(t, "red", g.Objects[0].Attributes.Style.Fill.Value) diff --git a/d2ir/compile.go b/d2ir/compile.go index 57acd3bb20..156f7a41c1 100644 --- a/d2ir/compile.go +++ b/d2ir/compile.go @@ -761,6 +761,22 @@ func (c *compiler) ampersandFilter(refctx *RefContext) bool { f := refctx.ScopeMap.Parent().(*Field) isLeaf := f.Map() == nil || !f.Map().IsContainer() return isLeaf == boolVal + case "connected": + raw := refctx.Key.Value.ScalarBox().Unbox().ScalarString() + boolVal, err := strconv.ParseBool(raw) + if err != nil { + c.errorf(refctx.Key, `&connected must be "true" or "false", got %q`, raw) + return false + } + f := refctx.ScopeMap.Parent().(*Field) + isConnected := false + for _, r := range f.References { + if r.InEdge() { + isConnected = true + break + } + } + return isConnected == boolVal case "label": f := &Field{} n := refctx.ScopeMap.Parent() diff --git a/testdata/d2compiler/TestCompile2/globs/connected-filter.exp.json b/testdata/d2compiler/TestCompile2/globs/connected-filter.exp.json new file mode 100644 index 0000000000..72b9499804 --- /dev/null +++ b/testdata/d2compiler/TestCompile2/globs/connected-filter.exp.json @@ -0,0 +1,386 @@ +{ + "graph": { + "name": "", + "isFolderOnly": false, + "ast": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/connected-filter.d2,0:0:0-7:0:54", + "nodes": [ + { + "map_key": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/connected-filter.d2,1:0:1-4:1:44", + "key": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/connected-filter.d2,1:0:1-1:1:2", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/connected-filter.d2,1:0:1-1:1:2", + "value": [ + { + "string": "*", + "raw_string": "*" + } + ], + "pattern": [ + "*" + ] + } + } + ] + }, + "primary": {}, + "value": { + "map": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/connected-filter.d2,1:3:4-4:1:44", + "nodes": [ + { + "map_key": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/connected-filter.d2,2:2:8-2:18:24", + "ampersand": true, + "key": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/connected-filter.d2,2:3:9-2:12:18", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/connected-filter.d2,2:3:9-2:12:18", + "value": [ + { + "string": "connected", + "raw_string": "connected" + } + ] + } + } + ] + }, + "primary": {}, + "value": { + "boolean": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/connected-filter.d2,2:14:20-2:18:24", + "value": true + } + } + } + }, + { + "map_key": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/connected-filter.d2,3:2:27-3:17:42", + "key": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/connected-filter.d2,3:2:27-3:12:37", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/connected-filter.d2,3:2:27-3:7:32", + "value": [ + { + "string": "style", + "raw_string": "style" + } + ] + } + }, + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/connected-filter.d2,3:8:33-3:12:37", + "value": [ + { + "string": "fill", + "raw_string": "fill" + } + ] + } + } + ] + }, + "primary": {}, + "value": { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/connected-filter.d2,3:14:39-3:17:42", + "value": [ + { + "string": "red", + "raw_string": "red" + } + ] + } + } + } + } + ] + } + } + } + }, + { + "map_key": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/connected-filter.d2,5:0:45-5:6:51", + "edges": [ + { + "range": "d2/testdata/d2compiler/TestCompile2/globs/connected-filter.d2,5:0:45-5:6:51", + "src": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/connected-filter.d2,5:0:45-5:1:46", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/connected-filter.d2,5:0:45-5:1:46", + "value": [ + { + "string": "a", + "raw_string": "a" + } + ] + } + } + ] + }, + "src_arrow": "", + "dst": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/connected-filter.d2,5:5:50-5:6:51", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/connected-filter.d2,5:5:50-5:6:51", + "value": [ + { + "string": "b", + "raw_string": "b" + } + ] + } + } + ] + }, + "dst_arrow": ">" + } + ], + "primary": {}, + "value": {} + } + }, + { + "map_key": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/connected-filter.d2,6:0:52-6:1:53", + "key": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/connected-filter.d2,6:0:52-6:1:53", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/connected-filter.d2,6:0:52-6:1:53", + "value": [ + { + "string": "c", + "raw_string": "c" + } + ] + } + } + ] + }, + "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": [ + { + "index": 0, + "isCurve": false, + "src_arrow": false, + "dst_arrow": true, + "references": [ + { + "map_key_edge_index": 0 + } + ], + "attributes": { + "label": { + "value": "" + }, + "labelDimensions": { + "width": 0, + "height": 0 + }, + "style": {}, + "near_key": null, + "shape": { + "value": "" + }, + "direction": { + "value": "" + }, + "constraint": null + }, + "zIndex": 0 + } + ], + "objects": [ + { + "id": "a", + "id_val": "a", + "references": [ + { + "key": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/connected-filter.d2,5:0:45-5:1:46", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/connected-filter.d2,5:0:45-5:1:46", + "value": [ + { + "string": "a", + "raw_string": "a" + } + ] + } + } + ] + }, + "key_path_index": 0, + "map_key_edge_index": 0 + } + ], + "attributes": { + "label": { + "value": "a" + }, + "labelDimensions": { + "width": 0, + "height": 0 + }, + "style": { + "fill": { + "value": "red" + } + }, + "near_key": null, + "shape": { + "value": "rectangle" + }, + "direction": { + "value": "" + }, + "constraint": null + }, + "zIndex": 0 + }, + { + "id": "b", + "id_val": "b", + "references": [ + { + "key": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/connected-filter.d2,5:5:50-5:6:51", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/connected-filter.d2,5:5:50-5:6:51", + "value": [ + { + "string": "b", + "raw_string": "b" + } + ] + } + } + ] + }, + "key_path_index": 0, + "map_key_edge_index": 0 + } + ], + "attributes": { + "label": { + "value": "b" + }, + "labelDimensions": { + "width": 0, + "height": 0 + }, + "style": { + "fill": { + "value": "red" + } + }, + "near_key": null, + "shape": { + "value": "rectangle" + }, + "direction": { + "value": "" + }, + "constraint": null + }, + "zIndex": 0 + }, + { + "id": "c", + "id_val": "c", + "references": [ + { + "key": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/connected-filter.d2,6:0:52-6:1:53", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/connected-filter.d2,6:0:52-6:1:53", + "value": [ + { + "string": "c", + "raw_string": "c" + } + ] + } + } + ] + }, + "key_path_index": 0, + "map_key_edge_index": -1 + } + ], + "attributes": { + "label": { + "value": "c" + }, + "labelDimensions": { + "width": 0, + "height": 0 + }, + "style": {}, + "near_key": null, + "shape": { + "value": "rectangle" + }, + "direction": { + "value": "" + }, + "constraint": null + }, + "zIndex": 0 + } + ] + }, + "err": null +} From ce5df700cf74c66b2bfbe2667e835c0be2cf0857 Mon Sep 17 00:00:00 2001 From: Alexander Wang Date: Mon, 20 Jan 2025 18:56:26 -0800 Subject: [PATCH 4/4] next --- ci/release/changelogs/next.md | 1 + 1 file changed, 1 insertion(+) diff --git a/ci/release/changelogs/next.md b/ci/release/changelogs/next.md index 6b64ad66cc..806b4951e5 100644 --- a/ci/release/changelogs/next.md +++ b/ci/release/changelogs/next.md @@ -6,6 +6,7 @@ - Markdown: Github-flavored tables work in `md` blocks [#2221](https://github.com/terrastruct/d2/pull/2221) - `d2 fmt` now supports a `--check` flag [#2253](https://github.com/terrastruct/d2/pull/2253) - CLI: PNG output to stdout is supported using `--stdout-format png -` [#2291](https://github.com/terrastruct/d2/pull/2291) +- Globs: `&connected` and `&leaf` filters are implemented [#2299](https://github.com/terrastruct/d2/pull/2299) #### Improvements 🧹