Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion d2cli/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -783,7 +783,7 @@ func relink(currDiagramPath string, d *d2target.Diagram, linkToOutput map[string
if err != nil {
return err
}
d.Shapes[i].Link = rel
d.Shapes[i].Link = filepath.ToSlash(rel)
break
}
}
Expand Down
2 changes: 1 addition & 1 deletion d2cli/watch.go
Original file line number Diff line number Diff line change
Expand Up @@ -442,7 +442,7 @@ func (w *watcher) compileLoop(ctx context.Context) error {
w.boardpathMu.Lock()
var boardPath []string
if w.boardPath != "" {
boardPath = strings.Split(w.boardPath, string(os.PathSeparator))
boardPath = strings.Split(w.boardPath, "/")
}
svg, _, err := compile(ctx, w.ms, w.plugins, &fs, w.layout, w.renderOpts, w.fontFamily, w.monoFontFamily, w.animateInterval, w.inputPath, w.outputPath, boardPath, false, w.bundle, w.forceAppendix, w.pw.Browser, w.outputFormat, w.asciiMode)
w.boardpathMu.Unlock()
Expand Down
11 changes: 11 additions & 0 deletions d2compiler/compile.go
Original file line number Diff line number Diff line change
Expand Up @@ -1251,6 +1251,9 @@ func (c *compiler) validatePositionsCompatibility(g *d2graph.Graph) {
if o.OuterSequenceDiagram() != nil {
c.errorf(pos.MapKey, `position keywords cannot be used inside shape "sequence_diagram"`)
}
if o.OuterCycleDiagram() != nil {
c.errorf(pos.MapKey, `position keywords cannot be used inside shape "cycle"`)
}
if o.Parent.GridColumns != nil || o.Parent.GridRows != nil {
c.errorf(pos.MapKey, `position keywords cannot be used with grids`)
}
Expand Down Expand Up @@ -1290,6 +1293,14 @@ func (c *compiler) validateEdges(g *d2graph.Graph) {
c.errorf(edge.GetAstEdge(), "edge from sequence diagram %#v cannot enter itself", edge.Dst.AbsID())
continue
}
if edge.Src.IsCycleDiagram() && edge.Dst.IsDescendantOf(edge.Src) {
c.errorf(edge.GetAstEdge(), "edge from cycle diagram %#v cannot enter itself", edge.Src.AbsID())
continue
}
if edge.Dst.IsCycleDiagram() && edge.Src.IsDescendantOf(edge.Dst) {
c.errorf(edge.GetAstEdge(), "edge from cycle diagram %#v cannot enter itself", edge.Dst.AbsID())
continue
}
}
}

Expand Down
27 changes: 24 additions & 3 deletions d2compiler/compile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"oss.terrastruct.com/d2/d2format"
"oss.terrastruct.com/d2/d2graph"
"oss.terrastruct.com/d2/d2target"
"oss.terrastruct.com/d2/internal/testdiff"
)

func TestCompile(t *testing.T) {
Expand Down Expand Up @@ -3238,6 +3239,12 @@ grid.cell -> grid.cell.c: no
grid.cell -> grid.cell.c.d: no
seq -> seq.e: no
seq -> seq.e.f: no
cycle: {
shape: cycle
e.f
}
cycle -> cycle.e: no
cycle -> cycle.e.f: no
`,
expErr: `d2/testdata/d2compiler/TestCompile/parent_graph_edge_to_descendant.d2:13:1: edge from constant near "tl" cannot enter itself
d2/testdata/d2compiler/TestCompile/parent_graph_edge_to_descendant.d2:14:1: edge from constant near "tl" cannot enter itself
Expand All @@ -3246,7 +3253,9 @@ d2/testdata/d2compiler/TestCompile/parent_graph_edge_to_descendant.d2:18:1: edge
d2/testdata/d2compiler/TestCompile/parent_graph_edge_to_descendant.d2:15:1: edge from grid diagram "grid" cannot enter itself
d2/testdata/d2compiler/TestCompile/parent_graph_edge_to_descendant.d2:16:1: edge from grid diagram "grid" cannot enter itself
d2/testdata/d2compiler/TestCompile/parent_graph_edge_to_descendant.d2:19:1: edge from sequence diagram "seq" cannot enter itself
d2/testdata/d2compiler/TestCompile/parent_graph_edge_to_descendant.d2:20:1: edge from sequence diagram "seq" cannot enter itself`,
d2/testdata/d2compiler/TestCompile/parent_graph_edge_to_descendant.d2:20:1: edge from sequence diagram "seq" cannot enter itself
d2/testdata/d2compiler/TestCompile/parent_graph_edge_to_descendant.d2:25:1: edge from cycle diagram "cycle" cannot enter itself
d2/testdata/d2compiler/TestCompile/parent_graph_edge_to_descendant.d2:26:1: edge from cycle diagram "cycle" cannot enter itself`,
},
{
name: "grid_nested",
Expand Down Expand Up @@ -3688,6 +3697,18 @@ d2/testdata/d2compiler/TestCompile/no_arrowheads_in_shape.d2:2:3: "source-arrowh
`,
expErr: `d2/testdata/d2compiler/TestCompile/fixed-pos-shape-hierarchy.d2:4:2: position keywords cannot be used with shape "hierarchy"
d2/testdata/d2compiler/TestCompile/fixed-pos-shape-hierarchy.d2:5:2: position keywords cannot be used with shape "hierarchy"`,
},
{
name: "fixed-pos-shape-cycle",
text: `x: {
shape: cycle
a -> b
a.top: 20
a.left: 20
}
`,
expErr: `d2/testdata/d2compiler/TestCompile/fixed-pos-shape-cycle.d2:4:3: position keywords cannot be used inside shape "cycle"
d2/testdata/d2compiler/TestCompile/fixed-pos-shape-cycle.d2:5:3: position keywords cannot be used inside shape "cycle"`,
},
{
name: "vars-in-imports",
Expand Down Expand Up @@ -4085,7 +4106,7 @@ svc_1.t2 -> b: do with B
Err: err,
}

err = diff.TestdataJSON(filepath.Join("..", "testdata", "d2compiler", t.Name()), got)
err = testdiff.TestdataJSON(filepath.Join("..", "testdata", "d2compiler", t.Name()), got)
assert.Success(t, err)
})
}
Expand Down Expand Up @@ -6349,7 +6370,7 @@ func assertCompile(t *testing.T, text string, expErr string) (*d2graph.Graph, *d
Err: err,
}

err = diff.TestdataJSON(filepath.Join("..", "testdata", "d2compiler", t.Name()), got)
err = testdiff.TestdataJSON(filepath.Join("..", "testdata", "d2compiler", t.Name()), got)
assert.Success(t, err)
return g, config
}
2 changes: 1 addition & 1 deletion d2exporter/export.go
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ func toShape(obj *d2graph.Object, g *d2graph.Graph) d2target.Shape {
shape.Italic = text.IsItalic
shape.FontSize = text.FontSize

if obj.IsSequenceDiagram() {
if obj.IsSequenceDiagram() || obj.IsCycleDiagram() {
shape.StrokeWidth = 0
}

Expand Down
4 changes: 2 additions & 2 deletions d2exporter/export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import (
tassert "github.com/stretchr/testify/assert"

"oss.terrastruct.com/util-go/assert"
"oss.terrastruct.com/util-go/diff"
"oss.terrastruct.com/util-go/go2"

"oss.terrastruct.com/d2/d2compiler"
Expand All @@ -20,6 +19,7 @@ import (
"oss.terrastruct.com/d2/d2layouts/d2dagrelayout"
"oss.terrastruct.com/d2/d2lib"
"oss.terrastruct.com/d2/d2target"
"oss.terrastruct.com/d2/internal/testdiff"
"oss.terrastruct.com/d2/lib/geo"
"oss.terrastruct.com/d2/lib/log"
"oss.terrastruct.com/d2/lib/textmeasure"
Expand Down Expand Up @@ -294,7 +294,7 @@ func run(t *testing.T, tc testCase) {
got.Connections[i].LabelPosition = ""
}

err = diff.TestdataJSON(filepath.Join("..", "testdata", "d2exporter", t.Name()), got)
err = testdiff.TestdataJSON(filepath.Join("..", "testdata", "d2exporter", t.Name()), got)
assert.Success(t, err)
}

Expand Down
17 changes: 17 additions & 0 deletions d2graph/cycle_diagram.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package d2graph

import "oss.terrastruct.com/d2/d2target"

func (obj *Object) IsCycleDiagram() bool {
return obj != nil && obj.Shape.Value == d2target.ShapeCycleDiagram
}

func (obj *Object) OuterCycleDiagram() *Object {
for obj != nil {
obj = obj.Parent
if obj.IsCycleDiagram() {
return obj
}
}
return nil
}
4 changes: 2 additions & 2 deletions d2ir/compile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ import (
"testing"

"oss.terrastruct.com/util-go/assert"
"oss.terrastruct.com/util-go/diff"
"oss.terrastruct.com/util-go/mapfs"

"oss.terrastruct.com/d2/d2ast"
"oss.terrastruct.com/d2/d2ir"
"oss.terrastruct.com/d2/d2parser"
"oss.terrastruct.com/d2/internal/testdiff"
)

func TestCompile(t *testing.T) {
Expand Down Expand Up @@ -74,7 +74,7 @@ func compileFS(t testing.TB, path string, mfs map[string]string) (*d2ir.Map, err
return nil, err
}

err = diff.TestdataJSON(filepath.Join("..", "testdata", "d2ir", t.Name()), m)
err = testdiff.TestdataJSON(filepath.Join("..", "testdata", "d2ir", t.Name()), m)
if err != nil {
return nil, err
}
Expand Down
39 changes: 25 additions & 14 deletions d2ir/import.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,7 @@ func (c *compiler) pushImportStack(imp *d2ast.Import) (string, bool) {
return "", false
}
if len(c.importStack) > 0 {
if path.Ext(impPath) != ".d2" {
impPath += ".d2"
}

if !filepath.IsAbs(impPath) {
impPath = path.Join(path.Dir(c.importStack[len(c.importStack)-1]), impPath)
}
impPath = resolveImportPath(c.importStack[len(c.importStack)-1], impPath)
}

for i, p := range c.importStack {
Expand Down Expand Up @@ -131,13 +125,7 @@ func (c *compiler) peekImport(imp *d2ast.Import) (*Map, bool) {
}

if len(c.importStack) > 0 {
if path.Ext(impPath) != ".d2" {
impPath += ".d2"
}

if !filepath.IsAbs(impPath) {
impPath = path.Join(path.Dir(c.importStack[len(c.importStack)-1]), impPath)
}
impPath = resolveImportPath(c.importStack[len(c.importStack)-1], impPath)
}

var f fs.File
Expand Down Expand Up @@ -171,6 +159,29 @@ func (c *compiler) peekImport(imp *d2ast.Import) (*Map, bool) {
return ir, true
}

func resolveImportPath(parentPath, impPath string) string {
if path.Ext(impPath) != ".d2" {
impPath += ".d2"
}

if isOSPath(parentPath) {
impPath = filepath.FromSlash(impPath)
if !filepath.IsAbs(impPath) {
impPath = filepath.Join(filepath.Dir(parentPath), impPath)
}
return impPath
}

if !path.IsAbs(impPath) {
impPath = path.Join(path.Dir(parentPath), impPath)
}
return impPath
}

func isOSPath(p string) bool {
return filepath.IsAbs(p) || strings.Contains(p, string(filepath.Separator))
}

func nilScopeMap(n Node) {
switch n := n.(type) {
case *Map:
Expand Down
48 changes: 47 additions & 1 deletion d2ir/import_test.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
package d2ir_test

import (
"os"
"path/filepath"
"strings"
"testing"

"oss.terrastruct.com/util-go/assert"

"oss.terrastruct.com/d2/d2ir"
"oss.terrastruct.com/d2/d2parser"
)

func testCompileImports(t *testing.T) {
Expand Down Expand Up @@ -124,6 +128,24 @@ label: meow`,
assertQuery(t, m, 0, 0, "wowa", "x")
},
},
{
name: "os/absolute_path",
run: func(t testing.TB) {
dir := t.TempDir()
indexPath := filepath.Join(dir, "index.d2")
err := os.WriteFile(indexPath, []byte("x: @x"), 0600)
assert.Success(t, err)
err = os.WriteFile(filepath.Join(dir, "x.d2"), []byte("shape: circle\nlabel: meow"), 0600)
assert.Success(t, err)

m, err := compileFile(t, indexPath)
assert.Success(t, err)
assertQuery(t, m, 3, 0, nil, "")
assertQuery(t, m, 2, 0, nil, "x")
assertQuery(t, m, 0, 0, "circle", "x.shape")
assertQuery(t, m, 0, 0, "meow", "x.label")
},
},
{
name: "nested/spread",
run: func(t testing.TB) {
Expand Down Expand Up @@ -240,7 +262,16 @@ label: meow`,
_, err := compileFS(t, "index.d2", map[string]string{
"index.d2": "...@x.d2",
})
assert.ErrorString(t, err, `index.d2:1:1: failed to import "x.d2": open x.d2: no such file or directory`)
assert.Error(t, err)
errText := err.Error()
if !strings.HasPrefix(errText, `index.d2:1:1: failed to import "x.d2": open x.d2: `) {
t.Fatalf("unexpected import error: %v", err)
}
if !strings.Contains(errText, "no such file or directory") &&
!strings.Contains(errText, "The system cannot find the file specified") &&
!strings.Contains(errText, "file does not exist") {
t.Fatalf("unexpected import error: %v", err)
}
},
},
{
Expand Down Expand Up @@ -293,3 +324,18 @@ x.d2:1:7: connection missing source`)
runa(t, tca)
})
}

func compileFile(t testing.TB, filePath string) (*d2ir.Map, error) {
t.Helper()

b, err := os.ReadFile(filePath)
if err != nil {
return nil, err
}
ast, err := d2parser.Parse(filePath, strings.NewReader(string(b)), nil)
if err != nil {
return nil, err
}
m, _, err := d2ir.Compile(ast, nil)
return m, err
}
Loading