1 package d2ir
2
3 import (
4 "io/fs"
5 "os"
6 "path"
7 "strings"
8
9 "oss.terrastruct.com/d2/d2ast"
10 "oss.terrastruct.com/d2/d2parser"
11 )
12
13 func (c *compiler) pushImportStack(imp *d2ast.Import) (string, bool) {
14 impPath := imp.PathWithPre()
15 if impPath == "" && imp.Range.Path != "" {
16 c.errorf(imp, "imports must specify a path to import")
17 return "", false
18 }
19 if len(c.importStack) > 0 {
20 if path.IsAbs(impPath) {
21 c.errorf(imp, "import paths must be relative")
22 return "", false
23 }
24
25 if path.Ext(impPath) != ".d2" {
26 impPath += ".d2"
27 }
28
29
30 impPath = path.Join(path.Dir(c.importStack[len(c.importStack)-1]), impPath)
31 }
32
33 for i, p := range c.importStack {
34 if impPath == p {
35 c.errorf(imp, "detected cyclic import chain: %s", formatCyclicChain(c.importStack[i:]))
36 return "", false
37 }
38 }
39
40 c.importStack = append(c.importStack, impPath)
41 return impPath, true
42 }
43
44 func (c *compiler) popImportStack() {
45 c.importStack = c.importStack[:len(c.importStack)-1]
46 }
47
48 func formatCyclicChain(cyclicChain []string) string {
49 var b strings.Builder
50 for _, p := range cyclicChain {
51 b.WriteString(p)
52 b.WriteString(" -> ")
53 }
54 b.WriteString(cyclicChain[0])
55 return b.String()
56 }
57
58
59 func (c *compiler) _import(imp *d2ast.Import) (Node, bool) {
60 ir, ok := c.__import(imp)
61 if !ok {
62 return nil, false
63 }
64 nilScopeMap(ir)
65 if len(imp.IDA()) > 0 {
66 f := ir.GetField(imp.IDA()...)
67 if f == nil {
68 c.errorf(imp, "import key %q doesn't exist inside import", imp.IDA())
69 return nil, false
70 }
71 return f, true
72 }
73 return ir, true
74 }
75
76 func (c *compiler) __import(imp *d2ast.Import) (*Map, bool) {
77 impPath, ok := c.pushImportStack(imp)
78 if !ok {
79 return nil, false
80 }
81 defer c.popImportStack()
82
83 ir, ok := c.importCache[impPath]
84 if ok {
85 return ir, true
86 }
87
88 var f fs.File
89 var err error
90 if c.fs == nil {
91 f, err = os.Open(impPath)
92 } else {
93 f, err = c.fs.Open(impPath)
94 }
95 if err != nil {
96 c.errorf(imp, "failed to import %q: %v", impPath, err)
97 return nil, false
98 }
99 defer f.Close()
100
101 ast, err := d2parser.Parse(impPath, f, &d2parser.ParseOptions{
102 UTF16Pos: c.utf16Pos,
103 ParseError: c.err,
104 })
105 if err != nil {
106 return nil, false
107 }
108
109 ir = &Map{}
110 ir.initRoot()
111 ir.parent.(*Field).References[0].Context_.Scope = ast
112
113 c.compileMap(ir, ast, ast)
114
115 c.importCache[impPath] = ir
116
117 return ir, true
118 }
119
120 func nilScopeMap(n Node) {
121 switch n := n.(type) {
122 case *Map:
123 for _, f := range n.Fields {
124 nilScopeMap(f)
125 }
126 for _, e := range n.Edges {
127 nilScopeMap(e)
128 }
129 case *Edge:
130 for _, r := range n.References {
131 r.Context_.ScopeMap = nil
132 }
133 if n.Map() != nil {
134 nilScopeMap(n.Map())
135 }
136 case *Field:
137 for _, r := range n.References {
138 r.Context_.ScopeMap = nil
139 }
140 if n.Map() != nil {
141 nilScopeMap(n.Map())
142 }
143 }
144 }
145
View as plain text