Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add imports #1371

Merged
merged 27 commits into from
Jun 7, 2023
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
89e1e32
d2parser: Add imports support
nhooyr May 24, 2023
c325e94
d2parser: Allow .d2 extension on quoted import strings
nhooyr May 30, 2023
f61409c
d2parser: Allow passing in ParseError for nicer error messages in imp…
nhooyr Jun 5, 2023
2fcc9ed
d2ir: Compile imports
nhooyr Jun 4, 2023
9f0c24f
fmt
nhooyr Jun 5, 2023
42706f8
d2ir: More import tests
nhooyr Jun 6, 2023
ad21f1f
d2ir: Add more import error tests
nhooyr Jun 6, 2023
111759f
d2cli: Pass path for imports correctly
nhooyr Jun 6, 2023
f819e53
e2etests-cli: Add import test
nhooyr Jun 6, 2023
10c6d2a
d2parser: Fix bad import key panic
nhooyr Jun 6, 2023
b4d5bc0
d2parser: Error on leading @ in non import key strings
nhooyr Jun 6, 2023
bc8f180
d2parser: Error on unquoted strings starting with ...@
nhooyr Jun 6, 2023
0300b81
d2parser: Strip leading ./ from imports
nhooyr Jun 6, 2023
de0a863
d2: Fix nested import spread ghost container bug
nhooyr Jun 6, 2023
35bf5d2
d2parser: Fix non file map range not including last brace
nhooyr Jun 6, 2023
a5d3cc1
d2parser: Parse and format @../file imports correctly
nhooyr Jun 6, 2023
7f04501
d2ir: Handle imported scenarios/steps/links
nhooyr Jun 6, 2023
4701848
d2ir: Fix pushImportStack
nhooyr Jun 6, 2023
d296ba0
add failing test
alixander Jun 7, 2023
7d9f8b6
d2ir: Update links after import
nhooyr Jun 7, 2023
42cf8be
d2ir: Update cyclic import test
nhooyr Jun 7, 2023
8857f46
add failing test
alixander Jun 7, 2023
986f069
d2ir: Fix link paths to be unquoted strings
nhooyr Jun 7, 2023
424f671
add test exercising steps/scenarios
alixander Jun 7, 2023
538812f
Merge branch 'master' into imports
alixander Jun 7, 2023
686198e
changelog
alixander Jun 7, 2023
0bd11c4
fix test race condition
alixander Jun 7, 2023
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
53 changes: 51 additions & 2 deletions d2ast/d2ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ var _ Node = &DoubleQuotedString{}
var _ Node = &SingleQuotedString{}
var _ Node = &BlockString{}
var _ Node = &Substitution{}
var _ Node = &Import{}

var _ Node = &Array{}
var _ Node = &Map{}
Expand Down Expand Up @@ -277,6 +278,7 @@ var _ MapNode = &Comment{}
var _ MapNode = &BlockComment{}
var _ MapNode = &Key{}
var _ MapNode = &Substitution{}
var _ MapNode = &Import{}

// ArrayNode is implemented by nodes that may be children of Arrays.
type ArrayNode interface {
Expand All @@ -288,6 +290,7 @@ type ArrayNode interface {
var _ ArrayNode = &Comment{}
var _ ArrayNode = &BlockComment{}
var _ ArrayNode = &Substitution{}
var _ ArrayNode = &Import{}

// Value is implemented by nodes that may be values of a key.
type Value interface {
Expand Down Expand Up @@ -334,6 +337,7 @@ func (s *DoubleQuotedString) node() {}
func (s *SingleQuotedString) node() {}
func (s *BlockString) node() {}
func (s *Substitution) node() {}
func (i *Import) node() {}
func (a *Array) node() {}
func (m *Map) node() {}
func (k *Key) node() {}
Expand All @@ -351,6 +355,7 @@ func (s *DoubleQuotedString) Type() string { return "double quoted string" }
func (s *SingleQuotedString) Type() string { return "single quoted string" }
func (s *BlockString) Type() string { return s.Tag + " block string" }
func (s *Substitution) Type() string { return "substitution" }
func (i *Import) Type() string { return "import" }
func (a *Array) Type() string { return "array" }
func (m *Map) Type() string { return "map" }
func (k *Key) Type() string { return "map key" }
Expand All @@ -368,6 +373,7 @@ func (s *DoubleQuotedString) GetRange() Range { return s.Range }
func (s *SingleQuotedString) GetRange() Range { return s.Range }
func (s *BlockString) GetRange() Range { return s.Range }
func (s *Substitution) GetRange() Range { return s.Range }
func (i *Import) GetRange() Range { return i.Range }
func (a *Array) GetRange() Range { return a.Range }
func (m *Map) GetRange() Range { return m.Range }
func (k *Key) GetRange() Range { return k.Range }
Expand All @@ -379,6 +385,7 @@ func (c *Comment) mapNode() {}
func (c *BlockComment) mapNode() {}
func (k *Key) mapNode() {}
func (s *Substitution) mapNode() {}
func (i *Import) mapNode() {}

func (c *Comment) arrayNode() {}
func (c *BlockComment) arrayNode() {}
Expand All @@ -390,6 +397,7 @@ func (s *DoubleQuotedString) arrayNode() {}
func (s *SingleQuotedString) arrayNode() {}
func (s *BlockString) arrayNode() {}
func (s *Substitution) arrayNode() {}
func (i *Import) arrayNode() {}
func (a *Array) arrayNode() {}
func (m *Map) arrayNode() {}

Expand All @@ -402,6 +410,7 @@ func (s *SingleQuotedString) value() {}
func (s *BlockString) value() {}
func (a *Array) value() {}
func (m *Map) value() {}
func (i *Import) value() {}

func (n *Null) scalar() {}
func (b *Boolean) scalar() {}
Expand Down Expand Up @@ -722,11 +731,19 @@ type Substitution struct {
Path []*StringBox `json:"path"`
}

type Import struct {
Range Range `json:"range"`

Spread bool `json:"spread"`
Path []*StringBox `json:"path"`
}

// MapNodeBox is used to box MapNode for JSON persistence.
type MapNodeBox struct {
Comment *Comment `json:"comment,omitempty"`
BlockComment *BlockComment `json:"block_comment,omitempty"`
Substitution *Substitution `json:"substitution,omitempty"`
Import *Import `json:"import,omitempty"`
MapKey *Key `json:"map_key,omitempty"`
}

Expand All @@ -739,6 +756,8 @@ func MakeMapNodeBox(n MapNode) MapNodeBox {
box.BlockComment = n
case *Substitution:
box.Substitution = n
case *Import:
box.Import = n
case *Key:
box.MapKey = n
}
Expand All @@ -753,6 +772,8 @@ func (mb MapNodeBox) Unbox() MapNode {
return mb.BlockComment
case mb.Substitution != nil:
return mb.Substitution
case mb.Import != nil:
return mb.Import
case mb.MapKey != nil:
return mb.MapKey
default:
Expand All @@ -765,6 +786,7 @@ type ArrayNodeBox struct {
Comment *Comment `json:"comment,omitempty"`
BlockComment *BlockComment `json:"block_comment,omitempty"`
Substitution *Substitution `json:"substitution,omitempty"`
Import *Import `json:"import,omitempty"`
Null *Null `json:"null,omitempty"`
Boolean *Boolean `json:"boolean,omitempty"`
Number *Number `json:"number,omitempty"`
Expand All @@ -785,6 +807,8 @@ func MakeArrayNodeBox(an ArrayNode) ArrayNodeBox {
ab.BlockComment = an
case *Substitution:
ab.Substitution = an
case *Import:
ab.Import = an
case *Null:
ab.Null = an
case *Boolean:
Expand Down Expand Up @@ -815,6 +839,8 @@ func (ab ArrayNodeBox) Unbox() ArrayNode {
return ab.BlockComment
case ab.Substitution != nil:
return ab.Substitution
case ab.Import != nil:
return ab.Import
case ab.Null != nil:
return ab.Null
case ab.Boolean != nil:
Expand Down Expand Up @@ -849,6 +875,7 @@ type ValueBox struct {
BlockString *BlockString `json:"block_string,omitempty"`
Array *Array `json:"array,omitempty"`
Map *Map `json:"map,omitempty"`
Import *Import `json:"import,omitempty"`
}

func (vb ValueBox) Unbox() Value {
Expand All @@ -871,6 +898,8 @@ func (vb ValueBox) Unbox() Value {
return vb.Array
case vb.Map != nil:
return vb.Map
case vb.Import != nil:
return vb.Import
default:
return nil
}
Expand All @@ -897,6 +926,8 @@ func MakeValueBox(v Value) ValueBox {
vb.Array = v
case *Map:
vb.Map = v
case *Import:
vb.Import = v
}
return vb
}
Expand Down Expand Up @@ -990,8 +1021,8 @@ type InterpolationBox struct {
// & is only special if it begins a key.
// - is only special if followed by another - in a key.
// ' " and | are only special if they begin an unquoted key or value.
var UnquotedKeySpecials = string([]rune{'#', ';', '\n', '\\', '{', '}', '[', ']', '\'', '"', '|', ':', '.', '-', '<', '>', '*', '&', '(', ')'})
var UnquotedValueSpecials = string([]rune{'#', ';', '\n', '\\', '{', '}', '[', ']', '\'', '"', '|', '$'})
var UnquotedKeySpecials = string([]rune{'#', ';', '\n', '\\', '{', '}', '[', ']', '\'', '"', '|', ':', '.', '-', '<', '>', '*', '&', '(', ')', '@'})
var UnquotedValueSpecials = string([]rune{'#', ';', '\n', '\\', '{', '}', '[', ']', '\'', '"', '|', '$', '@'})

// RawString returns s in a AST String node that can format s in the most aesthetically
// pleasing way.
Expand Down Expand Up @@ -1040,8 +1071,26 @@ func RawString(s string, inKey bool) String {
return FlatUnquotedString(s)
}

func RawStringBox(s string, inKey bool) *StringBox {
return MakeValueBox(RawString(s, inKey)).StringBox()
}

func hasSurroundingWhitespace(s string) bool {
r, _ := utf8.DecodeRuneInString(s)
r2, _ := utf8.DecodeLastRuneInString(s)
return unicode.IsSpace(r) || unicode.IsSpace(r2)
}

func (s *Substitution) IDA() (ida []string) {
for _, el := range s.Path {
ida = append(ida, el.Unbox().ScalarString())
}
return ida
}

func (i *Import) IDA() (ida []string) {
for _, el := range i.Path[1:] {
ida = append(ida, el.Unbox().ScalarString())
}
return ida
}
1 change: 1 addition & 0 deletions d2cli/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,7 @@ func compile(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, rende
Ruler: ruler,
ThemeID: renderOpts.ThemeID,
FontFamily: fontFamily,
InputPath: inputPath,
}
if renderOpts.Sketch {
opts.FontFamily = go2.Pointer(d2fonts.HandDrawn)
Expand Down
23 changes: 18 additions & 5 deletions d2compiler/compile.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import (
"encoding/xml"
"fmt"
"io"
"io/fs"
"net/url"
"os"
"strconv"
"strings"

Expand All @@ -21,21 +23,30 @@ import (

type CompileOptions struct {
UTF16 bool
// FS is the file system used for resolving imports in the d2 text.
// It should correspond to the root path.
FS fs.FS
}

func Compile(path string, r io.RuneReader, opts *CompileOptions) (*d2graph.Graph, error) {
func Compile(p string, r io.RuneReader, opts *CompileOptions) (*d2graph.Graph, error) {
if opts == nil {
opts = &CompileOptions{}
}
if opts.FS == nil {
opts.FS = os.DirFS("/")
}
nhooyr marked this conversation as resolved.
Show resolved Hide resolved

ast, err := d2parser.Parse(path, r, &d2parser.ParseOptions{
ast, err := d2parser.Parse(p, r, &d2parser.ParseOptions{
UTF16: opts.UTF16,
})
if err != nil {
return nil, err
}

ir, err := d2ir.Compile(ast)
ir, err := d2ir.Compile(ast, &d2ir.CompileOptions{
UTF16: opts.UTF16,
FS: opts.FS,
})
if err != nil {
return nil, err
}
Expand All @@ -50,7 +61,9 @@ func Compile(path string, r io.RuneReader, opts *CompileOptions) (*d2graph.Graph
}

func compileIR(ast *d2ast.Map, m *d2ir.Map) (*d2graph.Graph, error) {
c := &compiler{}
c := &compiler{
err: &d2parser.ParseError{},
nhooyr marked this conversation as resolved.
Show resolved Hide resolved
}

g := d2graph.NewGraph()
g.AST = ast
Expand Down Expand Up @@ -116,7 +129,7 @@ func (c *compiler) compileBoardsField(g *d2graph.Graph, ir *d2ir.Map, fieldName
}

type compiler struct {
err d2parser.ParseError
err *d2parser.ParseError
}

func (c *compiler) errorf(n d2ast.Node, f string, v ...interface{}) {
Expand Down
10 changes: 10 additions & 0 deletions d2format/format.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ func (p *printer) node(n d2ast.Node) {
p.blockString(n)
case *d2ast.Substitution:
p.substitution(n)
case *d2ast.Import:
p._import(n)
case *d2ast.Array:
p.array(n)
case *d2ast.Map:
Expand Down Expand Up @@ -203,6 +205,14 @@ func (p *printer) substitution(s *d2ast.Substitution) {
p.sb.WriteByte('}')
}

func (p *printer) _import(i *d2ast.Import) {
alixander marked this conversation as resolved.
Show resolved Hide resolved
if i.Spread {
p.sb.WriteString("...")
}
p.sb.WriteString("@")
p.path(i.Path)
}

func (p *printer) array(a *d2ast.Array) {
p.sb.WriteByte('[')
if !a.Range.OneLine() {
Expand Down
16 changes: 16 additions & 0 deletions d2format/format_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -617,6 +617,22 @@ y
x <= y
`,
exp: `x <- = y
`,
},
{
name: "import/1",
in: `
x: @file.d2
`,
exp: `x: @file
`,
},
{
name: "import/2",
in: `
x: @file."d2"
nhooyr marked this conversation as resolved.
Show resolved Hide resolved
`,
exp: `x: @file."d2"
`,
},
}
Expand Down
Loading