Merge pull request #311 from nhooyr/util-go-ef2b

util-go migration
This commit is contained in:
Anmol Sethi 2022-12-01 11:56:48 -08:00 committed by GitHub
commit 5e151c1e0a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
46 changed files with 191 additions and 1060 deletions

View file

@ -29,6 +29,8 @@
So you can run `go install oss.terrastruct.com/d2@latest` to install from source
now.
[#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 🔴
@ -42,3 +44,6 @@
[#224](https://github.com/terrastruct/d2/pull/224)
- Avoid logging benign file watching errors.
[#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

@ -1 +1 @@
Subproject commit e894a489abaead7314a3f126cdb32f18b272a23f
Subproject commit ea5566075122e826293ef3cf98c5fffcbee99341

View file

@ -3,8 +3,9 @@
package main
import (
"oss.terrastruct.com/util-go/xmain"
"oss.terrastruct.com/d2/d2plugin"
"oss.terrastruct.com/d2/lib/xmain"
)
func main() {

View file

@ -32,7 +32,7 @@ import (
"unicode/utf16"
"unicode/utf8"
"oss.terrastruct.com/xdefer"
"oss.terrastruct.com/util-go/xdefer"
)
// Node is the base interface implemented by all d2 AST nodes.

View file

@ -9,14 +9,16 @@ import (
"strings"
"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/d2format"
"oss.terrastruct.com/d2/d2parser"
"oss.terrastruct.com/d2/lib/go2"
)
func TestRange(t *testing.T) {
@ -193,18 +195,18 @@ func TestRange(t *testing.T) {
var p d2ast.Position
p = p.Advance('a', false)
diff.AssertJSONEq(t, `"0:1:1"`, p)
assert.StringJSON(t, `"0:1:1"`, p)
p = p.Advance('\n', false)
diff.AssertJSONEq(t, `"1:0:2"`, p)
assert.StringJSON(t, `"1:0:2"`, p)
p = p.Advance('è', false)
diff.AssertJSONEq(t, `"1:2:4"`, p)
assert.StringJSON(t, `"1:2:4"`, p)
p = p.Advance('𐀀', false)
diff.AssertJSONEq(t, `"1:6:8"`, p)
assert.StringJSON(t, `"1:6:8"`, p)
p = p.Subtract('𐀀', false)
diff.AssertJSONEq(t, `"1:2:4"`, p)
assert.StringJSON(t, `"1:2:4"`, p)
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) {
@ -212,18 +214,18 @@ func TestRange(t *testing.T) {
var p d2ast.Position
p = p.Advance('a', true)
diff.AssertJSONEq(t, `"0:1:1"`, p)
assert.StringJSON(t, `"0:1:1"`, p)
p = p.Advance('\n', true)
diff.AssertJSONEq(t, `"1:0:2"`, p)
assert.StringJSON(t, `"1:0:2"`, p)
p = p.Advance('è', true)
diff.AssertJSONEq(t, `"1:1:3"`, p)
assert.StringJSON(t, `"1:1:3"`, p)
p = p.Advance('𐀀', true)
diff.AssertJSONEq(t, `"1:3:5"`, p)
assert.StringJSON(t, `"1:3:5"`, p)
p = p.Subtract('𐀀', true)
diff.AssertJSONEq(t, `"1:1:3"`, p)
assert.StringJSON(t, `"1:1:3"`, p)
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",
"nodes": [
{
@ -807,7 +809,7 @@ _park `,
t.Parallel()
ast := d2ast.RawString(tc.str, tc.inKey)
diff.AssertStringEq(t, tc.exp, d2format.Format(ast))
assert.String(t, tc.exp, d2format.Format(ast))
})
}
}

View file

@ -6,12 +6,13 @@ import (
"strings"
"time"
"oss.terrastruct.com/util-go/go2"
"oss.terrastruct.com/d2/d2ast"
"oss.terrastruct.com/d2/d2format"
"oss.terrastruct.com/d2/d2graph"
"oss.terrastruct.com/d2/d2oracle"
"oss.terrastruct.com/d2/d2target"
"oss.terrastruct.com/d2/lib/go2"
)
func GenDSL(maxi int) (_ string, err error) {

View file

@ -8,12 +8,13 @@ import (
"strconv"
"strings"
"oss.terrastruct.com/util-go/go2"
"oss.terrastruct.com/d2/d2ast"
"oss.terrastruct.com/d2/d2format"
"oss.terrastruct.com/d2/d2graph"
"oss.terrastruct.com/d2/d2parser"
"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

View file

@ -6,9 +6,8 @@ import (
"strings"
"testing"
"oss.terrastruct.com/diff"
"github.com/stretchr/testify/assert"
"oss.terrastruct.com/util-go/assert"
"oss.terrastruct.com/util-go/diff"
"oss.terrastruct.com/d2/d2compiler"
"oss.terrastruct.com/d2/d2format"
@ -775,8 +774,8 @@ x -> y: {
if len(g.Objects) != 2 {
t.Fatalf("expected 2 objects: %#v", g.Objects)
}
diff.AssertStringEq(t, "diamond", g.Edges[0].SrcArrowhead.Shape.Value)
assert.Empty(t, g.Edges[0].Attributes.Shape.Value)
assert.String(t, "diamond", g.Edges[0].SrcArrowhead.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
exp := `x -> y: {
source-arrowhead: {
@ -814,13 +813,13 @@ x -> y: {
if len(g.Objects) != 2 {
t.Fatalf("expected 2 objects: %#v", g.Objects)
}
diff.AssertStringEq(t, "diamond", g.Edges[0].SrcArrowhead.Shape.Value)
diff.AssertStringEq(t, "Reisner's Rule of Conceptual Inertia", g.Edges[0].SrcArrowhead.Label.Value)
diff.AssertStringEq(t, "QOTD", g.Edges[0].DstArrowhead.Label.Value)
diff.AssertStringEq(t, "true", g.Edges[0].DstArrowhead.Style.Filled.Value)
assert.Empty(t, g.Edges[0].Attributes.Shape.Value)
assert.Empty(t, g.Edges[0].Attributes.Label.Value)
assert.Nil(t, g.Edges[0].Attributes.Style.Filled)
assert.String(t, "diamond", g.Edges[0].SrcArrowhead.Shape.Value)
assert.String(t, "Reisner's Rule of Conceptual Inertia", g.Edges[0].SrcArrowhead.Label.Value)
assert.String(t, "QOTD", g.Edges[0].DstArrowhead.Label.Value)
assert.String(t, "true", g.Edges[0].DstArrowhead.Style.Filled.Value)
assert.String(t, "", g.Edges[0].Attributes.Shape.Value)
assert.String(t, "", g.Edges[0].Attributes.Label.Value)
assert.JSON(t, nil, g.Edges[0].Attributes.Style.Filled)
},
},
{
@ -836,8 +835,8 @@ x -> y: {
if len(g.Objects) != 2 {
t.Fatalf("expected 2 objects: %#v", g.Objects)
}
diff.AssertStringEq(t, "diamond", g.Edges[0].SrcArrowhead.Shape.Value)
assert.Empty(t, g.Edges[0].Attributes.Shape.Value)
assert.String(t, "diamond", g.Edges[0].SrcArrowhead.Shape.Value)
assert.String(t, "", g.Edges[0].Attributes.Shape.Value)
},
},
{
@ -853,8 +852,8 @@ x -> y: {
if len(g.Objects) != 2 {
t.Fatalf("expected 2 objects: %#v", g.Objects)
}
diff.AssertStringEq(t, "triangle", g.Edges[0].SrcArrowhead.Shape.Value)
assert.Empty(t, g.Edges[0].Attributes.Shape.Value)
assert.String(t, "triangle", g.Edges[0].SrcArrowhead.Shape.Value)
assert.String(t, "", g.Edges[0].Attributes.Shape.Value)
},
},
{
@ -880,8 +879,8 @@ x -> y: {
if len(g.Objects) != 2 {
t.Fatalf("expected 2 objects: %#v", g.Objects)
}
diff.AssertStringEq(t, "yo", g.Edges[0].SrcArrowhead.Label.Value)
assert.Empty(t, g.Edges[0].Attributes.Label.Value)
assert.String(t, "yo", g.Edges[0].SrcArrowhead.Label.Value)
assert.String(t, "", g.Edges[0].Attributes.Label.Value)
},
},
{
@ -899,8 +898,8 @@ x -> y: {
if len(g.Objects) != 2 {
t.Fatalf("expected 2 objects: %#v", g.Objects)
}
diff.AssertStringEq(t, "diamond", g.Edges[0].SrcArrowhead.Shape.Value)
assert.Empty(t, g.Edges[0].Attributes.Shape.Value)
assert.String(t, "diamond", g.Edges[0].SrcArrowhead.Shape.Value)
assert.String(t, "", g.Edges[0].Attributes.Shape.Value)
},
},
{
@ -920,9 +919,9 @@ x -> y: {
if len(g.Objects) != 2 {
t.Fatalf("expected 2 objects: %#v", g.Objects)
}
diff.AssertStringEq(t, "diamond", g.Edges[0].SrcArrowhead.Shape.Value)
diff.AssertStringEq(t, "diamond", g.Edges[0].DstArrowhead.Shape.Value)
assert.Empty(t, g.Edges[0].Attributes.Shape.Value)
assert.String(t, "diamond", g.Edges[0].SrcArrowhead.Shape.Value)
assert.String(t, "diamond", g.Edges[0].DstArrowhead.Shape.Value)
assert.String(t, "", g.Edges[0].Attributes.Shape.Value)
},
},
{
@ -1333,7 +1332,7 @@ y -> x.style
if len(g.Objects) != 1 {
t.Fatal(g.Objects)
}
diff.AssertStringEq(t, `b
assert.String(t, `b
b`, g.Objects[0].Attributes.Label.Value)
},
},
@ -1420,9 +1419,9 @@ b`, g.Objects[0].Attributes.Label.Value)
if len(g.Objects) != 1 {
t.Fatal(g.Objects)
}
diff.AssertStringEq(t, `field here`, g.Objects[0].Class.Fields[0].Name)
diff.AssertStringEq(t, `GetType()`, g.Objects[0].Class.Methods[0].Name)
diff.AssertStringEq(t, `Is()`, g.Objects[0].Class.Methods[1].Name)
assert.String(t, `field here`, g.Objects[0].Class.Fields[0].Name)
assert.String(t, `GetType()`, g.Objects[0].Class.Methods[0].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 {
t.Fatal(g.Objects)
}
diff.AssertStringEq(t, `GetType()`, g.Objects[0].SQLTable.Columns[0].Name)
diff.AssertStringEq(t, `Is()`, g.Objects[0].SQLTable.Columns[1].Name)
assert.String(t, `GetType()`, g.Objects[0].SQLTable.Columns[0].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 {
t.Fatal(g.Objects)
}
diff.AssertStringEq(t, `GetType()`, g.Objects[1].SQLTable.Columns[0].Name)
diff.AssertStringEq(t, `Is()`, g.Objects[1].SQLTable.Columns[1].Name)
assert.String(t, `GetType()`, g.Objects[1].SQLTable.Columns[0].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) {
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
`,
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`,
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`,
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
}`,
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 = diff.Testdata(filepath.Join("..", "testdata", "d2compiler", t.Name()), got)
if err != nil {
t.Fatal(err)
}
err = diff.TestdataJSON(filepath.Join("..", "testdata", "d2compiler", t.Name()), got)
assert.Success(t, err)
})
}
}

View file

@ -8,9 +8,8 @@ import (
"cdr.dev/slog"
"oss.terrastruct.com/diff"
"github.com/stretchr/testify/assert"
"oss.terrastruct.com/util-go/assert"
"oss.terrastruct.com/util-go/diff"
"oss.terrastruct.com/d2/d2compiler"
"oss.terrastruct.com/d2/d2exporter"
@ -215,10 +214,10 @@ func run(t *testing.T, tc testCase) {
}
ruler, err := textmeasure.NewRuler()
assert.Nil(t, err)
assert.JSON(t, nil, err)
err = g.SetDimensions(nil, ruler)
assert.Nil(t, err)
assert.JSON(t, nil, err)
err = d2dagrelayout.Layout(ctx, g)
if err != nil {
@ -252,8 +251,6 @@ func run(t *testing.T, tc testCase) {
got.Connections[i].LabelPosition = ""
}
err = diff.Testdata(filepath.Join("..", "testdata", "d2exporter", t.Name()), got)
if err != nil {
t.Fatal(err)
}
err = diff.TestdataJSON(filepath.Join("..", "testdata", "d2exporter", t.Name()), got)
assert.Success(t, err)
}

View file

@ -3,7 +3,7 @@ package d2format_test
import (
"testing"
"oss.terrastruct.com/diff"
"oss.terrastruct.com/util-go/assert"
"oss.terrastruct.com/d2/d2ast"
"oss.terrastruct.com/d2/d2format"
@ -42,7 +42,7 @@ func TestEscapeSingleQuoted(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
diff.AssertStringEq(t, tc.exp, d2format.Format(&d2ast.SingleQuotedString{
assert.String(t, tc.exp, d2format.Format(&d2ast.SingleQuotedString{
Value: tc.str,
}))
})
@ -104,7 +104,7 @@ func TestEscapeDoubleQuoted(t *testing.T) {
} else {
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)
}
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,
}
diff.AssertStringEq(t, tc.exp, d2format.Format(n))
assert.String(t, tc.exp, d2format.Format(n))
})
}
}

View file

@ -5,7 +5,7 @@ import (
"strings"
"testing"
"oss.terrastruct.com/diff"
"oss.terrastruct.com/util-go/assert"
"oss.terrastruct.com/d2/d2format"
"oss.terrastruct.com/d2/d2parser"
@ -605,7 +605,7 @@ hi # Fraud is the homage that force pays to reason.
if err != nil {
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)
}
diff.AssertStringEq(t, `x -> y`, d2format.Format(mk.Edges[0]))
diff.AssertStringEq(t, `[0]`, d2format.Format(mk.EdgeIndex))
assert.String(t, `x -> y`, d2format.Format(mk.Edges[0]))
assert.String(t, `[0]`, d2format.Format(mk.EdgeIndex))
}

View file

@ -7,6 +7,8 @@ import (
"strconv"
"strings"
"oss.terrastruct.com/util-go/go2"
"oss.terrastruct.com/d2/d2ast"
"oss.terrastruct.com/d2/d2format"
"oss.terrastruct.com/d2/d2parser"
@ -15,7 +17,6 @@ import (
"oss.terrastruct.com/d2/d2target"
"oss.terrastruct.com/d2/d2themes"
"oss.terrastruct.com/d2/lib/geo"
"oss.terrastruct.com/d2/lib/go2"
"oss.terrastruct.com/d2/lib/textmeasure"
)

View file

@ -4,7 +4,7 @@ import (
"strings"
"testing"
"oss.terrastruct.com/diff"
"oss.terrastruct.com/util-go/assert"
"oss.terrastruct.com/d2/d2graph"
"oss.terrastruct.com/d2/d2parser"
@ -44,7 +44,7 @@ func TestKey(t *testing.T) {
if err != nil {
t.Fatal(err)
}
diff.AssertStringEq(t, tc.exp, strings.Join(d2graph.Key(k), "."))
assert.String(t, tc.exp, strings.Join(d2graph.Key(k), "."))
})
}
}

View file

@ -3,7 +3,7 @@ package d2graph
import (
"encoding/json"
"oss.terrastruct.com/d2/lib/go2"
"oss.terrastruct.com/util-go/go2"
)
type SerializedGraph struct {

View file

@ -11,12 +11,13 @@ import (
"cdr.dev/slog"
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/d2target"
"oss.terrastruct.com/d2/lib/geo"
"oss.terrastruct.com/d2/lib/go2"
"oss.terrastruct.com/d2/lib/label"
"oss.terrastruct.com/d2/lib/log"
"oss.terrastruct.com/d2/lib/shape"

View file

@ -13,12 +13,13 @@ import (
"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/d2target"
"oss.terrastruct.com/d2/lib/geo"
"oss.terrastruct.com/d2/lib/go2"
"oss.terrastruct.com/d2/lib/label"
)

View file

@ -6,9 +6,10 @@ import (
"math"
"sort"
"oss.terrastruct.com/util-go/go2"
"oss.terrastruct.com/d2/d2graph"
"oss.terrastruct.com/d2/lib/geo"
"oss.terrastruct.com/d2/lib/go2"
"oss.terrastruct.com/d2/lib/label"
"oss.terrastruct.com/d2/lib/shape"
)

View file

@ -7,9 +7,11 @@ import (
"strings"
"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/d2compiler"
@ -17,7 +19,6 @@ import (
"oss.terrastruct.com/d2/d2graph"
"oss.terrastruct.com/d2/d2parser"
"oss.terrastruct.com/d2/d2target"
"oss.terrastruct.com/d2/lib/go2"
)
func Create(g *d2graph.Graph, key string) (_ *d2graph.Graph, newKey string, err error) {

View file

@ -7,18 +7,16 @@ import (
"strings"
"testing"
"github.com/stretchr/testify/assert"
"oss.terrastruct.com/xjson"
"oss.terrastruct.com/diff"
"oss.terrastruct.com/util-go/assert"
"oss.terrastruct.com/util-go/diff"
"oss.terrastruct.com/util-go/go2"
"oss.terrastruct.com/util-go/xjson"
"oss.terrastruct.com/d2/d2compiler"
"oss.terrastruct.com/d2/d2format"
"oss.terrastruct.com/d2/d2graph"
"oss.terrastruct.com/d2/d2oracle"
"oss.terrastruct.com/d2/d2target"
"oss.terrastruct.com/d2/lib/go2"
)
// TODO: make assertions less specific
@ -966,10 +964,10 @@ z: {
}
`,
assertions: func(t *testing.T, g *d2graph.Graph) {
assert.Equal(t, 3, len(g.Objects))
assert.Equal(t, 1, len(g.Edges))
assert.Equal(t, "q", g.Edges[0].Src.ID)
assert.Equal(t, "0.4", g.Edges[0].Attributes.Style.Opacity.Value)
assert.JSON(t, 3, len(g.Objects))
assert.JSON(t, 1, len(g.Edges))
assert.JSON(t, "q", g.Edges[0].Src.ID)
assert.JSON(t, "0.4", g.Edges[0].Attributes.Style.Opacity.Value)
},
},
{
@ -1617,8 +1615,8 @@ func TestMove(t *testing.T) {
exp: `b
`,
assertions: func(t *testing.T, g *d2graph.Graph) {
assert.Equal(t, len(g.Objects), 1)
assert.Equal(t, g.Objects[0].ID, "b")
assert.JSON(t, len(g.Objects), 1)
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) {
assert.Equal(t, len(g.Objects), 2)
assert.Equal(t, g.Objects[1].ID, "c")
assert.JSON(t, len(g.Objects), 2)
assert.JSON(t, g.Objects[1].ID, "c")
},
},
{
@ -1692,9 +1690,9 @@ c
}
`,
assertions: func(t *testing.T, g *d2graph.Graph) {
assert.Equal(t, len(g.Objects), 3)
assert.Equal(t, "a", g.Objects[0].ID)
assert.Equal(t, 2, len(g.Objects[0].Children))
assert.JSON(t, len(g.Objects), 3)
assert.JSON(t, "a", g.Objects[0].ID)
assert.JSON(t, 2, len(g.Objects[0].Children))
},
},
{
@ -1733,9 +1731,9 @@ c
}
`,
assertions: func(t *testing.T, g *d2graph.Graph) {
assert.Equal(t, len(g.Objects), 2)
assert.Equal(t, "a", g.Objects[0].ID)
assert.Equal(t, 1, len(g.Objects[0].Children))
assert.JSON(t, len(g.Objects), 2)
assert.JSON(t, "a", g.Objects[0].ID)
assert.JSON(t, 1, len(g.Objects[0].Children))
},
},
{
@ -1752,9 +1750,9 @@ c
b
`,
assertions: func(t *testing.T, g *d2graph.Graph) {
assert.Equal(t, len(g.Objects), 2)
assert.Equal(t, "a", g.Objects[0].ID)
assert.Equal(t, 0, len(g.Objects[0].Children))
assert.JSON(t, len(g.Objects), 2)
assert.JSON(t, "a", g.Objects[0].ID)
assert.JSON(t, 0, len(g.Objects[0].Children))
},
},
{
@ -1863,11 +1861,11 @@ c: {
}
`,
assertions: func(t *testing.T, g *d2graph.Graph) {
assert.Equal(t, len(g.Objects), 3)
assert.Equal(t, "a", g.Objects[0].ID)
assert.Equal(t, 0, len(g.Objects[0].Children))
assert.Equal(t, "c", g.Objects[1].ID)
assert.Equal(t, 1, len(g.Objects[1].Children))
assert.JSON(t, len(g.Objects), 3)
assert.JSON(t, "a", g.Objects[0].ID)
assert.JSON(t, 0, len(g.Objects[0].Children))
assert.JSON(t, "c", g.Objects[1].ID)
assert.JSON(t, 1, len(g.Objects[1].Children))
},
},
{
@ -1929,7 +1927,7 @@ a: {
}
`,
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) {
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) {
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) {
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 = diff.Testdata(filepath.Join("..", "testdata", "d2oracle", t.Name()), got)
if err != nil {
t.Fatal(err)
}
err = diff.TestdataJSON(filepath.Join("..", "testdata", "d2oracle", t.Name()), got)
assert.Success(t, err)
}
func TestMoveIDDeltas(t *testing.T) {
@ -4635,7 +4631,7 @@ x.a -> x.b
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 {
t.Fatal(err)
}
@ -4825,7 +4821,7 @@ x.y.z.w.e.p.l -> x.y.z.1.2.3.4
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 {
t.Fatal(err)
}
@ -4977,7 +4973,7 @@ x.y.z.w.e.p.l -> x.y.z.1.2.3.4
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 {
t.Fatal(err)
}

View file

@ -9,8 +9,9 @@ import (
"unicode"
"unicode/utf8"
"oss.terrastruct.com/util-go/go2"
"oss.terrastruct.com/d2/d2ast"
"oss.terrastruct.com/d2/lib/go2"
)
type ParseOptions struct {

View file

@ -6,7 +6,8 @@ import (
"strings"
"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/d2parser"
@ -382,10 +383,8 @@ q.(x -> y).z: (rawr)
Err: err,
}
err = diff.Testdata(filepath.Join("..", "testdata", "d2parser", t.Name()), got)
if err != nil {
t.Fatal(err)
}
err = diff.TestdataJSON(filepath.Join("..", "testdata", "d2parser", t.Name()), got)
assert.Success(t, err)
})
}
}

View file

@ -9,7 +9,7 @@ import (
"os/exec"
"time"
"oss.terrastruct.com/xdefer"
"oss.terrastruct.com/util-go/xdefer"
"oss.terrastruct.com/d2/d2graph"
)

View file

@ -9,8 +9,9 @@ import (
"context"
"os/exec"
"oss.terrastruct.com/util-go/xexec"
"oss.terrastruct.com/d2/d2graph"
"oss.terrastruct.com/d2/lib/xexec"
)
// plugins contains the bundled d2 plugins.

View file

@ -7,8 +7,9 @@ import (
"fmt"
"io"
"oss.terrastruct.com/util-go/xmain"
"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

View file

@ -9,7 +9,7 @@ import (
"regexp"
"strconv"
"oss.terrastruct.com/xdefer"
"oss.terrastruct.com/util-go/xdefer"
v8 "rogchap.com/v8go"
)

View file

@ -20,12 +20,13 @@ import (
"github.com/alecthomas/chroma/lexers"
"github.com/alecthomas/chroma/styles"
"oss.terrastruct.com/util-go/go2"
"oss.terrastruct.com/d2/d2renderers/d2fonts"
"oss.terrastruct.com/d2/d2renderers/d2latex"
"oss.terrastruct.com/d2/d2target"
"oss.terrastruct.com/d2/lib/color"
"oss.terrastruct.com/d2/lib/geo"
"oss.terrastruct.com/d2/lib/go2"
"oss.terrastruct.com/d2/lib/label"
"oss.terrastruct.com/d2/lib/shape"
"oss.terrastruct.com/d2/lib/textmeasure"

View file

@ -5,9 +5,10 @@ import (
"net/url"
"strings"
"oss.terrastruct.com/util-go/go2"
"oss.terrastruct.com/d2/d2themes"
"oss.terrastruct.com/d2/lib/geo"
"oss.terrastruct.com/d2/lib/go2"
"oss.terrastruct.com/d2/lib/label"
"oss.terrastruct.com/d2/lib/shape"
)

5
fmt.go
View file

@ -4,11 +4,12 @@ import (
"bytes"
"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/d2parser"
"oss.terrastruct.com/d2/lib/xmain"
)
func fmtCmd(ctx context.Context, ms *xmain.State) (err error) {

15
go.mod generated
View file

@ -10,27 +10,17 @@ require (
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
github.com/lucasb-eyer/go-colorful v1.2.0
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/spf13/pflag v1.0.5
github.com/stretchr/testify v1.8.1
github.com/yuin/goldmark v1.5.3
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/net v0.2.0
golang.org/x/text v0.4.0
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2
gonum.org/v1/plot v0.12.0
nhooyr.io/websocket v1.8.7
oss.terrastruct.com/cmdlog v0.0.0-20221201100934-012c01b3431c
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
oss.terrastruct.com/util-go v0.0.0-20221201191904-5edc89ce397b
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-isatty v0.0.16 // 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/rogpeppe/go-internal v1.8.0 // indirect
github.com/ugorji/go/codec v1.2.6 // indirect
go.opencensus.io v0.24.0 // indirect
go.uber.org/atomic v1.7.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/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/protobuf v1.28.1 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect

18
go.sum generated
View file

@ -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=
nhooyr.io/websocket v1.8.7 h1:usjR2uOr/zjjkVMy0lW+PPohFok7PCow5sDjLgX4P4g=
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/cmdlog v0.0.0-20221201100934-012c01b3431c/go.mod h1:C8u/lYTvQWc1xC7rHpgFfpScfQC4NMeGGMmlKVZZUXM=
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=
oss.terrastruct.com/util-go v0.0.0-20221201191904-5edc89ce397b h1:o8+5KfZpQyaw7uKcPIdc9HOqVjVDEdsPZpdRV1k0rmc=
oss.terrastruct.com/util-go v0.0.0-20221201191904-5edc89ce397b/go.mod h1:Fwy72FDIOOM4K8F96ScXkxHHppR1CPfUyo9+x9c1PBU=
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=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=

View file

@ -9,8 +9,9 @@ import (
"path/filepath"
"strings"
"oss.terrastruct.com/util-go/xmain"
"oss.terrastruct.com/d2/d2plugin"
"oss.terrastruct.com/d2/lib/xmain"
)
func help(ms *xmain.State) {

View file

@ -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)))
}

View file

@ -17,9 +17,9 @@ import (
"time"
"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

View file

@ -12,10 +12,10 @@ import (
"strings"
"testing"
"oss.terrastruct.com/cmdlog"
"oss.terrastruct.com/xos"
"oss.terrastruct.com/util-go/cmdlog"
"oss.terrastruct.com/util-go/xos"
"oss.terrastruct.com/d2/lib/xmain"
"oss.terrastruct.com/util-go/xmain"
)
//go:embed test_png.png

View file

@ -9,7 +9,7 @@ import (
"github.com/playwright-community/playwright-go"
"oss.terrastruct.com/d2/lib/xmain"
"oss.terrastruct.com/util-go/xmain"
)
type Playwright struct {

View file

@ -10,8 +10,9 @@ import (
goldmarkHtml "github.com/yuin/goldmark/renderer/html"
"golang.org/x/net/html"
"oss.terrastruct.com/util-go/go2"
"oss.terrastruct.com/d2/d2renderers/d2fonts"
"oss.terrastruct.com/d2/lib/go2"
)
var markdownRenderer goldmark.Markdown

View file

@ -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)
}

View file

@ -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
}

View file

@ -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)
}

View file

@ -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)
})
}

View file

@ -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)
}
}

View file

@ -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:]
}

View file

@ -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"
}

View file

@ -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
View file

@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
@ -14,6 +15,8 @@ import (
"github.com/spf13/pflag"
"go.uber.org/multierr"
"oss.terrastruct.com/util-go/xmain"
"oss.terrastruct.com/d2/d2layouts/d2sequence"
"oss.terrastruct.com/d2/d2lib"
"oss.terrastruct.com/d2/d2plugin"
@ -21,10 +24,13 @@ import (
"oss.terrastruct.com/d2/d2themes"
"oss.terrastruct.com/d2/d2themes/d2themescatalog"
"oss.terrastruct.com/d2/lib/imgbundler"
ctxlog "oss.terrastruct.com/d2/lib/log"
"oss.terrastruct.com/d2/lib/png"
"oss.terrastruct.com/d2/lib/textmeasure"
"oss.terrastruct.com/d2/lib/version"
"oss.terrastruct.com/d2/lib/xmain"
"cdr.dev/slog"
"cdr.dev/slog/sloggers/sloghuman"
)
func main() {
@ -33,7 +39,7 @@ func main() {
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
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
}
}
// TODO: remove after removing slog
func DiscardSlog(ctx context.Context) context.Context {
return ctxlog.With(ctx, slog.Make(sloghuman.Sink(io.Discard)))
}

View file

@ -19,11 +19,14 @@ import (
"nhooyr.io/websocket"
"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/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".
@ -372,7 +375,7 @@ func (w *watcher) compileLoop(ctx context.Context) error {
if firstCompile {
firstCompile = false
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 {
w.ms.Log.Warn.Printf("failed to open browser to %v: %v", url, err)
}