diff --git a/ci/release/changelogs/next.md b/ci/release/changelogs/next.md index 6b64ad66c..806b4951e 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 🧹 diff --git a/d2compiler/compile_test.go b/d2compiler/compile_test.go index 35055cb56..a9b1c8c1e 100644 --- a/d2compiler/compile_test.go +++ b/d2compiler/compile_test.go @@ -5197,6 +5197,43 @@ y.link: https://google.com assert.Equal(t, "true", g.Objects[1].Attributes.Style.Underline.Value) }, }, + { + name: "leaf-filter", + run: func(t *testing.T) { + g, _ := assertCompile(t, ` +**: { + &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].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) + 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: "glob-filter", run: func(t *testing.T) { diff --git a/d2ir/compile.go b/d2ir/compile.go index fe28a1c1b..156f7a41c 100644 --- a/d2ir/compile.go +++ b/d2ir/compile.go @@ -750,6 +750,33 @@ 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 "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 000000000..72b949980 --- /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 +} 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 000000000..cfb785772 --- /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 +}