Skip to content

Commit

Permalink
Merge pull request #2090 from alixander/default-glob
Browse files Browse the repository at this point in the history
globs: filter interoperability with defaults
  • Loading branch information
alixander authored Sep 10, 2024
2 parents f0b3a95 + 96b386a commit 4740645
Show file tree
Hide file tree
Showing 10 changed files with 2,503 additions and 56 deletions.
1 change: 1 addition & 0 deletions ci/release/changelogs/next.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
- Sequence diagram: The spacing between self-referential edges and regular edges is uniform [#2043](https://github.com/terrastruct/d2/pull/2043)
- Compiler: Error on multi-line labels in `sql_table` shapes [#2057](https://github.com/terrastruct/d2/pull/2057)
- Sequence diagram: Image shape actors can use spans and notes [#2056](https://github.com/terrastruct/d2/issues/2056)
- Globs: Filters work with default values (e.g. `&opacity: 1` will capture everything without opacity explicitly set) [#2090](https://github.com/terrastruct/d2/pull/2090)

#### Bugfixes ⛑️

Expand Down
71 changes: 71 additions & 0 deletions d2compiler/compile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4924,6 +4924,77 @@ scenarios: {
assert.Equal(t, "circle", g.Scenarios[1].Objects[1].Attributes.Shape.Value)
},
},
{
name: "default-glob-filter/1",
run: func(t *testing.T) {
g, _ := assertCompile(t, `
*: {
&shape: rectangle
style.fill: red
}
*: {
&style.opacity: 1
style.stroke: blue
}
a
b.shape: circle
c.shape: rectangle
`, ``)
assert.Equal(t, "red", g.Objects[0].Style.Fill.Value)
assert.Equal(t, "blue", g.Objects[0].Style.Stroke.Value)
assert.Equal(t, (*d2graph.Scalar)(nil), g.Objects[1].Style.Fill)
assert.Equal(t, "red", g.Objects[2].Style.Fill.Value)
},
},
{
name: "default-glob-filter/2",
run: func(t *testing.T) {
g, _ := assertCompile(t, `
*: {
&shape: rectangle
style.opacity: 0.2
}
a
b -> c
`, ``)
assert.Equal(t, "0.2", g.Objects[0].Style.Opacity.Value)
assert.Equal(t, (*d2graph.Scalar)(nil), g.Edges[0].Style.Opacity)
},
},
{
name: "default-glob-filter/3",
run: func(t *testing.T) {
g, _ := assertCompile(t, `
*: {
&icon: ""
style.opacity: 0.2
}
a
b.icon: https://google.com/cat.jpg
`, ``)
assert.Equal(t, "0.2", g.Objects[0].Style.Opacity.Value)
assert.Equal(t, (*d2graph.Scalar)(nil), g.Objects[1].Style.Opacity)
},
},
{
name: "default-glob-filter/4",
run: func(t *testing.T) {
g, _ := assertCompile(t, `
*: {
&opacity: 1
style.stroke: red
}
(* -> *)[*]: {
&opacity: 1
style.stroke: red
}
a
b -> c
`, ``)
assert.Equal(t, "red", g.Objects[0].Style.Stroke.Value)
assert.Equal(t, "red", g.Edges[0].Style.Stroke.Value)
},
},
}

for _, tc := range tca {
Expand Down
94 changes: 64 additions & 30 deletions d2ir/compile.go
Original file line number Diff line number Diff line change
Expand Up @@ -674,44 +674,78 @@ func (c *compiler) ampersandFilter(refctx *RefContext) bool {
return false
}
if len(fa) == 0 {
if refctx.Key.Key.Last().ScalarString() != "label" {
if refctx.Key.Value.ScalarBox().Unbox().ScalarString() == "*" {
return false
}
kp := refctx.Key.Key.Copy()
kp.Path = kp.Path[:len(kp.Path)-1]
if len(kp.Path) == 0 {
// The field/edge has no value for this filter
// But the filter might still match default, e.g. opacity 1
// So we make a fake field for the default
// NOTE: this does not apply to things that themes control, like stroke and fill
// Nor does it apply to layout things like width and height
switch refctx.Key.Key.Last().ScalarString() {
case "shape":
f := &Field{
Primary_: &Scalar{
Value: d2ast.FlatUnquotedString("rectangle"),
},
}
return c._ampersandFilter(f, refctx)
case "border-radius", "stroke-dash":
f := &Field{
Primary_: &Scalar{
Value: d2ast.FlatUnquotedString("0"),
},
}
return c._ampersandFilter(f, refctx)
case "opacity":
f := &Field{
Primary_: &Scalar{
Value: d2ast.FlatUnquotedString("1"),
},
}
return c._ampersandFilter(f, refctx)
case "stroke-width":
f := &Field{
Primary_: &Scalar{
Value: d2ast.FlatUnquotedString("2"),
},
}
return c._ampersandFilter(f, refctx)
case "icon", "tooltip", "link":
f := &Field{
Primary_: &Scalar{
Value: d2ast.FlatUnquotedString(""),
},
}
return c._ampersandFilter(f, refctx)
case "shadow", "multiple", "3d", "animated", "filled":
f := &Field{
Primary_: &Scalar{
Value: d2ast.FlatUnquotedString("false"),
},
}
return c._ampersandFilter(f, refctx)
case "label":
f := &Field{}
n := refctx.ScopeMap.Parent()
switch n := n.(type) {
case *Field:
fa = append(fa, n)
case *Edge:
if n.Primary_ == nil {
if refctx.Key.Value.ScalarBox().Unbox().ScalarString() == "" {
return true
if n.Primary() == nil {
switch n := n.(type) {
case *Field:
// The label value for fields is their key value
f.Primary_ = &Scalar{
Value: d2ast.FlatUnquotedString(n.Name),
}
case *Edge:
// But for edges, it's nothing
return false
}
if n.Primary_.Value.ScalarString() != refctx.Key.Value.ScalarBox().Unbox().ScalarString() {
return false
}
}
} else {
fa, err = refctx.ScopeMap.EnsureField(kp, refctx, false, c)
if err != nil {
c.err.Errors = append(c.err.Errors, err.(d2ast.Error))
return false
}
}
for _, f := range fa {
label := f.Name
if f.Primary_ != nil {
label = f.Primary_.Value.ScalarString()
}
if label != refctx.Key.Value.ScalarBox().Unbox().ScalarString() {
return false
} else {
f.Primary_ = n.Primary()
}
return c._ampersandFilter(f, refctx)
default:
return false
}
return true
}
for _, f := range fa {
ok := c._ampersandFilter(f, refctx)
Expand Down
1 change: 0 additions & 1 deletion d2ir/filter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,6 @@ x -> y: hi
}
a
# if i remove this line, the glob applies as expected
b
b.label: a
`)
Expand Down
Loading

0 comments on commit 4740645

Please sign in to comment.