@@ -16,6 +16,7 @@ package caddyfile
1616
1717import (
1818 "bytes"
19+ "fmt"
1920 "io/ioutil"
2021 "log"
2122 "os"
@@ -40,7 +41,13 @@ func Parse(filename string, input []byte) ([]ServerBlock, error) {
4041 if err != nil {
4142 return nil , err
4243 }
43- p := parser {Dispenser : NewDispenser (tokens )}
44+ p := parser {
45+ Dispenser : NewDispenser (tokens ),
46+ importGraph : importGraph {
47+ nodes : make (map [string ]bool ),
48+ edges : make (adjacency ),
49+ },
50+ }
4451 return p .parseAll ()
4552}
4653
@@ -110,6 +117,7 @@ type parser struct {
110117 eof bool // if we encounter a valid EOF in a hard place
111118 definedSnippets map [string ][]Token
112119 nesting int
120+ importGraph importGraph
113121}
114122
115123func (p * parser ) parseAll () ([]ServerBlock , error ) {
@@ -165,6 +173,15 @@ func (p *parser) begin() error {
165173 if err != nil {
166174 return err
167175 }
176+ // Just as we need to track which file the token comes from, we need to
177+ // keep track of which snippets do the tokens come from. This is helpful
178+ // in tracking import cycles across files/snippets by namespacing them. Without
179+ // this we end up with false-positives in cycle-detection.
180+ for k , v := range tokens {
181+ v .inSnippet = true
182+ v .snippetName = name
183+ tokens [k ] = v
184+ }
168185 p .definedSnippets [name ] = tokens
169186 // empty block keys so we don't save this block as a real server.
170187 p .block .Keys = nil
@@ -314,10 +331,15 @@ func (p *parser) doImport() error {
314331 tokensBefore := p .tokens [:p .cursor - 1 - len (args )]
315332 tokensAfter := p .tokens [p .cursor + 1 :]
316333 var importedTokens []Token
334+ var nodes []string
317335
318336 // first check snippets. That is a simple, non-recursive replacement
319337 if p .definedSnippets != nil && p .definedSnippets [importPattern ] != nil {
320338 importedTokens = p .definedSnippets [importPattern ]
339+ if len (importedTokens ) > 0 {
340+ // just grab the first one
341+ nodes = append (nodes , fmt .Sprintf ("%s:%s" , importedTokens [0 ].File , importedTokens [0 ].snippetName ))
342+ }
321343 } else {
322344 // make path relative to the file of the _token_ being processed rather
323345 // than current working directory (issue #867) and then use glob to get
@@ -353,14 +375,25 @@ func (p *parser) doImport() error {
353375 }
354376
355377 // collect all the imported tokens
356-
357378 for _ , importFile := range matches {
358379 newTokens , err := p .doSingleImport (importFile )
359380 if err != nil {
360381 return err
361382 }
362383 importedTokens = append (importedTokens , newTokens ... )
363384 }
385+ nodes = matches
386+ }
387+
388+ nodeName := p .File ()
389+ if p .Token ().inSnippet {
390+ nodeName += fmt .Sprintf (":%s" , p .Token ().snippetName )
391+ }
392+ p .importGraph .addNode (nodeName )
393+ p .importGraph .addNodes (nodes )
394+ if err := p .importGraph .addEdges (nodeName , nodes ); err != nil {
395+ p .importGraph .removeNodes (nodes )
396+ return err
364397 }
365398
366399 // copy the tokens so we don't overwrite p.definedSnippets
0 commit comments