From bae5705674d721c84c32ca7576016e2a7d91f4cc Mon Sep 17 00:00:00 2001 From: Alexander Wang Date: Fri, 4 Aug 2023 20:16:25 -0700 Subject: [PATCH] setup imports with tests --- d2compiler/compile.go | 1 + d2graph/d2graph.go | 2 + d2oracle/edit.go | 48 ++-- d2oracle/edit_test.go | 90 ++++++- testdata/d2oracle/TestSet/import/1.exp.json | 262 ++++++++++++++++++++ 5 files changed, 374 insertions(+), 29 deletions(-) create mode 100644 testdata/d2oracle/TestSet/import/1.exp.json diff --git a/d2compiler/compile.go b/d2compiler/compile.go index f777ff3dd..841e381a0 100644 --- a/d2compiler/compile.go +++ b/d2compiler/compile.go @@ -51,6 +51,7 @@ func Compile(p string, r io.Reader, opts *CompileOptions) (*d2graph.Graph, *d2ta if err != nil { return nil, nil, err } + g.FS = opts.FS g.SortObjectsByAST() g.SortEdgesByAST() return g, compileConfig(ir), nil diff --git a/d2graph/d2graph.go b/d2graph/d2graph.go index f7aa7ec2b..8379d184b 100644 --- a/d2graph/d2graph.go +++ b/d2graph/d2graph.go @@ -5,6 +5,7 @@ import ( "context" "errors" "fmt" + "io/fs" "math" "net/url" "sort" @@ -36,6 +37,7 @@ const DEFAULT_SHAPE_SIZE = 100. const MIN_SHAPE_SIZE = 5 type Graph struct { + FS fs.FS `json:"-"` Parent *Graph `json:"-"` Name string `json:"name"` // IsFolderOnly indicates a board or scenario itself makes no modifications from its diff --git a/d2oracle/edit.go b/d2oracle/edit.go index 01f54d391..b5b9f602e 100644 --- a/d2oracle/edit.go +++ b/d2oracle/edit.go @@ -65,7 +65,7 @@ func Create(g *d2graph.Graph, boardPath []string, key string) (_ *d2graph.Graph, if err != nil { return nil, "", err } - g, err = recompile(g.AST) + g, err = recompile(g) if err != nil { return nil, "", err } @@ -112,7 +112,7 @@ func Set(g *d2graph.Graph, boardPath []string, key string, tag, value *string) ( } } - return recompile(g.AST) + return recompile(g) } func ReconnectEdge(g *d2graph.Graph, boardPath []string, edgeKey string, srcKey, dstKey *string) (_ *d2graph.Graph, err error) { @@ -271,7 +271,7 @@ func ReconnectEdge(g *d2graph.Graph, boardPath []string, edgeKey string, srcKey, } } - return recompile(g.AST) + return recompile(g) } func pathFromScopeKey(g *d2graph.Graph, key *d2ast.Key, scopeak []string) ([]*d2ast.StringBox, error) { @@ -303,13 +303,15 @@ func pathFromScopeObj(g *d2graph.Graph, key *d2ast.Key, fromScope *d2graph.Objec return pathFromScopeKey(g, key, scopeak) } -func recompile(ast *d2ast.Map) (*d2graph.Graph, error) { - s := d2format.Format(ast) - g, _, err := d2compiler.Compile(ast.Range.Path, strings.NewReader(s), nil) +func recompile(g *d2graph.Graph) (*d2graph.Graph, error) { + s := d2format.Format(g.AST) + g2, _, err := d2compiler.Compile(g.AST.Range.Path, strings.NewReader(s), &d2compiler.CompileOptions{ + FS: g.FS, + }) if err != nil { return nil, fmt.Errorf("failed to recompile:\n%s\n%w", s, err) } - return g, nil + return g2, nil } // TODO merge flat styles @@ -451,7 +453,9 @@ func _set(g *d2graph.Graph, baseAST *d2ast.Map, key string, tag, value *string) return nil } } - ir, err := d2ir.Compile(g.AST, nil) + ir, err := d2ir.Compile(g.AST, &d2ir.CompileOptions{ + FS: g.FS, + }) if err != nil { return err } @@ -897,12 +901,12 @@ func Delete(g *d2graph.Graph, boardPath []string, key string) (_ *d2graph.Graph, if !replaced { return nil, fmt.Errorf("board %v AST not found", boardPath) } - return recompile(g.AST) + return recompile(g) } - return recompile(boardG.AST) + return recompile(boardG) } - prevG, _ := recompile(boardG.AST) + prevG, _ := recompile(boardG) boardG, err = renameConflictsToParent(boardG, mk.Key) if err != nil { @@ -939,10 +943,10 @@ func Delete(g *d2graph.Graph, boardPath []string, key string) (_ *d2graph.Graph, if !replaced { return nil, fmt.Errorf("board %v AST not found", boardPath) } - return recompile(g.AST) + return recompile(g) } - return recompile(boardG.AST) + return recompile(boardG) } func bumpChildrenUnderscores(m *d2ast.Map) { @@ -1182,7 +1186,7 @@ func deleteReserved(g *d2graph.Graph, mk *d2ast.Key) (*d2graph.Graph, error) { if err := deleteEdgeField(g, e, targetKey.Path[len(targetKey.Path)-1].Unbox().ScalarString()); err != nil { return nil, err } - return recompile(g.AST) + return recompile(g) } isStyleKey := false @@ -1221,7 +1225,7 @@ func deleteReserved(g *d2graph.Graph, mk *d2ast.Key) (*d2graph.Graph, error) { } } - return recompile(g.AST) + return recompile(g) } func deleteMapField(m *d2ast.Map, field string) { @@ -1285,7 +1289,7 @@ func deleteObjField(g *d2graph.Graph, obj *d2graph.Object, field string) error { copy(tmpNodes, ref.Scope.Nodes) // If I delete this, will the object still exist? deleteFromMap(ref.Scope, ref.MapKey) - g2, err := recompile(g.AST) + g2, err := recompile(g) if err != nil { return err } @@ -1605,10 +1609,10 @@ func move(g *d2graph.Graph, boardPath []string, key, newKey string, includeDesce ref.MapKey.Edges[ref.MapKeyEdgeIndex].SrcArrow = mk2.Edges[0].SrcArrow ref.MapKey.Edges[ref.MapKeyEdgeIndex].DstArrow = mk2.Edges[0].DstArrow } - return recompile(g.AST) + return recompile(g) } - prevG, _ := recompile(boardG.AST) + prevG, _ := recompile(boardG) ak := d2graph.Key(mk.Key) ak2 := d2graph.Key(mk2.Key) @@ -2026,10 +2030,10 @@ func move(g *d2graph.Graph, boardPath []string, key, newKey string, includeDesce if !replaced { return nil, fmt.Errorf("board %v AST not found", boardPath) } - return recompile(g.AST) + return recompile(g) } - return recompile(boardG.AST) + return recompile(boardG) } // filterReserved takes a Value and splits it into 2 @@ -2141,7 +2145,7 @@ func updateNear(prevG, g *d2graph.Graph, from, to *string, includeDescendants bo if err != nil { return err } - tmpG, _ := recompile(prevG.AST) + tmpG, _ := recompile(prevG) appendMapKey(tmpG.AST, valueMK) if to == nil { deltas, err := DeleteIDDeltas(tmpG, nil, *from) @@ -2186,7 +2190,7 @@ func updateNear(prevG, g *d2graph.Graph, from, to *string, includeDescendants bo if err != nil { return err } - tmpG, _ := recompile(prevG.AST) + tmpG, _ := recompile(prevG) appendMapKey(tmpG.AST, valueMK) if to == nil { deltas, err := DeleteIDDeltas(tmpG, nil, *from) diff --git a/d2oracle/edit_test.go b/d2oracle/edit_test.go index 424deec72..cc87d8fdd 100644 --- a/d2oracle/edit_test.go +++ b/d2oracle/edit_test.go @@ -2,6 +2,9 @@ package d2oracle_test import ( "fmt" + "io" + "io/fs" + "os" "path/filepath" "strconv" "strings" @@ -732,6 +735,7 @@ func TestSet(t *testing.T) { boardPath []string name string text string + fsTexts map[string]string key string tag *string value *string @@ -1984,6 +1988,29 @@ scenarios: { c -> d } } +`, + }, + { + name: "import/1", + + text: `x: { + ...@meow.x + y +} +`, + fsTexts: map[string]string{ + "meow": `x: { + style.fill: blue +} +`, + }, + key: `x.style.stroke`, + value: go2.Pointer(`red`), + exp: `x: { + ...@meow.x + y + style.stroke: red +} `, }, } @@ -1994,7 +2021,8 @@ scenarios: { t.Parallel() et := editTest{ - text: tc.text, + text: tc.text, + fsTexts: tc.fsTexts, testFunc: func(g *d2graph.Graph) (*d2graph.Graph, error) { return d2oracle.Set(g, tc.boardPath, tc.key, tc.tag, tc.value) }, @@ -2412,6 +2440,7 @@ func TestRename(t *testing.T) { boardPath []string text string + fsTexts map[string]string key string newName string @@ -2922,7 +2951,8 @@ scenarios: { t.Parallel() et := editTest{ - text: tc.text, + text: tc.text, + fsTexts: tc.fsTexts, testFunc: func(g *d2graph.Graph) (*d2graph.Graph, error) { objectsBefore := len(g.Objects) var err error @@ -2956,6 +2986,7 @@ func TestMove(t *testing.T) { boardPath []string text string + fsTexts map[string]string key string newKey string includeDescendants bool @@ -5190,7 +5221,8 @@ scenarios: { t.Parallel() et := editTest{ - text: tc.text, + text: tc.text, + fsTexts: tc.fsTexts, testFunc: func(g *d2graph.Graph) (*d2graph.Graph, error) { objectsBefore := len(g.Objects) var err error @@ -5221,8 +5253,9 @@ func TestDelete(t *testing.T) { name string boardPath []string - text string - key string + text string + fsTexts map[string]string + key string expErr string exp string @@ -6977,7 +7010,8 @@ scenarios: { t.Parallel() et := editTest{ - text: tc.text, + text: tc.text, + fsTexts: tc.fsTexts, testFunc: func(g *d2graph.Graph) (*d2graph.Graph, error) { return d2oracle.Delete(g, tc.boardPath, tc.key) }, @@ -6993,6 +7027,7 @@ scenarios: { type editTest struct { text string + fsTexts map[string]string testFunc func(*d2graph.Graph) (*d2graph.Graph, error) exp string @@ -7002,7 +7037,13 @@ type editTest struct { func (tc editTest) run(t *testing.T) { d2Path := fmt.Sprintf("d2/testdata/d2oracle/%v.d2", t.Name()) - g, _, err := d2compiler.Compile(d2Path, strings.NewReader(tc.text), nil) + tfs := testFS(make(map[string]*testF)) + for name, text := range tc.fsTexts { + tfs[name] = &testF{content: text} + } + g, _, err := d2compiler.Compile(d2Path, strings.NewReader(tc.text), &d2compiler.CompileOptions{ + FS: tfs, + }) if err != nil { t.Fatal(err) } @@ -8360,3 +8401,38 @@ scenarios: { }) } } + +type testF struct { + content string + readIndex int +} + +func (tf *testF) Close() error { + return nil +} + +func (tf *testF) Read(p []byte) (int, error) { + data := []byte(tf.content) + if tf.readIndex >= len(data) { + tf.readIndex = 0 + return 0, io.EOF + } + readBytes := copy(p, data[tf.readIndex:]) + tf.readIndex += readBytes + return readBytes, nil +} + +func (tf *testF) Stat() (os.FileInfo, error) { + return nil, nil +} + +type testFS map[string]*testF + +func (tfs testFS) Open(name string) (fs.File, error) { + for k := range tfs { + if strings.HasSuffix(name[:len(name)-3], k) { + return tfs[k], nil + } + } + return nil, fs.ErrNotExist +} diff --git a/testdata/d2oracle/TestSet/import/1.exp.json b/testdata/d2oracle/TestSet/import/1.exp.json new file mode 100644 index 000000000..6b6107050 --- /dev/null +++ b/testdata/d2oracle/TestSet/import/1.exp.json @@ -0,0 +1,262 @@ +{ + "graph": { + "name": "", + "isFolderOnly": false, + "ast": { + "range": "d2/testdata/d2oracle/TestSet/import/1.d2,0:0:0-5:0:44", + "nodes": [ + { + "map_key": { + "range": "d2/testdata/d2oracle/TestSet/import/1.d2,0:0:0-4:1:43", + "key": { + "range": "d2/testdata/d2oracle/TestSet/import/1.d2,0:0:0-0:1:1", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2oracle/TestSet/import/1.d2,0:0:0-0:1:1", + "value": [ + { + "string": "x", + "raw_string": "x" + } + ] + } + } + ] + }, + "primary": {}, + "value": { + "map": { + "range": "d2/testdata/d2oracle/TestSet/import/1.d2,0:3:3-4:1:43", + "nodes": [ + { + "import": { + "range": "d2/testdata/d2oracle/TestSet/import/1.d2,1:2:7-1:12:17", + "spread": true, + "pre": "", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2oracle/TestSet/import/1.d2,1:6:11-1:10:15", + "value": [ + { + "string": "meow", + "raw_string": "meow" + } + ] + } + }, + { + "unquoted_string": { + "range": "d2/testdata/d2oracle/TestSet/import/1.d2,1:11:16-1:12:17", + "value": [ + { + "string": "x", + "raw_string": "x" + } + ] + } + } + ] + } + }, + { + "map_key": { + "range": "d2/testdata/d2oracle/TestSet/import/1.d2,2:2:20-2:3:21", + "key": { + "range": "d2/testdata/d2oracle/TestSet/import/1.d2,2:2:20-2:3:21", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2oracle/TestSet/import/1.d2,2:2:20-2:3:21", + "value": [ + { + "string": "y", + "raw_string": "y" + } + ] + } + } + ] + }, + "primary": {}, + "value": {} + } + }, + { + "map_key": { + "range": "d2/testdata/d2oracle/TestSet/import/1.d2,3:2:24-3:19:41", + "key": { + "range": "d2/testdata/d2oracle/TestSet/import/1.d2,3:2:24-3:14:36", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2oracle/TestSet/import/1.d2,3:2:24-3:7:29", + "value": [ + { + "string": "style", + "raw_string": "style" + } + ] + } + }, + { + "unquoted_string": { + "range": "d2/testdata/d2oracle/TestSet/import/1.d2,3:8:30-3:14:36", + "value": [ + { + "string": "stroke", + "raw_string": "stroke" + } + ] + } + } + ] + }, + "primary": {}, + "value": { + "unquoted_string": { + "range": "d2/testdata/d2oracle/TestSet/import/1.d2,3:16:38-3:19:41", + "value": [ + { + "string": "red", + "raw_string": "red" + } + ] + } + } + } + } + ] + } + } + } + } + ] + }, + "root": { + "id": "", + "id_val": "", + "attributes": { + "label": { + "value": "" + }, + "labelDimensions": { + "width": 0, + "height": 0 + }, + "style": {}, + "near_key": null, + "shape": { + "value": "" + }, + "direction": { + "value": "" + }, + "constraint": null + }, + "zIndex": 0 + }, + "edges": null, + "objects": [ + { + "id": "x", + "id_val": "x", + "references": [ + { + "key": { + "range": "d2/testdata/d2oracle/TestSet/import/1.d2,0:0:0-0:1:1", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2oracle/TestSet/import/1.d2,0:0:0-0:1:1", + "value": [ + { + "string": "x", + "raw_string": "x" + } + ] + } + } + ] + }, + "key_path_index": 0, + "map_key_edge_index": -1 + } + ], + "attributes": { + "label": { + "value": "x" + }, + "labelDimensions": { + "width": 0, + "height": 0 + }, + "style": { + "stroke": { + "value": "red" + }, + "fill": { + "value": "blue" + } + }, + "near_key": null, + "shape": { + "value": "rectangle" + }, + "direction": { + "value": "" + }, + "constraint": null + }, + "zIndex": 0 + }, + { + "id": "y", + "id_val": "y", + "references": [ + { + "key": { + "range": "d2/testdata/d2oracle/TestSet/import/1.d2,2:2:20-2:3:21", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2oracle/TestSet/import/1.d2,2:2:20-2:3:21", + "value": [ + { + "string": "y", + "raw_string": "y" + } + ] + } + } + ] + }, + "key_path_index": 0, + "map_key_edge_index": -1 + } + ], + "attributes": { + "label": { + "value": "y" + }, + "labelDimensions": { + "width": 0, + "height": 0 + }, + "style": {}, + "near_key": null, + "shape": { + "value": "rectangle" + }, + "direction": { + "value": "" + }, + "constraint": null + }, + "zIndex": 0 + } + ] + }, + "err": "" +}