...

Source file src/oss.terrastruct.com/d2/d2ir/import.go

Documentation: oss.terrastruct.com/d2/d2ir

     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  		// Imports are always relative to the importing file.
    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  // Returns either *Map or *Field.
    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