commit
5e151c1e0a
46 changed files with 191 additions and 1060 deletions
|
|
@ -29,6 +29,8 @@
|
||||||
So you can run `go install oss.terrastruct.com/d2@latest` to install from source
|
So you can run `go install oss.terrastruct.com/d2@latest` to install from source
|
||||||
now.
|
now.
|
||||||
[#290](https://github.com/terrastruct/d2/pull/290)
|
[#290](https://github.com/terrastruct/d2/pull/290)
|
||||||
|
- `BROWSER=0` now works to disable opening a browser on `--watch`.
|
||||||
|
[#311](https://github.com/terrastruct/d2/pull/311)
|
||||||
|
|
||||||
#### Bugfixes 🔴
|
#### Bugfixes 🔴
|
||||||
|
|
||||||
|
|
@ -42,3 +44,6 @@
|
||||||
[#224](https://github.com/terrastruct/d2/pull/224)
|
[#224](https://github.com/terrastruct/d2/pull/224)
|
||||||
- Avoid logging benign file watching errors.
|
- Avoid logging benign file watching errors.
|
||||||
[#293](https://github.com/terrastruct/d2/pull/293)
|
[#293](https://github.com/terrastruct/d2/pull/293)
|
||||||
|
- `$BROWSER` now works to open a custom browser correctly.
|
||||||
|
For example, to open Firefox on macOS: `BROWSER='open -aFirefox'`
|
||||||
|
[#311](https://github.com/terrastruct/d2/pull/311)
|
||||||
|
|
|
||||||
2
ci/sub
2
ci/sub
|
|
@ -1 +1 @@
|
||||||
Subproject commit e894a489abaead7314a3f126cdb32f18b272a23f
|
Subproject commit ea5566075122e826293ef3cf98c5fffcbee99341
|
||||||
|
|
@ -3,8 +3,9 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"oss.terrastruct.com/util-go/xmain"
|
||||||
|
|
||||||
"oss.terrastruct.com/d2/d2plugin"
|
"oss.terrastruct.com/d2/d2plugin"
|
||||||
"oss.terrastruct.com/d2/lib/xmain"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ import (
|
||||||
"unicode/utf16"
|
"unicode/utf16"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
"oss.terrastruct.com/xdefer"
|
"oss.terrastruct.com/util-go/xdefer"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Node is the base interface implemented by all d2 AST nodes.
|
// Node is the base interface implemented by all d2 AST nodes.
|
||||||
|
|
|
||||||
|
|
@ -9,14 +9,16 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"oss.terrastruct.com/xrand"
|
"oss.terrastruct.com/util-go/assert"
|
||||||
|
"oss.terrastruct.com/util-go/xrand"
|
||||||
|
|
||||||
"oss.terrastruct.com/diff"
|
"oss.terrastruct.com/util-go/diff"
|
||||||
|
|
||||||
|
"oss.terrastruct.com/util-go/go2"
|
||||||
|
|
||||||
"oss.terrastruct.com/d2/d2ast"
|
"oss.terrastruct.com/d2/d2ast"
|
||||||
"oss.terrastruct.com/d2/d2format"
|
"oss.terrastruct.com/d2/d2format"
|
||||||
"oss.terrastruct.com/d2/d2parser"
|
"oss.terrastruct.com/d2/d2parser"
|
||||||
"oss.terrastruct.com/d2/lib/go2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestRange(t *testing.T) {
|
func TestRange(t *testing.T) {
|
||||||
|
|
@ -193,18 +195,18 @@ func TestRange(t *testing.T) {
|
||||||
|
|
||||||
var p d2ast.Position
|
var p d2ast.Position
|
||||||
p = p.Advance('a', false)
|
p = p.Advance('a', false)
|
||||||
diff.AssertJSONEq(t, `"0:1:1"`, p)
|
assert.StringJSON(t, `"0:1:1"`, p)
|
||||||
p = p.Advance('\n', false)
|
p = p.Advance('\n', false)
|
||||||
diff.AssertJSONEq(t, `"1:0:2"`, p)
|
assert.StringJSON(t, `"1:0:2"`, p)
|
||||||
p = p.Advance('è', false)
|
p = p.Advance('è', false)
|
||||||
diff.AssertJSONEq(t, `"1:2:4"`, p)
|
assert.StringJSON(t, `"1:2:4"`, p)
|
||||||
p = p.Advance('𐀀', false)
|
p = p.Advance('𐀀', false)
|
||||||
diff.AssertJSONEq(t, `"1:6:8"`, p)
|
assert.StringJSON(t, `"1:6:8"`, p)
|
||||||
|
|
||||||
p = p.Subtract('𐀀', false)
|
p = p.Subtract('𐀀', false)
|
||||||
diff.AssertJSONEq(t, `"1:2:4"`, p)
|
assert.StringJSON(t, `"1:2:4"`, p)
|
||||||
p = p.Subtract('è', false)
|
p = p.Subtract('è', false)
|
||||||
diff.AssertJSONEq(t, `"1:0:2"`, p)
|
assert.StringJSON(t, `"1:0:2"`, p)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("UTF-16", func(t *testing.T) {
|
t.Run("UTF-16", func(t *testing.T) {
|
||||||
|
|
@ -212,18 +214,18 @@ func TestRange(t *testing.T) {
|
||||||
|
|
||||||
var p d2ast.Position
|
var p d2ast.Position
|
||||||
p = p.Advance('a', true)
|
p = p.Advance('a', true)
|
||||||
diff.AssertJSONEq(t, `"0:1:1"`, p)
|
assert.StringJSON(t, `"0:1:1"`, p)
|
||||||
p = p.Advance('\n', true)
|
p = p.Advance('\n', true)
|
||||||
diff.AssertJSONEq(t, `"1:0:2"`, p)
|
assert.StringJSON(t, `"1:0:2"`, p)
|
||||||
p = p.Advance('è', true)
|
p = p.Advance('è', true)
|
||||||
diff.AssertJSONEq(t, `"1:1:3"`, p)
|
assert.StringJSON(t, `"1:1:3"`, p)
|
||||||
p = p.Advance('𐀀', true)
|
p = p.Advance('𐀀', true)
|
||||||
diff.AssertJSONEq(t, `"1:3:5"`, p)
|
assert.StringJSON(t, `"1:3:5"`, p)
|
||||||
|
|
||||||
p = p.Subtract('𐀀', true)
|
p = p.Subtract('𐀀', true)
|
||||||
diff.AssertJSONEq(t, `"1:1:3"`, p)
|
assert.StringJSON(t, `"1:1:3"`, p)
|
||||||
p = p.Subtract('è', true)
|
p = p.Subtract('è', true)
|
||||||
diff.AssertJSONEq(t, `"1:0:2"`, p)
|
assert.StringJSON(t, `"1:0:2"`, p)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -411,7 +413,7 @@ name to "America".
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
diff.AssertJSONEq(t, `{
|
assert.StringJSON(t, `{
|
||||||
"range": "json_test.d2,0:0:0-5:1:50",
|
"range": "json_test.d2,0:0:0-5:1:50",
|
||||||
"nodes": [
|
"nodes": [
|
||||||
{
|
{
|
||||||
|
|
@ -807,7 +809,7 @@ _park `,
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
ast := d2ast.RawString(tc.str, tc.inKey)
|
ast := d2ast.RawString(tc.str, tc.inKey)
|
||||||
diff.AssertStringEq(t, tc.exp, d2format.Format(ast))
|
assert.String(t, tc.exp, d2format.Format(ast))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,13 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"oss.terrastruct.com/util-go/go2"
|
||||||
|
|
||||||
"oss.terrastruct.com/d2/d2ast"
|
"oss.terrastruct.com/d2/d2ast"
|
||||||
"oss.terrastruct.com/d2/d2format"
|
"oss.terrastruct.com/d2/d2format"
|
||||||
"oss.terrastruct.com/d2/d2graph"
|
"oss.terrastruct.com/d2/d2graph"
|
||||||
"oss.terrastruct.com/d2/d2oracle"
|
"oss.terrastruct.com/d2/d2oracle"
|
||||||
"oss.terrastruct.com/d2/d2target"
|
"oss.terrastruct.com/d2/d2target"
|
||||||
"oss.terrastruct.com/d2/lib/go2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func GenDSL(maxi int) (_ string, err error) {
|
func GenDSL(maxi int) (_ string, err error) {
|
||||||
|
|
|
||||||
|
|
@ -8,12 +8,13 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"oss.terrastruct.com/util-go/go2"
|
||||||
|
|
||||||
"oss.terrastruct.com/d2/d2ast"
|
"oss.terrastruct.com/d2/d2ast"
|
||||||
"oss.terrastruct.com/d2/d2format"
|
"oss.terrastruct.com/d2/d2format"
|
||||||
"oss.terrastruct.com/d2/d2graph"
|
"oss.terrastruct.com/d2/d2graph"
|
||||||
"oss.terrastruct.com/d2/d2parser"
|
"oss.terrastruct.com/d2/d2parser"
|
||||||
"oss.terrastruct.com/d2/d2target"
|
"oss.terrastruct.com/d2/d2target"
|
||||||
"oss.terrastruct.com/d2/lib/go2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO: should Parse even be exported? guess not. IR should contain list of files and
|
// TODO: should Parse even be exported? guess not. IR should contain list of files and
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,8 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"oss.terrastruct.com/diff"
|
"oss.terrastruct.com/util-go/assert"
|
||||||
|
"oss.terrastruct.com/util-go/diff"
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
|
|
||||||
"oss.terrastruct.com/d2/d2compiler"
|
"oss.terrastruct.com/d2/d2compiler"
|
||||||
"oss.terrastruct.com/d2/d2format"
|
"oss.terrastruct.com/d2/d2format"
|
||||||
|
|
@ -775,8 +774,8 @@ x -> y: {
|
||||||
if len(g.Objects) != 2 {
|
if len(g.Objects) != 2 {
|
||||||
t.Fatalf("expected 2 objects: %#v", g.Objects)
|
t.Fatalf("expected 2 objects: %#v", g.Objects)
|
||||||
}
|
}
|
||||||
diff.AssertStringEq(t, "diamond", g.Edges[0].SrcArrowhead.Shape.Value)
|
assert.String(t, "diamond", g.Edges[0].SrcArrowhead.Shape.Value)
|
||||||
assert.Empty(t, g.Edges[0].Attributes.Shape.Value)
|
assert.String(t, "", g.Edges[0].Attributes.Shape.Value)
|
||||||
// Make sure the DSL didn't change. this is a regression test where it did
|
// Make sure the DSL didn't change. this is a regression test where it did
|
||||||
exp := `x -> y: {
|
exp := `x -> y: {
|
||||||
source-arrowhead: {
|
source-arrowhead: {
|
||||||
|
|
@ -814,13 +813,13 @@ x -> y: {
|
||||||
if len(g.Objects) != 2 {
|
if len(g.Objects) != 2 {
|
||||||
t.Fatalf("expected 2 objects: %#v", g.Objects)
|
t.Fatalf("expected 2 objects: %#v", g.Objects)
|
||||||
}
|
}
|
||||||
diff.AssertStringEq(t, "diamond", g.Edges[0].SrcArrowhead.Shape.Value)
|
assert.String(t, "diamond", g.Edges[0].SrcArrowhead.Shape.Value)
|
||||||
diff.AssertStringEq(t, "Reisner's Rule of Conceptual Inertia", g.Edges[0].SrcArrowhead.Label.Value)
|
assert.String(t, "Reisner's Rule of Conceptual Inertia", g.Edges[0].SrcArrowhead.Label.Value)
|
||||||
diff.AssertStringEq(t, "QOTD", g.Edges[0].DstArrowhead.Label.Value)
|
assert.String(t, "QOTD", g.Edges[0].DstArrowhead.Label.Value)
|
||||||
diff.AssertStringEq(t, "true", g.Edges[0].DstArrowhead.Style.Filled.Value)
|
assert.String(t, "true", g.Edges[0].DstArrowhead.Style.Filled.Value)
|
||||||
assert.Empty(t, g.Edges[0].Attributes.Shape.Value)
|
assert.String(t, "", g.Edges[0].Attributes.Shape.Value)
|
||||||
assert.Empty(t, g.Edges[0].Attributes.Label.Value)
|
assert.String(t, "", g.Edges[0].Attributes.Label.Value)
|
||||||
assert.Nil(t, g.Edges[0].Attributes.Style.Filled)
|
assert.JSON(t, nil, g.Edges[0].Attributes.Style.Filled)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -836,8 +835,8 @@ x -> y: {
|
||||||
if len(g.Objects) != 2 {
|
if len(g.Objects) != 2 {
|
||||||
t.Fatalf("expected 2 objects: %#v", g.Objects)
|
t.Fatalf("expected 2 objects: %#v", g.Objects)
|
||||||
}
|
}
|
||||||
diff.AssertStringEq(t, "diamond", g.Edges[0].SrcArrowhead.Shape.Value)
|
assert.String(t, "diamond", g.Edges[0].SrcArrowhead.Shape.Value)
|
||||||
assert.Empty(t, g.Edges[0].Attributes.Shape.Value)
|
assert.String(t, "", g.Edges[0].Attributes.Shape.Value)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -853,8 +852,8 @@ x -> y: {
|
||||||
if len(g.Objects) != 2 {
|
if len(g.Objects) != 2 {
|
||||||
t.Fatalf("expected 2 objects: %#v", g.Objects)
|
t.Fatalf("expected 2 objects: %#v", g.Objects)
|
||||||
}
|
}
|
||||||
diff.AssertStringEq(t, "triangle", g.Edges[0].SrcArrowhead.Shape.Value)
|
assert.String(t, "triangle", g.Edges[0].SrcArrowhead.Shape.Value)
|
||||||
assert.Empty(t, g.Edges[0].Attributes.Shape.Value)
|
assert.String(t, "", g.Edges[0].Attributes.Shape.Value)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -880,8 +879,8 @@ x -> y: {
|
||||||
if len(g.Objects) != 2 {
|
if len(g.Objects) != 2 {
|
||||||
t.Fatalf("expected 2 objects: %#v", g.Objects)
|
t.Fatalf("expected 2 objects: %#v", g.Objects)
|
||||||
}
|
}
|
||||||
diff.AssertStringEq(t, "yo", g.Edges[0].SrcArrowhead.Label.Value)
|
assert.String(t, "yo", g.Edges[0].SrcArrowhead.Label.Value)
|
||||||
assert.Empty(t, g.Edges[0].Attributes.Label.Value)
|
assert.String(t, "", g.Edges[0].Attributes.Label.Value)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -899,8 +898,8 @@ x -> y: {
|
||||||
if len(g.Objects) != 2 {
|
if len(g.Objects) != 2 {
|
||||||
t.Fatalf("expected 2 objects: %#v", g.Objects)
|
t.Fatalf("expected 2 objects: %#v", g.Objects)
|
||||||
}
|
}
|
||||||
diff.AssertStringEq(t, "diamond", g.Edges[0].SrcArrowhead.Shape.Value)
|
assert.String(t, "diamond", g.Edges[0].SrcArrowhead.Shape.Value)
|
||||||
assert.Empty(t, g.Edges[0].Attributes.Shape.Value)
|
assert.String(t, "", g.Edges[0].Attributes.Shape.Value)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -920,9 +919,9 @@ x -> y: {
|
||||||
if len(g.Objects) != 2 {
|
if len(g.Objects) != 2 {
|
||||||
t.Fatalf("expected 2 objects: %#v", g.Objects)
|
t.Fatalf("expected 2 objects: %#v", g.Objects)
|
||||||
}
|
}
|
||||||
diff.AssertStringEq(t, "diamond", g.Edges[0].SrcArrowhead.Shape.Value)
|
assert.String(t, "diamond", g.Edges[0].SrcArrowhead.Shape.Value)
|
||||||
diff.AssertStringEq(t, "diamond", g.Edges[0].DstArrowhead.Shape.Value)
|
assert.String(t, "diamond", g.Edges[0].DstArrowhead.Shape.Value)
|
||||||
assert.Empty(t, g.Edges[0].Attributes.Shape.Value)
|
assert.String(t, "", g.Edges[0].Attributes.Shape.Value)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -1333,7 +1332,7 @@ y -> x.style
|
||||||
if len(g.Objects) != 1 {
|
if len(g.Objects) != 1 {
|
||||||
t.Fatal(g.Objects)
|
t.Fatal(g.Objects)
|
||||||
}
|
}
|
||||||
diff.AssertStringEq(t, `b
|
assert.String(t, `b
|
||||||
b`, g.Objects[0].Attributes.Label.Value)
|
b`, g.Objects[0].Attributes.Label.Value)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -1420,9 +1419,9 @@ b`, g.Objects[0].Attributes.Label.Value)
|
||||||
if len(g.Objects) != 1 {
|
if len(g.Objects) != 1 {
|
||||||
t.Fatal(g.Objects)
|
t.Fatal(g.Objects)
|
||||||
}
|
}
|
||||||
diff.AssertStringEq(t, `field here`, g.Objects[0].Class.Fields[0].Name)
|
assert.String(t, `field here`, g.Objects[0].Class.Fields[0].Name)
|
||||||
diff.AssertStringEq(t, `GetType()`, g.Objects[0].Class.Methods[0].Name)
|
assert.String(t, `GetType()`, g.Objects[0].Class.Methods[0].Name)
|
||||||
diff.AssertStringEq(t, `Is()`, g.Objects[0].Class.Methods[1].Name)
|
assert.String(t, `Is()`, g.Objects[0].Class.Methods[1].Name)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -1438,8 +1437,8 @@ b`, g.Objects[0].Attributes.Label.Value)
|
||||||
if len(g.Objects) != 1 {
|
if len(g.Objects) != 1 {
|
||||||
t.Fatal(g.Objects)
|
t.Fatal(g.Objects)
|
||||||
}
|
}
|
||||||
diff.AssertStringEq(t, `GetType()`, g.Objects[0].SQLTable.Columns[0].Name)
|
assert.String(t, `GetType()`, g.Objects[0].SQLTable.Columns[0].Name)
|
||||||
diff.AssertStringEq(t, `Is()`, g.Objects[0].SQLTable.Columns[1].Name)
|
assert.String(t, `Is()`, g.Objects[0].SQLTable.Columns[1].Name)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -1463,8 +1462,8 @@ b`, g.Objects[0].Attributes.Label.Value)
|
||||||
if len(g.Objects[0].ChildrenArray) != 1 {
|
if len(g.Objects[0].ChildrenArray) != 1 {
|
||||||
t.Fatal(g.Objects)
|
t.Fatal(g.Objects)
|
||||||
}
|
}
|
||||||
diff.AssertStringEq(t, `GetType()`, g.Objects[1].SQLTable.Columns[0].Name)
|
assert.String(t, `GetType()`, g.Objects[1].SQLTable.Columns[0].Name)
|
||||||
diff.AssertStringEq(t, `Is()`, g.Objects[1].SQLTable.Columns[1].Name)
|
assert.String(t, `Is()`, g.Objects[1].SQLTable.Columns[1].Name)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -1509,7 +1508,7 @@ dst.id <-> src.dst_id
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
assertions: func(t *testing.T, g *d2graph.Graph) {
|
assertions: func(t *testing.T, g *d2graph.Graph) {
|
||||||
diff.AssertStringEq(t, "sequence_diagram", g.Objects[0].Attributes.Shape.Value)
|
assert.String(t, "sequence_diagram", g.Objects[0].Attributes.Shape.Value)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -1518,7 +1517,7 @@ dst.id <-> src.dst_id
|
||||||
text: `shape: sequence_diagram
|
text: `shape: sequence_diagram
|
||||||
`,
|
`,
|
||||||
assertions: func(t *testing.T, g *d2graph.Graph) {
|
assertions: func(t *testing.T, g *d2graph.Graph) {
|
||||||
diff.AssertStringEq(t, "sequence_diagram", g.Root.Attributes.Shape.Value)
|
assert.String(t, "sequence_diagram", g.Root.Attributes.Shape.Value)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -1526,7 +1525,7 @@ dst.id <-> src.dst_id
|
||||||
|
|
||||||
text: `direction: right`,
|
text: `direction: right`,
|
||||||
assertions: func(t *testing.T, g *d2graph.Graph) {
|
assertions: func(t *testing.T, g *d2graph.Graph) {
|
||||||
diff.AssertStringEq(t, "right", g.Root.Attributes.Direction.Value)
|
assert.String(t, "right", g.Root.Attributes.Direction.Value)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -1534,7 +1533,7 @@ dst.id <-> src.dst_id
|
||||||
|
|
||||||
text: `x`,
|
text: `x`,
|
||||||
assertions: func(t *testing.T, g *d2graph.Graph) {
|
assertions: func(t *testing.T, g *d2graph.Graph) {
|
||||||
diff.AssertStringEq(t, "", g.Objects[0].Attributes.Direction.Value)
|
assert.String(t, "", g.Objects[0].Attributes.Direction.Value)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -1544,7 +1543,7 @@ dst.id <-> src.dst_id
|
||||||
direction: left
|
direction: left
|
||||||
}`,
|
}`,
|
||||||
assertions: func(t *testing.T, g *d2graph.Graph) {
|
assertions: func(t *testing.T, g *d2graph.Graph) {
|
||||||
diff.AssertStringEq(t, "left", g.Objects[0].Attributes.Direction.Value)
|
assert.String(t, "left", g.Objects[0].Attributes.Direction.Value)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -1594,10 +1593,8 @@ dst.id <-> src.dst_id
|
||||||
Err: err,
|
Err: err,
|
||||||
}
|
}
|
||||||
|
|
||||||
err = diff.Testdata(filepath.Join("..", "testdata", "d2compiler", t.Name()), got)
|
err = diff.TestdataJSON(filepath.Join("..", "testdata", "d2compiler", t.Name()), got)
|
||||||
if err != nil {
|
assert.Success(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,8 @@ import (
|
||||||
|
|
||||||
"cdr.dev/slog"
|
"cdr.dev/slog"
|
||||||
|
|
||||||
"oss.terrastruct.com/diff"
|
"oss.terrastruct.com/util-go/assert"
|
||||||
|
"oss.terrastruct.com/util-go/diff"
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
|
|
||||||
"oss.terrastruct.com/d2/d2compiler"
|
"oss.terrastruct.com/d2/d2compiler"
|
||||||
"oss.terrastruct.com/d2/d2exporter"
|
"oss.terrastruct.com/d2/d2exporter"
|
||||||
|
|
@ -215,10 +214,10 @@ func run(t *testing.T, tc testCase) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ruler, err := textmeasure.NewRuler()
|
ruler, err := textmeasure.NewRuler()
|
||||||
assert.Nil(t, err)
|
assert.JSON(t, nil, err)
|
||||||
|
|
||||||
err = g.SetDimensions(nil, ruler)
|
err = g.SetDimensions(nil, ruler)
|
||||||
assert.Nil(t, err)
|
assert.JSON(t, nil, err)
|
||||||
|
|
||||||
err = d2dagrelayout.Layout(ctx, g)
|
err = d2dagrelayout.Layout(ctx, g)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -252,8 +251,6 @@ func run(t *testing.T, tc testCase) {
|
||||||
got.Connections[i].LabelPosition = ""
|
got.Connections[i].LabelPosition = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
err = diff.Testdata(filepath.Join("..", "testdata", "d2exporter", t.Name()), got)
|
err = diff.TestdataJSON(filepath.Join("..", "testdata", "d2exporter", t.Name()), got)
|
||||||
if err != nil {
|
assert.Success(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ package d2format_test
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"oss.terrastruct.com/diff"
|
"oss.terrastruct.com/util-go/assert"
|
||||||
|
|
||||||
"oss.terrastruct.com/d2/d2ast"
|
"oss.terrastruct.com/d2/d2ast"
|
||||||
"oss.terrastruct.com/d2/d2format"
|
"oss.terrastruct.com/d2/d2format"
|
||||||
|
|
@ -42,7 +42,7 @@ func TestEscapeSingleQuoted(t *testing.T) {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
diff.AssertStringEq(t, tc.exp, d2format.Format(&d2ast.SingleQuotedString{
|
assert.String(t, tc.exp, d2format.Format(&d2ast.SingleQuotedString{
|
||||||
Value: tc.str,
|
Value: tc.str,
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
|
|
@ -104,7 +104,7 @@ func TestEscapeDoubleQuoted(t *testing.T) {
|
||||||
} else {
|
} else {
|
||||||
n = d2ast.FlatDoubleQuotedString(tc.str)
|
n = d2ast.FlatDoubleQuotedString(tc.str)
|
||||||
}
|
}
|
||||||
diff.AssertStringEq(t, tc.exp, d2format.Format(n))
|
assert.String(t, tc.exp, d2format.Format(n))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -203,7 +203,7 @@ func TestEscapeUnquoted(t *testing.T) {
|
||||||
n = d2ast.FlatUnquotedString(tc.str)
|
n = d2ast.FlatUnquotedString(tc.str)
|
||||||
}
|
}
|
||||||
|
|
||||||
diff.AssertStringEq(t, tc.exp, d2format.Format(n))
|
assert.String(t, tc.exp, d2format.Format(n))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -286,7 +286,7 @@ func TestEscapeBlockString(t *testing.T) {
|
||||||
Value: tc.value,
|
Value: tc.value,
|
||||||
}
|
}
|
||||||
|
|
||||||
diff.AssertStringEq(t, tc.exp, d2format.Format(n))
|
assert.String(t, tc.exp, d2format.Format(n))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"oss.terrastruct.com/diff"
|
"oss.terrastruct.com/util-go/assert"
|
||||||
|
|
||||||
"oss.terrastruct.com/d2/d2format"
|
"oss.terrastruct.com/d2/d2format"
|
||||||
"oss.terrastruct.com/d2/d2parser"
|
"oss.terrastruct.com/d2/d2parser"
|
||||||
|
|
@ -605,7 +605,7 @@ hi # Fraud is the homage that force pays to reason.
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
diff.AssertStringEq(t, tc.exp, d2format.Format(ast))
|
assert.String(t, tc.exp, d2format.Format(ast))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -621,6 +621,6 @@ func TestEdge(t *testing.T) {
|
||||||
t.Fatalf("expected one edge: %#v", mk.Edges)
|
t.Fatalf("expected one edge: %#v", mk.Edges)
|
||||||
}
|
}
|
||||||
|
|
||||||
diff.AssertStringEq(t, `x -> y`, d2format.Format(mk.Edges[0]))
|
assert.String(t, `x -> y`, d2format.Format(mk.Edges[0]))
|
||||||
diff.AssertStringEq(t, `[0]`, d2format.Format(mk.EdgeIndex))
|
assert.String(t, `[0]`, d2format.Format(mk.EdgeIndex))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"oss.terrastruct.com/util-go/go2"
|
||||||
|
|
||||||
"oss.terrastruct.com/d2/d2ast"
|
"oss.terrastruct.com/d2/d2ast"
|
||||||
"oss.terrastruct.com/d2/d2format"
|
"oss.terrastruct.com/d2/d2format"
|
||||||
"oss.terrastruct.com/d2/d2parser"
|
"oss.terrastruct.com/d2/d2parser"
|
||||||
|
|
@ -15,7 +17,6 @@ import (
|
||||||
"oss.terrastruct.com/d2/d2target"
|
"oss.terrastruct.com/d2/d2target"
|
||||||
"oss.terrastruct.com/d2/d2themes"
|
"oss.terrastruct.com/d2/d2themes"
|
||||||
"oss.terrastruct.com/d2/lib/geo"
|
"oss.terrastruct.com/d2/lib/geo"
|
||||||
"oss.terrastruct.com/d2/lib/go2"
|
|
||||||
"oss.terrastruct.com/d2/lib/textmeasure"
|
"oss.terrastruct.com/d2/lib/textmeasure"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"oss.terrastruct.com/diff"
|
"oss.terrastruct.com/util-go/assert"
|
||||||
|
|
||||||
"oss.terrastruct.com/d2/d2graph"
|
"oss.terrastruct.com/d2/d2graph"
|
||||||
"oss.terrastruct.com/d2/d2parser"
|
"oss.terrastruct.com/d2/d2parser"
|
||||||
|
|
@ -44,7 +44,7 @@ func TestKey(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
diff.AssertStringEq(t, tc.exp, strings.Join(d2graph.Key(k), "."))
|
assert.String(t, tc.exp, strings.Join(d2graph.Key(k), "."))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ package d2graph
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
||||||
"oss.terrastruct.com/d2/lib/go2"
|
"oss.terrastruct.com/util-go/go2"
|
||||||
)
|
)
|
||||||
|
|
||||||
type SerializedGraph struct {
|
type SerializedGraph struct {
|
||||||
|
|
|
||||||
|
|
@ -11,12 +11,13 @@ import (
|
||||||
"cdr.dev/slog"
|
"cdr.dev/slog"
|
||||||
v8 "rogchap.com/v8go"
|
v8 "rogchap.com/v8go"
|
||||||
|
|
||||||
"oss.terrastruct.com/xdefer"
|
"oss.terrastruct.com/util-go/xdefer"
|
||||||
|
|
||||||
|
"oss.terrastruct.com/util-go/go2"
|
||||||
|
|
||||||
"oss.terrastruct.com/d2/d2graph"
|
"oss.terrastruct.com/d2/d2graph"
|
||||||
"oss.terrastruct.com/d2/d2target"
|
"oss.terrastruct.com/d2/d2target"
|
||||||
"oss.terrastruct.com/d2/lib/geo"
|
"oss.terrastruct.com/d2/lib/geo"
|
||||||
"oss.terrastruct.com/d2/lib/go2"
|
|
||||||
"oss.terrastruct.com/d2/lib/label"
|
"oss.terrastruct.com/d2/lib/label"
|
||||||
"oss.terrastruct.com/d2/lib/log"
|
"oss.terrastruct.com/d2/lib/log"
|
||||||
"oss.terrastruct.com/d2/lib/shape"
|
"oss.terrastruct.com/d2/lib/shape"
|
||||||
|
|
|
||||||
|
|
@ -13,12 +13,13 @@ import (
|
||||||
|
|
||||||
"rogchap.com/v8go"
|
"rogchap.com/v8go"
|
||||||
|
|
||||||
"oss.terrastruct.com/xdefer"
|
"oss.terrastruct.com/util-go/xdefer"
|
||||||
|
|
||||||
|
"oss.terrastruct.com/util-go/go2"
|
||||||
|
|
||||||
"oss.terrastruct.com/d2/d2graph"
|
"oss.terrastruct.com/d2/d2graph"
|
||||||
"oss.terrastruct.com/d2/d2target"
|
"oss.terrastruct.com/d2/d2target"
|
||||||
"oss.terrastruct.com/d2/lib/geo"
|
"oss.terrastruct.com/d2/lib/geo"
|
||||||
"oss.terrastruct.com/d2/lib/go2"
|
|
||||||
"oss.terrastruct.com/d2/lib/label"
|
"oss.terrastruct.com/d2/lib/label"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,10 @@ import (
|
||||||
"math"
|
"math"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
|
"oss.terrastruct.com/util-go/go2"
|
||||||
|
|
||||||
"oss.terrastruct.com/d2/d2graph"
|
"oss.terrastruct.com/d2/d2graph"
|
||||||
"oss.terrastruct.com/d2/lib/geo"
|
"oss.terrastruct.com/d2/lib/geo"
|
||||||
"oss.terrastruct.com/d2/lib/go2"
|
|
||||||
"oss.terrastruct.com/d2/lib/label"
|
"oss.terrastruct.com/d2/lib/label"
|
||||||
"oss.terrastruct.com/d2/lib/shape"
|
"oss.terrastruct.com/d2/lib/shape"
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,11 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"unicode"
|
"unicode"
|
||||||
|
|
||||||
"oss.terrastruct.com/xdefer"
|
"oss.terrastruct.com/util-go/xdefer"
|
||||||
|
|
||||||
"oss.terrastruct.com/xrand"
|
"oss.terrastruct.com/util-go/xrand"
|
||||||
|
|
||||||
|
"oss.terrastruct.com/util-go/go2"
|
||||||
|
|
||||||
"oss.terrastruct.com/d2/d2ast"
|
"oss.terrastruct.com/d2/d2ast"
|
||||||
"oss.terrastruct.com/d2/d2compiler"
|
"oss.terrastruct.com/d2/d2compiler"
|
||||||
|
|
@ -17,7 +19,6 @@ import (
|
||||||
"oss.terrastruct.com/d2/d2graph"
|
"oss.terrastruct.com/d2/d2graph"
|
||||||
"oss.terrastruct.com/d2/d2parser"
|
"oss.terrastruct.com/d2/d2parser"
|
||||||
"oss.terrastruct.com/d2/d2target"
|
"oss.terrastruct.com/d2/d2target"
|
||||||
"oss.terrastruct.com/d2/lib/go2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func Create(g *d2graph.Graph, key string) (_ *d2graph.Graph, newKey string, err error) {
|
func Create(g *d2graph.Graph, key string) (_ *d2graph.Graph, newKey string, err error) {
|
||||||
|
|
|
||||||
|
|
@ -7,18 +7,16 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"oss.terrastruct.com/util-go/assert"
|
||||||
|
"oss.terrastruct.com/util-go/diff"
|
||||||
"oss.terrastruct.com/xjson"
|
"oss.terrastruct.com/util-go/go2"
|
||||||
|
"oss.terrastruct.com/util-go/xjson"
|
||||||
"oss.terrastruct.com/diff"
|
|
||||||
|
|
||||||
"oss.terrastruct.com/d2/d2compiler"
|
"oss.terrastruct.com/d2/d2compiler"
|
||||||
"oss.terrastruct.com/d2/d2format"
|
"oss.terrastruct.com/d2/d2format"
|
||||||
"oss.terrastruct.com/d2/d2graph"
|
"oss.terrastruct.com/d2/d2graph"
|
||||||
"oss.terrastruct.com/d2/d2oracle"
|
"oss.terrastruct.com/d2/d2oracle"
|
||||||
"oss.terrastruct.com/d2/d2target"
|
"oss.terrastruct.com/d2/d2target"
|
||||||
"oss.terrastruct.com/d2/lib/go2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO: make assertions less specific
|
// TODO: make assertions less specific
|
||||||
|
|
@ -966,10 +964,10 @@ z: {
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
assertions: func(t *testing.T, g *d2graph.Graph) {
|
assertions: func(t *testing.T, g *d2graph.Graph) {
|
||||||
assert.Equal(t, 3, len(g.Objects))
|
assert.JSON(t, 3, len(g.Objects))
|
||||||
assert.Equal(t, 1, len(g.Edges))
|
assert.JSON(t, 1, len(g.Edges))
|
||||||
assert.Equal(t, "q", g.Edges[0].Src.ID)
|
assert.JSON(t, "q", g.Edges[0].Src.ID)
|
||||||
assert.Equal(t, "0.4", g.Edges[0].Attributes.Style.Opacity.Value)
|
assert.JSON(t, "0.4", g.Edges[0].Attributes.Style.Opacity.Value)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -1617,8 +1615,8 @@ func TestMove(t *testing.T) {
|
||||||
exp: `b
|
exp: `b
|
||||||
`,
|
`,
|
||||||
assertions: func(t *testing.T, g *d2graph.Graph) {
|
assertions: func(t *testing.T, g *d2graph.Graph) {
|
||||||
assert.Equal(t, len(g.Objects), 1)
|
assert.JSON(t, len(g.Objects), 1)
|
||||||
assert.Equal(t, g.Objects[0].ID, "b")
|
assert.JSON(t, g.Objects[0].ID, "b")
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -1636,8 +1634,8 @@ func TestMove(t *testing.T) {
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
assertions: func(t *testing.T, g *d2graph.Graph) {
|
assertions: func(t *testing.T, g *d2graph.Graph) {
|
||||||
assert.Equal(t, len(g.Objects), 2)
|
assert.JSON(t, len(g.Objects), 2)
|
||||||
assert.Equal(t, g.Objects[1].ID, "c")
|
assert.JSON(t, g.Objects[1].ID, "c")
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -1692,9 +1690,9 @@ c
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
assertions: func(t *testing.T, g *d2graph.Graph) {
|
assertions: func(t *testing.T, g *d2graph.Graph) {
|
||||||
assert.Equal(t, len(g.Objects), 3)
|
assert.JSON(t, len(g.Objects), 3)
|
||||||
assert.Equal(t, "a", g.Objects[0].ID)
|
assert.JSON(t, "a", g.Objects[0].ID)
|
||||||
assert.Equal(t, 2, len(g.Objects[0].Children))
|
assert.JSON(t, 2, len(g.Objects[0].Children))
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -1733,9 +1731,9 @@ c
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
assertions: func(t *testing.T, g *d2graph.Graph) {
|
assertions: func(t *testing.T, g *d2graph.Graph) {
|
||||||
assert.Equal(t, len(g.Objects), 2)
|
assert.JSON(t, len(g.Objects), 2)
|
||||||
assert.Equal(t, "a", g.Objects[0].ID)
|
assert.JSON(t, "a", g.Objects[0].ID)
|
||||||
assert.Equal(t, 1, len(g.Objects[0].Children))
|
assert.JSON(t, 1, len(g.Objects[0].Children))
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -1752,9 +1750,9 @@ c
|
||||||
b
|
b
|
||||||
`,
|
`,
|
||||||
assertions: func(t *testing.T, g *d2graph.Graph) {
|
assertions: func(t *testing.T, g *d2graph.Graph) {
|
||||||
assert.Equal(t, len(g.Objects), 2)
|
assert.JSON(t, len(g.Objects), 2)
|
||||||
assert.Equal(t, "a", g.Objects[0].ID)
|
assert.JSON(t, "a", g.Objects[0].ID)
|
||||||
assert.Equal(t, 0, len(g.Objects[0].Children))
|
assert.JSON(t, 0, len(g.Objects[0].Children))
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -1863,11 +1861,11 @@ c: {
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
assertions: func(t *testing.T, g *d2graph.Graph) {
|
assertions: func(t *testing.T, g *d2graph.Graph) {
|
||||||
assert.Equal(t, len(g.Objects), 3)
|
assert.JSON(t, len(g.Objects), 3)
|
||||||
assert.Equal(t, "a", g.Objects[0].ID)
|
assert.JSON(t, "a", g.Objects[0].ID)
|
||||||
assert.Equal(t, 0, len(g.Objects[0].Children))
|
assert.JSON(t, 0, len(g.Objects[0].Children))
|
||||||
assert.Equal(t, "c", g.Objects[1].ID)
|
assert.JSON(t, "c", g.Objects[1].ID)
|
||||||
assert.Equal(t, 1, len(g.Objects[1].Children))
|
assert.JSON(t, 1, len(g.Objects[1].Children))
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -1929,7 +1927,7 @@ a: {
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
assertions: func(t *testing.T, g *d2graph.Graph) {
|
assertions: func(t *testing.T, g *d2graph.Graph) {
|
||||||
assert.Equal(t, len(g.Objects), 3)
|
assert.JSON(t, len(g.Objects), 3)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -1986,7 +1984,7 @@ c: {
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
assertions: func(t *testing.T, g *d2graph.Graph) {
|
assertions: func(t *testing.T, g *d2graph.Graph) {
|
||||||
assert.Equal(t, len(g.Objects), 3)
|
assert.JSON(t, len(g.Objects), 3)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -2004,7 +2002,7 @@ d: {
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
assertions: func(t *testing.T, g *d2graph.Graph) {
|
assertions: func(t *testing.T, g *d2graph.Graph) {
|
||||||
assert.Equal(t, len(g.Objects), 4)
|
assert.JSON(t, len(g.Objects), 4)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -2023,7 +2021,7 @@ c: {
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
assertions: func(t *testing.T, g *d2graph.Graph) {
|
assertions: func(t *testing.T, g *d2graph.Graph) {
|
||||||
assert.Equal(t, len(g.Objects), 4)
|
assert.JSON(t, len(g.Objects), 4)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -4422,10 +4420,8 @@ func (tc editTest) run(t *testing.T) {
|
||||||
Err: fmt.Sprintf("%#v", err),
|
Err: fmt.Sprintf("%#v", err),
|
||||||
}
|
}
|
||||||
|
|
||||||
err = diff.Testdata(filepath.Join("..", "testdata", "d2oracle", t.Name()), got)
|
err = diff.TestdataJSON(filepath.Join("..", "testdata", "d2oracle", t.Name()), got)
|
||||||
if err != nil {
|
assert.Success(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMoveIDDeltas(t *testing.T) {
|
func TestMoveIDDeltas(t *testing.T) {
|
||||||
|
|
@ -4635,7 +4631,7 @@ x.a -> x.b
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ds, err := diff.Strings(tc.exp, xjson.MarshalIndent(deltas))
|
ds, err := diff.Strings(tc.exp, string(xjson.Marshal(deltas)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
@ -4825,7 +4821,7 @@ x.y.z.w.e.p.l -> x.y.z.1.2.3.4
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ds, err := diff.Strings(tc.exp, xjson.MarshalIndent(deltas))
|
ds, err := diff.Strings(tc.exp, string(xjson.Marshal(deltas)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
@ -4977,7 +4973,7 @@ x.y.z.w.e.p.l -> x.y.z.1.2.3.4
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ds, err := diff.Strings(tc.exp, xjson.MarshalIndent(deltas))
|
ds, err := diff.Strings(tc.exp, string(xjson.Marshal(deltas)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,9 @@ import (
|
||||||
"unicode"
|
"unicode"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
|
"oss.terrastruct.com/util-go/go2"
|
||||||
|
|
||||||
"oss.terrastruct.com/d2/d2ast"
|
"oss.terrastruct.com/d2/d2ast"
|
||||||
"oss.terrastruct.com/d2/lib/go2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type ParseOptions struct {
|
type ParseOptions struct {
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,8 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"oss.terrastruct.com/diff"
|
"oss.terrastruct.com/util-go/assert"
|
||||||
|
"oss.terrastruct.com/util-go/diff"
|
||||||
|
|
||||||
"oss.terrastruct.com/d2/d2ast"
|
"oss.terrastruct.com/d2/d2ast"
|
||||||
"oss.terrastruct.com/d2/d2parser"
|
"oss.terrastruct.com/d2/d2parser"
|
||||||
|
|
@ -382,10 +383,8 @@ q.(x -> y).z: (rawr)
|
||||||
Err: err,
|
Err: err,
|
||||||
}
|
}
|
||||||
|
|
||||||
err = diff.Testdata(filepath.Join("..", "testdata", "d2parser", t.Name()), got)
|
err = diff.TestdataJSON(filepath.Join("..", "testdata", "d2parser", t.Name()), got)
|
||||||
if err != nil {
|
assert.Success(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ import (
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"oss.terrastruct.com/xdefer"
|
"oss.terrastruct.com/util-go/xdefer"
|
||||||
|
|
||||||
"oss.terrastruct.com/d2/d2graph"
|
"oss.terrastruct.com/d2/d2graph"
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,9 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
|
||||||
|
"oss.terrastruct.com/util-go/xexec"
|
||||||
|
|
||||||
"oss.terrastruct.com/d2/d2graph"
|
"oss.terrastruct.com/d2/d2graph"
|
||||||
"oss.terrastruct.com/d2/lib/xexec"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// plugins contains the bundled d2 plugins.
|
// plugins contains the bundled d2 plugins.
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,9 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
|
"oss.terrastruct.com/util-go/xmain"
|
||||||
|
|
||||||
"oss.terrastruct.com/d2/d2graph"
|
"oss.terrastruct.com/d2/d2graph"
|
||||||
"oss.terrastruct.com/d2/lib/xmain"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Serve returns a xmain.RunFunc that will invoke the plugin p as necessary to service the
|
// Serve returns a xmain.RunFunc that will invoke the plugin p as necessary to service the
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ import (
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"oss.terrastruct.com/xdefer"
|
"oss.terrastruct.com/util-go/xdefer"
|
||||||
v8 "rogchap.com/v8go"
|
v8 "rogchap.com/v8go"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,12 +20,13 @@ import (
|
||||||
"github.com/alecthomas/chroma/lexers"
|
"github.com/alecthomas/chroma/lexers"
|
||||||
"github.com/alecthomas/chroma/styles"
|
"github.com/alecthomas/chroma/styles"
|
||||||
|
|
||||||
|
"oss.terrastruct.com/util-go/go2"
|
||||||
|
|
||||||
"oss.terrastruct.com/d2/d2renderers/d2fonts"
|
"oss.terrastruct.com/d2/d2renderers/d2fonts"
|
||||||
"oss.terrastruct.com/d2/d2renderers/d2latex"
|
"oss.terrastruct.com/d2/d2renderers/d2latex"
|
||||||
"oss.terrastruct.com/d2/d2target"
|
"oss.terrastruct.com/d2/d2target"
|
||||||
"oss.terrastruct.com/d2/lib/color"
|
"oss.terrastruct.com/d2/lib/color"
|
||||||
"oss.terrastruct.com/d2/lib/geo"
|
"oss.terrastruct.com/d2/lib/geo"
|
||||||
"oss.terrastruct.com/d2/lib/go2"
|
|
||||||
"oss.terrastruct.com/d2/lib/label"
|
"oss.terrastruct.com/d2/lib/label"
|
||||||
"oss.terrastruct.com/d2/lib/shape"
|
"oss.terrastruct.com/d2/lib/shape"
|
||||||
"oss.terrastruct.com/d2/lib/textmeasure"
|
"oss.terrastruct.com/d2/lib/textmeasure"
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,10 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"oss.terrastruct.com/util-go/go2"
|
||||||
|
|
||||||
"oss.terrastruct.com/d2/d2themes"
|
"oss.terrastruct.com/d2/d2themes"
|
||||||
"oss.terrastruct.com/d2/lib/geo"
|
"oss.terrastruct.com/d2/lib/geo"
|
||||||
"oss.terrastruct.com/d2/lib/go2"
|
|
||||||
"oss.terrastruct.com/d2/lib/label"
|
"oss.terrastruct.com/d2/lib/label"
|
||||||
"oss.terrastruct.com/d2/lib/shape"
|
"oss.terrastruct.com/d2/lib/shape"
|
||||||
)
|
)
|
||||||
|
|
|
||||||
5
fmt.go
5
fmt.go
|
|
@ -4,11 +4,12 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"oss.terrastruct.com/xdefer"
|
"oss.terrastruct.com/util-go/xdefer"
|
||||||
|
|
||||||
|
"oss.terrastruct.com/util-go/xmain"
|
||||||
|
|
||||||
"oss.terrastruct.com/d2/d2format"
|
"oss.terrastruct.com/d2/d2format"
|
||||||
"oss.terrastruct.com/d2/d2parser"
|
"oss.terrastruct.com/d2/d2parser"
|
||||||
"oss.terrastruct.com/d2/lib/xmain"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func fmtCmd(ctx context.Context, ms *xmain.State) (err error) {
|
func fmtCmd(ctx context.Context, ms *xmain.State) (err error) {
|
||||||
|
|
|
||||||
15
go.mod
generated
15
go.mod
generated
|
|
@ -10,27 +10,17 @@ require (
|
||||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
|
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0
|
github.com/lucasb-eyer/go-colorful v1.2.0
|
||||||
github.com/mazznoer/csscolorparser v0.1.3
|
github.com/mazznoer/csscolorparser v0.1.3
|
||||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8
|
|
||||||
github.com/playwright-community/playwright-go v0.2000.1
|
github.com/playwright-community/playwright-go v0.2000.1
|
||||||
github.com/spf13/pflag v1.0.5
|
github.com/spf13/pflag v1.0.5
|
||||||
github.com/stretchr/testify v1.8.1
|
github.com/stretchr/testify v1.8.1
|
||||||
github.com/yuin/goldmark v1.5.3
|
github.com/yuin/goldmark v1.5.3
|
||||||
go.uber.org/multierr v1.8.0
|
go.uber.org/multierr v1.8.0
|
||||||
golang.org/x/exp v0.0.0-20221126150942-6ab00d035af9
|
|
||||||
golang.org/x/image v0.1.0
|
golang.org/x/image v0.1.0
|
||||||
golang.org/x/net v0.2.0
|
golang.org/x/net v0.2.0
|
||||||
golang.org/x/text v0.4.0
|
|
||||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2
|
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2
|
||||||
gonum.org/v1/plot v0.12.0
|
gonum.org/v1/plot v0.12.0
|
||||||
nhooyr.io/websocket v1.8.7
|
nhooyr.io/websocket v1.8.7
|
||||||
oss.terrastruct.com/cmdlog v0.0.0-20221201100934-012c01b3431c
|
oss.terrastruct.com/util-go v0.0.0-20221201191904-5edc89ce397b
|
||||||
oss.terrastruct.com/diff v1.0.2-0.20221116222035-8bf4dd3ab541
|
|
||||||
oss.terrastruct.com/util-go v0.0.0-20221201180807-58edfec77805
|
|
||||||
oss.terrastruct.com/xcontext v0.0.0-20221018000442-50fdafb12f4f
|
|
||||||
oss.terrastruct.com/xdefer v0.0.0-20221017222355-6f3b6e4d1557
|
|
||||||
oss.terrastruct.com/xjson v0.0.0-20221018000420-4986731c4c4a
|
|
||||||
oss.terrastruct.com/xos v0.0.0-20221130233107-5fb84d57c9e3
|
|
||||||
oss.terrastruct.com/xrand v0.0.0-20221020211818-4ac08e618333
|
|
||||||
rogchap.com/v8go v0.7.1-0.20221102201510-1f00b5007d95
|
rogchap.com/v8go v0.7.1-0.20221102201510-1f00b5007d95
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -51,14 +41,17 @@ require (
|
||||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.16 // indirect
|
github.com/mattn/go-isatty v0.0.16 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
|
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/rogpeppe/go-internal v1.8.0 // indirect
|
github.com/rogpeppe/go-internal v1.8.0 // indirect
|
||||||
github.com/ugorji/go/codec v1.2.6 // indirect
|
github.com/ugorji/go/codec v1.2.6 // indirect
|
||||||
go.opencensus.io v0.24.0 // indirect
|
go.opencensus.io v0.24.0 // indirect
|
||||||
go.uber.org/atomic v1.7.0 // indirect
|
go.uber.org/atomic v1.7.0 // indirect
|
||||||
golang.org/x/crypto v0.3.0 // indirect
|
golang.org/x/crypto v0.3.0 // indirect
|
||||||
|
golang.org/x/exp v0.0.0-20221126150942-6ab00d035af9 // indirect
|
||||||
golang.org/x/sys v0.2.0 // indirect
|
golang.org/x/sys v0.2.0 // indirect
|
||||||
golang.org/x/term v0.2.0 // indirect
|
golang.org/x/term v0.2.0 // indirect
|
||||||
|
golang.org/x/text v0.4.0 // indirect
|
||||||
google.golang.org/genproto v0.0.0-20220822174746-9e6da59bd2fc // indirect
|
google.golang.org/genproto v0.0.0-20220822174746-9e6da59bd2fc // indirect
|
||||||
google.golang.org/protobuf v1.28.1 // indirect
|
google.golang.org/protobuf v1.28.1 // indirect
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||||
|
|
|
||||||
18
go.sum
generated
18
go.sum
generated
|
|
@ -798,22 +798,8 @@ honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9
|
||||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||||
nhooyr.io/websocket v1.8.7 h1:usjR2uOr/zjjkVMy0lW+PPohFok7PCow5sDjLgX4P4g=
|
nhooyr.io/websocket v1.8.7 h1:usjR2uOr/zjjkVMy0lW+PPohFok7PCow5sDjLgX4P4g=
|
||||||
nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=
|
nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=
|
||||||
oss.terrastruct.com/cmdlog v0.0.0-20221201100934-012c01b3431c h1:C1DDLzj2NrVi1YJpbZluEYZ2MkpJGppZYfSQ+F87TT0=
|
oss.terrastruct.com/util-go v0.0.0-20221201191904-5edc89ce397b h1:o8+5KfZpQyaw7uKcPIdc9HOqVjVDEdsPZpdRV1k0rmc=
|
||||||
oss.terrastruct.com/cmdlog v0.0.0-20221201100934-012c01b3431c/go.mod h1:C8u/lYTvQWc1xC7rHpgFfpScfQC4NMeGGMmlKVZZUXM=
|
oss.terrastruct.com/util-go v0.0.0-20221201191904-5edc89ce397b/go.mod h1:Fwy72FDIOOM4K8F96ScXkxHHppR1CPfUyo9+x9c1PBU=
|
||||||
oss.terrastruct.com/diff v1.0.2-0.20221116222035-8bf4dd3ab541 h1:I9B1O1IJ6spivIQxbFRZmbhAwVeLwrcQRR1JbYUOvrI=
|
|
||||||
oss.terrastruct.com/diff v1.0.2-0.20221116222035-8bf4dd3ab541/go.mod h1:ags2QDy/T6jr69hT6bpmAmhr2H98n9o8Atf3QlUJPiU=
|
|
||||||
oss.terrastruct.com/util-go v0.0.0-20221201180807-58edfec77805 h1:DaMNXVEa+ZapP2U2gBVehedPdFsT7GGsGM+d7erCGAY=
|
|
||||||
oss.terrastruct.com/util-go v0.0.0-20221201180807-58edfec77805/go.mod h1:AN5T0bJ89/Q6ZebXIdPGpwAqVhK9PkDpgygWJaT2JzQ=
|
|
||||||
oss.terrastruct.com/xcontext v0.0.0-20221018000442-50fdafb12f4f h1:7voRCwKM7TZkTo9u7hj+uV/zXoVB8czWrTq6MVIh3dg=
|
|
||||||
oss.terrastruct.com/xcontext v0.0.0-20221018000442-50fdafb12f4f/go.mod h1:Y0coTLsWwX0q3a+/Ndq797t+vWyxm42T49Ik3bzaDKY=
|
|
||||||
oss.terrastruct.com/xdefer v0.0.0-20221017222355-6f3b6e4d1557 h1:rPbhJbN1q7B4tnppSPoAMwq0t6Pk5SrQDQ5S6uoNNHg=
|
|
||||||
oss.terrastruct.com/xdefer v0.0.0-20221017222355-6f3b6e4d1557/go.mod h1:plvfydF5METAlsbpeuSz44jckaOwrCWX3M0kTLoCA4I=
|
|
||||||
oss.terrastruct.com/xjson v0.0.0-20221018000420-4986731c4c4a h1:AAcupsjBwpbcyLASX0ppDlxbfHWb5Neq5gWdGpLfaSA=
|
|
||||||
oss.terrastruct.com/xjson v0.0.0-20221018000420-4986731c4c4a/go.mod h1:XJ71qiTzk/dbTWuYbuLJuRpBdKFN06Sk5FdFpq2TNmE=
|
|
||||||
oss.terrastruct.com/xos v0.0.0-20221130233107-5fb84d57c9e3 h1:bchGZ5WryJNqr/yZ00rTcgZh1AComRFwKKBWOIxzJZE=
|
|
||||||
oss.terrastruct.com/xos v0.0.0-20221130233107-5fb84d57c9e3/go.mod h1:lUSNCN0HA3pWHOMXT6gRNJtjg1U5t0TEEwAzPyV6enA=
|
|
||||||
oss.terrastruct.com/xrand v0.0.0-20221020211818-4ac08e618333 h1:7EdxwXM75Id1VIN71QbE8bLzZRMs0qD7olnDw5gbI7w=
|
|
||||||
oss.terrastruct.com/xrand v0.0.0-20221020211818-4ac08e618333/go.mod h1:O7TAoBmlQhoi46RdgVikDcoLRb/vLflhkXCAd+nO4SM=
|
|
||||||
rogchap.com/v8go v0.7.1-0.20221102201510-1f00b5007d95 h1:r89YHVIWeQj/A3Nu6462eqARUECJlJkLRk36pfML1xA=
|
rogchap.com/v8go v0.7.1-0.20221102201510-1f00b5007d95 h1:r89YHVIWeQj/A3Nu6462eqARUECJlJkLRk36pfML1xA=
|
||||||
rogchap.com/v8go v0.7.1-0.20221102201510-1f00b5007d95/go.mod h1:MxgP3pL2MW4dpme/72QRs8sgNMmM0pRc8DPhcuLWPAs=
|
rogchap.com/v8go v0.7.1-0.20221102201510-1f00b5007d95/go.mod h1:MxgP3pL2MW4dpme/72QRs8sgNMmM0pRc8DPhcuLWPAs=
|
||||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||||
|
|
|
||||||
3
help.go
3
help.go
|
|
@ -9,8 +9,9 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"oss.terrastruct.com/util-go/xmain"
|
||||||
|
|
||||||
"oss.terrastruct.com/d2/d2plugin"
|
"oss.terrastruct.com/d2/d2plugin"
|
||||||
"oss.terrastruct.com/d2/lib/xmain"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func help(ms *xmain.State) {
|
func help(ms *xmain.State) {
|
||||||
|
|
|
||||||
|
|
@ -1,60 +0,0 @@
|
||||||
// Package go2 contains general utility helpers that should've been in Go. Maybe they'll be in Go 2.0.
|
|
||||||
package go2
|
|
||||||
|
|
||||||
import (
|
|
||||||
"hash/fnv"
|
|
||||||
"math"
|
|
||||||
|
|
||||||
"golang.org/x/exp/constraints"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Pointer[T any](v T) *T {
|
|
||||||
return &v
|
|
||||||
}
|
|
||||||
|
|
||||||
func Min[T constraints.Ordered](a, b T) T {
|
|
||||||
if a < b {
|
|
||||||
return a
|
|
||||||
}
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
func Max[T constraints.Ordered](a, b T) T {
|
|
||||||
if a > b {
|
|
||||||
return a
|
|
||||||
}
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
func StringToIntHash(s string) int {
|
|
||||||
h := fnv.New32a()
|
|
||||||
h.Write([]byte(s))
|
|
||||||
return int(h.Sum32())
|
|
||||||
}
|
|
||||||
|
|
||||||
func Contains[T comparable](els []T, el T) bool {
|
|
||||||
for _, el2 := range els {
|
|
||||||
if el2 == el {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func Filter[T any](els []T, fn func(T) bool) []T {
|
|
||||||
out := []T{}
|
|
||||||
for _, el := range els {
|
|
||||||
if fn(el) {
|
|
||||||
out = append(out, el)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
func IntMax(x, y int) int {
|
|
||||||
return int(math.Max(float64(x), float64(y)))
|
|
||||||
}
|
|
||||||
|
|
||||||
func IntMin(x, y int) int {
|
|
||||||
return int(math.Min(float64(x), float64(y)))
|
|
||||||
}
|
|
||||||
|
|
@ -17,9 +17,9 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"golang.org/x/xerrors"
|
"golang.org/x/xerrors"
|
||||||
"oss.terrastruct.com/xdefer"
|
"oss.terrastruct.com/util-go/xdefer"
|
||||||
|
|
||||||
"oss.terrastruct.com/d2/lib/xmain"
|
"oss.terrastruct.com/util-go/xmain"
|
||||||
)
|
)
|
||||||
|
|
||||||
const maxImageSize int64 = 1 << 25 // 33_554_432
|
const maxImageSize int64 = 1 << 25 // 33_554_432
|
||||||
|
|
|
||||||
|
|
@ -12,10 +12,10 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"oss.terrastruct.com/cmdlog"
|
"oss.terrastruct.com/util-go/cmdlog"
|
||||||
"oss.terrastruct.com/xos"
|
"oss.terrastruct.com/util-go/xos"
|
||||||
|
|
||||||
"oss.terrastruct.com/d2/lib/xmain"
|
"oss.terrastruct.com/util-go/xmain"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed test_png.png
|
//go:embed test_png.png
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ import (
|
||||||
|
|
||||||
"github.com/playwright-community/playwright-go"
|
"github.com/playwright-community/playwright-go"
|
||||||
|
|
||||||
"oss.terrastruct.com/d2/lib/xmain"
|
"oss.terrastruct.com/util-go/xmain"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Playwright struct {
|
type Playwright struct {
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,9 @@ import (
|
||||||
goldmarkHtml "github.com/yuin/goldmark/renderer/html"
|
goldmarkHtml "github.com/yuin/goldmark/renderer/html"
|
||||||
"golang.org/x/net/html"
|
"golang.org/x/net/html"
|
||||||
|
|
||||||
|
"oss.terrastruct.com/util-go/go2"
|
||||||
|
|
||||||
"oss.terrastruct.com/d2/d2renderers/d2fonts"
|
"oss.terrastruct.com/d2/d2renderers/d2fonts"
|
||||||
"oss.terrastruct.com/d2/lib/go2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var markdownRenderer goldmark.Markdown
|
var markdownRenderer goldmark.Markdown
|
||||||
|
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
package xbrowser
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"os/exec"
|
|
||||||
|
|
||||||
"github.com/pkg/browser"
|
|
||||||
|
|
||||||
"oss.terrastruct.com/xos"
|
|
||||||
)
|
|
||||||
|
|
||||||
func OpenURL(ctx context.Context, env *xos.Env, url string) error {
|
|
||||||
browserEnv := env.Getenv("BROWSER")
|
|
||||||
if browserEnv != "" {
|
|
||||||
browserSh := fmt.Sprintf("%s '$1'", browserEnv)
|
|
||||||
cmd := exec.CommandContext(ctx, "sh", "-c", browserSh, "--", url)
|
|
||||||
out, err := cmd.CombinedOutput()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to run %v (out: %q): %w", cmd.Args, out, err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return browser.OpenURL(url)
|
|
||||||
}
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
package xexec
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"io/fs"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// findExecutable is from package exec
|
|
||||||
func findExecutable(file string) error {
|
|
||||||
d, err := os.Stat(file)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if m := d.Mode(); !m.IsDir() && m&0111 != 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return fs.ErrPermission
|
|
||||||
}
|
|
||||||
|
|
||||||
// SearchPath searches for all executables that have prefix in their names in
|
|
||||||
// the directories named by the PATH environment variable.
|
|
||||||
func SearchPath(prefix string) ([]string, error) {
|
|
||||||
var matches []string
|
|
||||||
envPath := os.Getenv("PATH")
|
|
||||||
dirSet := make(map[string]struct{})
|
|
||||||
for _, dir := range filepath.SplitList(envPath) {
|
|
||||||
if dir == "" {
|
|
||||||
// From exec package:
|
|
||||||
// Unix shell semantics: path element "" means "."
|
|
||||||
dir = "."
|
|
||||||
}
|
|
||||||
if _, ok := dirSet[dir]; ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
dirSet[dir] = struct{}{}
|
|
||||||
files, err := os.ReadDir(dir)
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, fs.ErrNotExist) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for _, f := range files {
|
|
||||||
if strings.HasPrefix(f.Name(), prefix) {
|
|
||||||
match := filepath.Join(dir, f.Name())
|
|
||||||
if err := findExecutable(match); err == nil {
|
|
||||||
matches = append(matches, match)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
return matches, nil
|
|
||||||
}
|
|
||||||
165
lib/xhttp/err.go
165
lib/xhttp/err.go
|
|
@ -1,165 +0,0 @@
|
||||||
package xhttp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"oss.terrastruct.com/cmdlog"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Error represents an HTTP error.
|
|
||||||
// It's exported only for comparison in tests.
|
|
||||||
type Error struct {
|
|
||||||
Code int
|
|
||||||
Resp interface{}
|
|
||||||
Err error
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ interface {
|
|
||||||
Is(error) bool
|
|
||||||
Unwrap() error
|
|
||||||
} = Error{}
|
|
||||||
|
|
||||||
// Errorf creates a new error with code, resp, msg and v.
|
|
||||||
//
|
|
||||||
// When returned from an xhttp.HandlerFunc, it will be correctly logged
|
|
||||||
// and written to the connection. See xhttp.WrapHandlerFunc
|
|
||||||
func Errorf(code int, resp interface{}, msg string, v ...interface{}) error {
|
|
||||||
return errorWrap(code, resp, fmt.Errorf(msg, v...))
|
|
||||||
}
|
|
||||||
|
|
||||||
// ErrorWrap wraps err with the code and resp for xhttp.HandlerFunc.
|
|
||||||
//
|
|
||||||
// When returned from an xhttp.HandlerFunc, it will be correctly logged
|
|
||||||
// and written to the connection. See xhttp.WrapHandlerFunc
|
|
||||||
func ErrorWrap(code int, resp interface{}, err error) error {
|
|
||||||
return errorWrap(code, resp, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func errorWrap(code int, resp interface{}, err error) error {
|
|
||||||
if resp == nil {
|
|
||||||
resp = http.StatusText(code)
|
|
||||||
}
|
|
||||||
return Error{code, resp, err}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e Error) Unwrap() error {
|
|
||||||
return e.Err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e Error) Is(err error) bool {
|
|
||||||
e2, ok := err.(Error)
|
|
||||||
if !ok {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return e.Code == e2.Code && e.Resp == e2.Resp && errors.Is(e.Err, e2.Err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e Error) Error() string {
|
|
||||||
return fmt.Sprintf("http error with code %v and resp %#v: %v", e.Code, e.Resp, e.Err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandlerFunc is like http.HandlerFunc but returns an error.
|
|
||||||
// See Errorf and ErrorWrap.
|
|
||||||
type HandlerFunc func(w http.ResponseWriter, r *http.Request) error
|
|
||||||
|
|
||||||
type HandlerFuncAdapter struct {
|
|
||||||
Log *cmdlog.Logger
|
|
||||||
Func HandlerFunc
|
|
||||||
}
|
|
||||||
|
|
||||||
// ServeHTTP adapts xhttp.HandlerFunc into http.Handler for usage with standard
|
|
||||||
// HTTP routers like chi.
|
|
||||||
//
|
|
||||||
// It logs and writes any error from xhttp.HandlerFunc to the connection.
|
|
||||||
//
|
|
||||||
// If err was created with xhttp.Errorf or wrapped with xhttp.WrapError, then the error
|
|
||||||
// will be logged at the correct level for the status code and xhttp.JSON will be called
|
|
||||||
// with the code and resp.
|
|
||||||
//
|
|
||||||
// 400s are logged as warns and 500s as errors.
|
|
||||||
//
|
|
||||||
// If the error was not created with the xhttp helpers then a 500 will be written.
|
|
||||||
//
|
|
||||||
// If resp is nil, then resp is set to http.StatusText(code)
|
|
||||||
//
|
|
||||||
// If the code is not a 400 or a 500, then an error about about the unexpected error code
|
|
||||||
// will be logged and a 500 will be written. The original error will also be logged.
|
|
||||||
func (a HandlerFuncAdapter) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var h http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
err := a.Func(w, r)
|
|
||||||
if err != nil {
|
|
||||||
handleError(a.Log, w, err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
h.ServeHTTP(w, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleError(clog *cmdlog.Logger, w http.ResponseWriter, err error) {
|
|
||||||
var herr Error
|
|
||||||
ok := errors.As(err, &herr)
|
|
||||||
if !ok {
|
|
||||||
herr = ErrorWrap(http.StatusInternalServerError, nil, err).(Error)
|
|
||||||
}
|
|
||||||
|
|
||||||
var logger *log.Logger
|
|
||||||
switch {
|
|
||||||
case 400 <= herr.Code && herr.Code < 500:
|
|
||||||
logger = clog.Warn
|
|
||||||
case 500 <= herr.Code && herr.Code < 600:
|
|
||||||
logger = clog.Error
|
|
||||||
default:
|
|
||||||
logger = clog.Error
|
|
||||||
|
|
||||||
clog.Error.Printf("unexpected non error http status code %d with resp: %#v", herr.Code, herr.Resp)
|
|
||||||
|
|
||||||
herr.Code = http.StatusInternalServerError
|
|
||||||
herr.Resp = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if herr.Resp == nil {
|
|
||||||
herr.Resp = http.StatusText(herr.Code)
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Printf("error handling http request: %v", err)
|
|
||||||
|
|
||||||
ww, ok := w.(writtenResponseWriter)
|
|
||||||
if !ok {
|
|
||||||
clog.Warn.Printf("response writer does not implement Written, double write logs possible: %#v", w)
|
|
||||||
} else if ww.Written() {
|
|
||||||
// Avoid double writes if an error occurred while the response was
|
|
||||||
// being written.
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
JSON(clog, w, herr.Code, map[string]interface{}{
|
|
||||||
"error": herr.Resp,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
type writtenResponseWriter interface {
|
|
||||||
Written() bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func JSON(clog *cmdlog.Logger, w http.ResponseWriter, code int, v interface{}) {
|
|
||||||
if v == nil {
|
|
||||||
v = map[string]interface{}{
|
|
||||||
"status": http.StatusText(code),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
b, err := json.Marshal(v)
|
|
||||||
if err != nil {
|
|
||||||
clog.Error.Printf("json marshal error: %v", err)
|
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
|
||||||
w.WriteHeader(code)
|
|
||||||
_, _ = w.Write(b)
|
|
||||||
}
|
|
||||||
125
lib/xhttp/log.go
125
lib/xhttp/log.go
|
|
@ -1,125 +0,0 @@
|
||||||
package xhttp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"runtime/debug"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"golang.org/x/text/message"
|
|
||||||
|
|
||||||
"oss.terrastruct.com/cmdlog"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ResponseWriter interface {
|
|
||||||
http.ResponseWriter
|
|
||||||
http.Hijacker
|
|
||||||
http.Flusher
|
|
||||||
writtenResponseWriter
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ ResponseWriter = &responseWriter{}
|
|
||||||
|
|
||||||
type responseWriter struct {
|
|
||||||
rw http.ResponseWriter
|
|
||||||
|
|
||||||
written bool
|
|
||||||
status int
|
|
||||||
length int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rw *responseWriter) Header() http.Header {
|
|
||||||
return rw.rw.Header()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rw *responseWriter) WriteHeader(statusCode int) {
|
|
||||||
if !rw.written {
|
|
||||||
rw.written = true
|
|
||||||
rw.status = statusCode
|
|
||||||
}
|
|
||||||
rw.rw.WriteHeader(statusCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rw *responseWriter) Write(p []byte) (int, error) {
|
|
||||||
if !rw.written && len(p) > 0 {
|
|
||||||
rw.written = true
|
|
||||||
if rw.status == 0 {
|
|
||||||
rw.status = http.StatusOK
|
|
||||||
}
|
|
||||||
}
|
|
||||||
rw.length += len(p)
|
|
||||||
return rw.rw.Write(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rw *responseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
|
||||||
hj, ok := rw.rw.(http.Hijacker)
|
|
||||||
if !ok {
|
|
||||||
return nil, nil, fmt.Errorf("underlying response writer does not implement http.Hijacker: %T", rw.rw)
|
|
||||||
}
|
|
||||||
return hj.Hijack()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rw *responseWriter) Flush() {
|
|
||||||
f, ok := rw.rw.(http.Flusher)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
f.Flush()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rw *responseWriter) Written() bool {
|
|
||||||
return rw.written
|
|
||||||
}
|
|
||||||
|
|
||||||
func Log(clog *cmdlog.Logger, next http.Handler) http.Handler {
|
|
||||||
englishPrinter := message.NewPrinter(message.MatchLanguage("en"))
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
defer func() {
|
|
||||||
rec := recover()
|
|
||||||
if rec != nil {
|
|
||||||
clog.Error.Printf("caught panic: %#v\n%s", rec, debug.Stack())
|
|
||||||
JSON(clog, w, http.StatusInternalServerError, map[string]interface{}{
|
|
||||||
"error": http.StatusText(http.StatusInternalServerError),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
rw := &responseWriter{
|
|
||||||
rw: w,
|
|
||||||
}
|
|
||||||
|
|
||||||
start := time.Now()
|
|
||||||
next.ServeHTTP(rw, r)
|
|
||||||
dur := time.Since(start)
|
|
||||||
|
|
||||||
if !rw.Written() {
|
|
||||||
_, err := rw.Write(nil)
|
|
||||||
if errors.Is(err, http.ErrHijacked) {
|
|
||||||
clog.Success.Printf("%s %s %v: hijacked", r.Method, r.URL, dur)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
clog.Warn.Printf("%s %s %v: no response written", r.Method, r.URL, dur)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var statusLogger *log.Logger
|
|
||||||
switch {
|
|
||||||
case 100 <= rw.status && rw.status <= 299:
|
|
||||||
statusLogger = clog.Success
|
|
||||||
case 300 <= rw.status && rw.status <= 399:
|
|
||||||
statusLogger = clog.Info
|
|
||||||
case 400 <= rw.status && rw.status <= 499:
|
|
||||||
statusLogger = clog.Warn
|
|
||||||
case 500 <= rw.status && rw.status <= 599:
|
|
||||||
statusLogger = clog.Error
|
|
||||||
}
|
|
||||||
lengthStr := englishPrinter.Sprint(rw.length)
|
|
||||||
// TODO: make work with watch.go on hijack, not after
|
|
||||||
statusLogger.Printf("%s %s %d %sB %v", r.Method, r.URL, rw.status, lengthStr, dur)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
@ -1,44 +0,0 @@
|
||||||
// Package xhttp implements http helpers.
|
|
||||||
package xhttp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"log"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"oss.terrastruct.com/xcontext"
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewServer(log *log.Logger, h http.Handler) *http.Server {
|
|
||||||
return &http.Server{
|
|
||||||
MaxHeaderBytes: 1 << 18, // 262,144B
|
|
||||||
ReadTimeout: time.Minute,
|
|
||||||
WriteTimeout: time.Minute,
|
|
||||||
IdleTimeout: time.Hour,
|
|
||||||
ErrorLog: log,
|
|
||||||
Handler: http.MaxBytesHandler(h, 1<<20), // 1,048,576B
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Serve(ctx context.Context, shutdownTimeout time.Duration, s *http.Server, l net.Listener) error {
|
|
||||||
s.BaseContext = func(net.Listener) context.Context {
|
|
||||||
return ctx
|
|
||||||
}
|
|
||||||
|
|
||||||
done := make(chan error, 1)
|
|
||||||
go func() {
|
|
||||||
done <- s.Serve(l)
|
|
||||||
}()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case err := <-done:
|
|
||||||
return err
|
|
||||||
case <-ctx.Done():
|
|
||||||
ctx = xcontext.WithoutCancel(ctx)
|
|
||||||
ctx, cancel := context.WithTimeout(ctx, shutdownTimeout)
|
|
||||||
defer cancel()
|
|
||||||
return s.Shutdown(ctx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,45 +0,0 @@
|
||||||
// flag_helpers.go are private functions from pflag/flag.go
|
|
||||||
package xmain
|
|
||||||
|
|
||||||
import "strings"
|
|
||||||
|
|
||||||
func wrap(i, w int, s string) string {
|
|
||||||
if w == 0 {
|
|
||||||
return strings.Replace(s, "\n", "\n"+strings.Repeat(" ", i), -1)
|
|
||||||
}
|
|
||||||
wrap := w - i
|
|
||||||
var r, l string
|
|
||||||
if wrap < 24 {
|
|
||||||
i = 16
|
|
||||||
wrap = w - i
|
|
||||||
r += "\n" + strings.Repeat(" ", i)
|
|
||||||
}
|
|
||||||
if wrap < 24 {
|
|
||||||
return strings.Replace(s, "\n", r, -1)
|
|
||||||
}
|
|
||||||
slop := 5
|
|
||||||
wrap = wrap - slop
|
|
||||||
l, s = wrapN(wrap, slop, s)
|
|
||||||
r = r + strings.Replace(l, "\n", "\n"+strings.Repeat(" ", i), -1)
|
|
||||||
for s != "" {
|
|
||||||
var t string
|
|
||||||
t, s = wrapN(wrap, slop, s)
|
|
||||||
r = r + "\n" + strings.Repeat(" ", i) + strings.Replace(t, "\n", "\n"+strings.Repeat(" ", i), -1)
|
|
||||||
}
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
func wrapN(i, slop int, s string) (string, string) {
|
|
||||||
if i+slop > len(s) {
|
|
||||||
return s, ""
|
|
||||||
}
|
|
||||||
w := strings.LastIndexAny(s[:i], " \t\n")
|
|
||||||
if w <= 0 {
|
|
||||||
return s, ""
|
|
||||||
}
|
|
||||||
nlPos := strings.LastIndex(s[:i], "\n")
|
|
||||||
if nlPos > 0 && nlPos < w {
|
|
||||||
return s[:nlPos], s[nlPos+1:]
|
|
||||||
}
|
|
||||||
return s[:w], s[w+1:]
|
|
||||||
}
|
|
||||||
|
|
@ -1,173 +0,0 @@
|
||||||
package xmain
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/spf13/pflag"
|
|
||||||
"oss.terrastruct.com/cmdlog"
|
|
||||||
"oss.terrastruct.com/xos"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Opts struct {
|
|
||||||
Args []string
|
|
||||||
Flags *pflag.FlagSet
|
|
||||||
env *xos.Env
|
|
||||||
log *cmdlog.Logger
|
|
||||||
|
|
||||||
flagEnv map[string]string
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewOpts(env *xos.Env, log *cmdlog.Logger, args []string) *Opts {
|
|
||||||
flags := pflag.NewFlagSet("", pflag.ContinueOnError)
|
|
||||||
flags.SortFlags = false
|
|
||||||
flags.Usage = func() {}
|
|
||||||
flags.SetOutput(io.Discard)
|
|
||||||
return &Opts{
|
|
||||||
Args: args,
|
|
||||||
Flags: flags,
|
|
||||||
env: env,
|
|
||||||
log: log,
|
|
||||||
flagEnv: make(map[string]string),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mostly copy pasted pasted from pflag.FlagUsagesWrapped
|
|
||||||
// with modifications for env var
|
|
||||||
func (o *Opts) Defaults() string {
|
|
||||||
buf := new(bytes.Buffer)
|
|
||||||
|
|
||||||
var lines []string
|
|
||||||
|
|
||||||
maxlen := 0
|
|
||||||
maxEnvLen := 0
|
|
||||||
o.Flags.VisitAll(func(flag *pflag.Flag) {
|
|
||||||
if flag.Hidden {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
line := ""
|
|
||||||
if flag.Shorthand != "" && flag.ShorthandDeprecated == "" {
|
|
||||||
line = fmt.Sprintf(" -%s, --%s", flag.Shorthand, flag.Name)
|
|
||||||
} else {
|
|
||||||
line = fmt.Sprintf(" --%s", flag.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
varname, usage := pflag.UnquoteUsage(flag)
|
|
||||||
if varname != "" {
|
|
||||||
line += " " + varname
|
|
||||||
}
|
|
||||||
if flag.NoOptDefVal != "" {
|
|
||||||
switch flag.Value.Type() {
|
|
||||||
case "string":
|
|
||||||
line += fmt.Sprintf("[=\"%s\"]", flag.NoOptDefVal)
|
|
||||||
case "bool":
|
|
||||||
if flag.NoOptDefVal != "true" {
|
|
||||||
line += fmt.Sprintf("[=%s]", flag.NoOptDefVal)
|
|
||||||
}
|
|
||||||
case "count":
|
|
||||||
if flag.NoOptDefVal != "+1" {
|
|
||||||
line += fmt.Sprintf("[=%s]", flag.NoOptDefVal)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
line += fmt.Sprintf("[=%s]", flag.NoOptDefVal)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
line += "\x00"
|
|
||||||
|
|
||||||
if len(line) > maxlen {
|
|
||||||
maxlen = len(line)
|
|
||||||
}
|
|
||||||
|
|
||||||
if e, ok := o.flagEnv[flag.Name]; ok {
|
|
||||||
line += fmt.Sprintf("$%s", e)
|
|
||||||
}
|
|
||||||
|
|
||||||
line += "\x01"
|
|
||||||
|
|
||||||
if len(line) > maxEnvLen {
|
|
||||||
maxEnvLen = len(line)
|
|
||||||
}
|
|
||||||
|
|
||||||
line += usage
|
|
||||||
if flag.Value.Type() == "string" {
|
|
||||||
line += fmt.Sprintf(" (default %q)", flag.DefValue)
|
|
||||||
} else {
|
|
||||||
line += fmt.Sprintf(" (default %s)", flag.DefValue)
|
|
||||||
}
|
|
||||||
if len(flag.Deprecated) != 0 {
|
|
||||||
line += fmt.Sprintf(" (DEPRECATED: %s)", flag.Deprecated)
|
|
||||||
}
|
|
||||||
|
|
||||||
lines = append(lines, line)
|
|
||||||
})
|
|
||||||
|
|
||||||
for _, line := range lines {
|
|
||||||
sidx1 := strings.Index(line, "\x00")
|
|
||||||
sidx2 := strings.Index(line, "\x01")
|
|
||||||
spacing1 := strings.Repeat(" ", maxlen-sidx1)
|
|
||||||
spacing2 := strings.Repeat(" ", (maxEnvLen-maxlen)-sidx2+sidx1)
|
|
||||||
fmt.Fprintln(buf, line[:sidx1], spacing1, line[sidx1+1:sidx2], spacing2, wrap(maxEnvLen+3, 0, line[sidx2+1:]))
|
|
||||||
}
|
|
||||||
|
|
||||||
return buf.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *Opts) getEnv(flag, k string) string {
|
|
||||||
if k != "" {
|
|
||||||
o.flagEnv[flag] = k
|
|
||||||
return o.env.Getenv(k)
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *Opts) Int64(envKey, flag, shortFlag string, defaultVal int64, usage string) (*int64, error) {
|
|
||||||
if env := o.getEnv(flag, envKey); env != "" {
|
|
||||||
envVal, err := strconv.ParseInt(env, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return nil, UsageErrorf(`invalid environment variable %s. Expected int64. Found "%v".`, envKey, envVal)
|
|
||||||
}
|
|
||||||
defaultVal = envVal
|
|
||||||
}
|
|
||||||
|
|
||||||
return o.Flags.Int64P(flag, shortFlag, defaultVal, usage), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *Opts) String(envKey, flag, shortFlag string, defaultVal, usage string) *string {
|
|
||||||
if env := o.getEnv(flag, envKey); env != "" {
|
|
||||||
defaultVal = env
|
|
||||||
}
|
|
||||||
|
|
||||||
return o.Flags.StringP(flag, shortFlag, defaultVal, usage)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *Opts) Bool(envKey, flag, shortFlag string, defaultVal bool, usage string) (*bool, error) {
|
|
||||||
if env := o.getEnv(flag, envKey); env != "" {
|
|
||||||
if !boolyEnv(env) {
|
|
||||||
return nil, UsageErrorf(`invalid environment variable %s. Expected bool. Found "%s".`, envKey, env)
|
|
||||||
}
|
|
||||||
if truthyEnv(env) {
|
|
||||||
defaultVal = true
|
|
||||||
} else {
|
|
||||||
defaultVal = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return o.Flags.BoolP(flag, shortFlag, defaultVal, usage), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func boolyEnv(s string) bool {
|
|
||||||
return falseyEnv(s) || truthyEnv(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
func falseyEnv(s string) bool {
|
|
||||||
return s == "0" || s == "false"
|
|
||||||
}
|
|
||||||
|
|
||||||
func truthyEnv(s string) bool {
|
|
||||||
return s == "1" || s == "true"
|
|
||||||
}
|
|
||||||
|
|
@ -1,180 +0,0 @@
|
||||||
// Package xmain provides a standard stub for the main of a command handling logging,
|
|
||||||
// flags, signals and shutdown.
|
|
||||||
package xmain
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"os/signal"
|
|
||||||
"syscall"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"cdr.dev/slog"
|
|
||||||
"cdr.dev/slog/sloggers/sloghuman"
|
|
||||||
|
|
||||||
"oss.terrastruct.com/xos"
|
|
||||||
|
|
||||||
"oss.terrastruct.com/cmdlog"
|
|
||||||
|
|
||||||
ctxlog "oss.terrastruct.com/d2/lib/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
type RunFunc func(context.Context, *State) error
|
|
||||||
|
|
||||||
func Main(run RunFunc) {
|
|
||||||
name := ""
|
|
||||||
args := []string(nil)
|
|
||||||
if len(os.Args) > 0 {
|
|
||||||
name = os.Args[0]
|
|
||||||
args = os.Args[1:]
|
|
||||||
}
|
|
||||||
|
|
||||||
ms := &State{
|
|
||||||
Name: name,
|
|
||||||
|
|
||||||
Stdin: os.Stdin,
|
|
||||||
Stdout: os.Stdout,
|
|
||||||
Stderr: os.Stderr,
|
|
||||||
|
|
||||||
Env: xos.NewEnv(os.Environ()),
|
|
||||||
}
|
|
||||||
ms.Log = cmdlog.New(ms.Env, os.Stderr)
|
|
||||||
ms.Opts = NewOpts(ms.Env, ms.Log, args)
|
|
||||||
|
|
||||||
sigs := make(chan os.Signal, 1)
|
|
||||||
signal.Notify(sigs, os.Interrupt, syscall.SIGTERM)
|
|
||||||
|
|
||||||
err := ms.Main(context.Background(), sigs, run)
|
|
||||||
if err != nil {
|
|
||||||
code := 1
|
|
||||||
msg := ""
|
|
||||||
usage := false
|
|
||||||
|
|
||||||
var eerr ExitError
|
|
||||||
var uerr UsageError
|
|
||||||
if errors.As(err, &eerr) {
|
|
||||||
code = eerr.Code
|
|
||||||
msg = eerr.Message
|
|
||||||
} else if errors.As(err, &uerr) {
|
|
||||||
msg = err.Error()
|
|
||||||
usage = true
|
|
||||||
} else {
|
|
||||||
msg = err.Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
if msg != "" {
|
|
||||||
ms.Log.Error.Print(msg)
|
|
||||||
if usage {
|
|
||||||
ms.Log.Error.Print("Run with --help to see usage.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
os.Exit(code)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type State struct {
|
|
||||||
Name string
|
|
||||||
|
|
||||||
Stdin io.Reader
|
|
||||||
Stdout io.WriteCloser
|
|
||||||
Stderr io.WriteCloser
|
|
||||||
|
|
||||||
Log *cmdlog.Logger
|
|
||||||
Env *xos.Env
|
|
||||||
Opts *Opts
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ms *State) Main(ctx context.Context, sigs <-chan os.Signal, run func(context.Context, *State) error) error {
|
|
||||||
ctx, cancel := context.WithCancel(ctx)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
done := make(chan error, 1)
|
|
||||||
go func() {
|
|
||||||
defer close(done)
|
|
||||||
done <- run(ctx, ms)
|
|
||||||
}()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case err := <-done:
|
|
||||||
return err
|
|
||||||
case sig := <-sigs:
|
|
||||||
ms.Log.Warn.Printf("received signal %v: shutting down...", sig)
|
|
||||||
cancel()
|
|
||||||
select {
|
|
||||||
case err := <-done:
|
|
||||||
if err != nil && !errors.Is(err, context.Canceled) {
|
|
||||||
return fmt.Errorf("failed to shutdown: %w", err)
|
|
||||||
}
|
|
||||||
if sig == syscall.SIGTERM {
|
|
||||||
// We successfully shutdown.
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return ExitError{Code: 1}
|
|
||||||
case <-time.After(time.Minute):
|
|
||||||
return ExitError{
|
|
||||||
Code: 1,
|
|
||||||
Message: "took longer than 1 minute to shutdown: exiting forcefully",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type ExitError struct {
|
|
||||||
Code int `json:"code"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExitErrorf(code int, msg string, v ...interface{}) ExitError {
|
|
||||||
return ExitError{
|
|
||||||
Code: code,
|
|
||||||
Message: fmt.Sprintf(msg, v...),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ee ExitError) Error() string {
|
|
||||||
s := fmt.Sprintf("exiting with code %d", ee.Code)
|
|
||||||
if ee.Message != "" {
|
|
||||||
s += ": " + ee.Message
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
type UsageError struct {
|
|
||||||
Message string `json:"message"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func UsageErrorf(msg string, v ...interface{}) UsageError {
|
|
||||||
return UsageError{
|
|
||||||
Message: fmt.Sprintf(msg, v...),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ue UsageError) Error() string {
|
|
||||||
return fmt.Sprintf("bad usage: %s", ue.Message)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ms *State) ReadPath(fp string) ([]byte, error) {
|
|
||||||
if fp == "-" {
|
|
||||||
return io.ReadAll(ms.Stdin)
|
|
||||||
}
|
|
||||||
return os.ReadFile(fp)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ms *State) WritePath(fp string, p []byte) error {
|
|
||||||
if fp == "-" {
|
|
||||||
_, err := ms.Stdout.Write(p)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return ms.Stdout.Close()
|
|
||||||
}
|
|
||||||
return os.WriteFile(fp, p, 0644)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: remove after removing slog
|
|
||||||
func DiscardSlog(ctx context.Context) context.Context {
|
|
||||||
return ctxlog.With(ctx, slog.Make(sloghuman.Sink(io.Discard)))
|
|
||||||
}
|
|
||||||
15
main.go
15
main.go
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
@ -14,6 +15,8 @@ import (
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
"go.uber.org/multierr"
|
"go.uber.org/multierr"
|
||||||
|
|
||||||
|
"oss.terrastruct.com/util-go/xmain"
|
||||||
|
|
||||||
"oss.terrastruct.com/d2/d2layouts/d2sequence"
|
"oss.terrastruct.com/d2/d2layouts/d2sequence"
|
||||||
"oss.terrastruct.com/d2/d2lib"
|
"oss.terrastruct.com/d2/d2lib"
|
||||||
"oss.terrastruct.com/d2/d2plugin"
|
"oss.terrastruct.com/d2/d2plugin"
|
||||||
|
|
@ -21,10 +24,13 @@ import (
|
||||||
"oss.terrastruct.com/d2/d2themes"
|
"oss.terrastruct.com/d2/d2themes"
|
||||||
"oss.terrastruct.com/d2/d2themes/d2themescatalog"
|
"oss.terrastruct.com/d2/d2themes/d2themescatalog"
|
||||||
"oss.terrastruct.com/d2/lib/imgbundler"
|
"oss.terrastruct.com/d2/lib/imgbundler"
|
||||||
|
ctxlog "oss.terrastruct.com/d2/lib/log"
|
||||||
"oss.terrastruct.com/d2/lib/png"
|
"oss.terrastruct.com/d2/lib/png"
|
||||||
"oss.terrastruct.com/d2/lib/textmeasure"
|
"oss.terrastruct.com/d2/lib/textmeasure"
|
||||||
"oss.terrastruct.com/d2/lib/version"
|
"oss.terrastruct.com/d2/lib/version"
|
||||||
"oss.terrastruct.com/d2/lib/xmain"
|
|
||||||
|
"cdr.dev/slog"
|
||||||
|
"cdr.dev/slog/sloggers/sloghuman"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
@ -33,7 +39,7 @@ func main() {
|
||||||
|
|
||||||
func run(ctx context.Context, ms *xmain.State) (err error) {
|
func run(ctx context.Context, ms *xmain.State) (err error) {
|
||||||
// :(
|
// :(
|
||||||
ctx = xmain.DiscardSlog(ctx)
|
ctx = DiscardSlog(ctx)
|
||||||
|
|
||||||
// These should be kept up-to-date with the d2 man page
|
// 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 is will open on a randomly available local port).")
|
||||||
|
|
@ -257,3 +263,8 @@ func renameExt(fp string, newExt string) string {
|
||||||
return strings.TrimSuffix(fp, ext) + newExt
|
return strings.TrimSuffix(fp, ext) + newExt
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: remove after removing slog
|
||||||
|
func DiscardSlog(ctx context.Context) context.Context {
|
||||||
|
return ctxlog.With(ctx, slog.Make(sloghuman.Sink(io.Discard)))
|
||||||
|
}
|
||||||
|
|
|
||||||
11
watch.go
11
watch.go
|
|
@ -19,11 +19,14 @@ import (
|
||||||
"nhooyr.io/websocket"
|
"nhooyr.io/websocket"
|
||||||
"nhooyr.io/websocket/wsjson"
|
"nhooyr.io/websocket/wsjson"
|
||||||
|
|
||||||
|
"oss.terrastruct.com/util-go/xbrowser"
|
||||||
|
|
||||||
|
"oss.terrastruct.com/util-go/xhttp"
|
||||||
|
|
||||||
|
"oss.terrastruct.com/util-go/xmain"
|
||||||
|
|
||||||
"oss.terrastruct.com/d2/d2plugin"
|
"oss.terrastruct.com/d2/d2plugin"
|
||||||
"oss.terrastruct.com/d2/lib/png"
|
"oss.terrastruct.com/d2/lib/png"
|
||||||
"oss.terrastruct.com/d2/lib/xbrowser"
|
|
||||||
"oss.terrastruct.com/d2/lib/xhttp"
|
|
||||||
"oss.terrastruct.com/d2/lib/xmain"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Enabled with the build tag "dev".
|
// Enabled with the build tag "dev".
|
||||||
|
|
@ -372,7 +375,7 @@ func (w *watcher) compileLoop(ctx context.Context) error {
|
||||||
if firstCompile {
|
if firstCompile {
|
||||||
firstCompile = false
|
firstCompile = false
|
||||||
url := fmt.Sprintf("http://%s", w.l.Addr())
|
url := fmt.Sprintf("http://%s", w.l.Addr())
|
||||||
err = xbrowser.OpenURL(ctx, w.ms.Env, url)
|
err = xbrowser.Open(ctx, w.ms.Env, url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
w.ms.Log.Warn.Printf("failed to open browser to %v: %v", url, err)
|
w.ms.Log.Warn.Printf("failed to open browser to %v: %v", url, err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue