summaryrefslogtreecommitdiff
path: root/caddyconfig/caddyfile/parse.go
diff options
context:
space:
mode:
authorTom Barrett <tom@tombarrett.xyz>2023-11-01 17:57:48 +0100
committerTom Barrett <tom@tombarrett.xyz>2023-11-01 18:11:33 +0100
commit240c3d1338415e5d82ef7ca0e52c4284be6441bd (patch)
tree4b0ee5d208c2cdffa78d65f1b0abe0ec85f15652 /caddyconfig/caddyfile/parse.go
parent73e78ab226f21e6c6c68961af88c4ab9c746f4f4 (diff)
parent0e204b730aa2b1fa0835336b1117eff8c420f713 (diff)
vbump to v2.7.5HEADcaddy-cgi
Diffstat (limited to 'caddyconfig/caddyfile/parse.go')
-rw-r--r--caddyconfig/caddyfile/parse.go148
1 files changed, 118 insertions, 30 deletions
diff --git a/caddyconfig/caddyfile/parse.go b/caddyconfig/caddyfile/parse.go
index edc86f2..65d6ee9 100644
--- a/caddyconfig/caddyfile/parse.go
+++ b/caddyconfig/caddyfile/parse.go
@@ -20,11 +20,11 @@ import (
"io"
"os"
"path/filepath"
- "strconv"
"strings"
- "github.com/caddyserver/caddy/v2"
"go.uber.org/zap"
+
+ "github.com/caddyserver/caddy/v2"
)
// Parse parses the input just enough to group tokens, in
@@ -149,7 +149,6 @@ func (p *parser) begin() error {
}
err := p.addresses()
-
if err != nil {
return err
}
@@ -160,6 +159,25 @@ func (p *parser) begin() error {
return nil
}
+ if ok, name := p.isNamedRoute(); ok {
+ // named routes only have one key, the route name
+ p.block.Keys = []string{name}
+ p.block.IsNamedRoute = true
+
+ // we just need a dummy leading token to ease parsing later
+ nameToken := p.Token()
+ nameToken.Text = name
+
+ // get all the tokens from the block, including the braces
+ tokens, err := p.blockTokens(true)
+ if err != nil {
+ return err
+ }
+ tokens = append([]Token{nameToken}, tokens...)
+ p.block.Segments = []Segment{tokens}
+ return nil
+ }
+
if ok, name := p.isSnippet(); ok {
if p.definedSnippets == nil {
p.definedSnippets = map[string][]Token{}
@@ -168,16 +186,15 @@ func (p *parser) begin() error {
return p.Errf("redeclaration of previously declared snippet %s", name)
}
// consume all tokens til matched close brace
- tokens, err := p.snippetTokens()
+ tokens, err := p.blockTokens(false)
if err != nil {
return err
}
// Just as we need to track which file the token comes from, we need to
- // keep track of which snippets do the tokens come from. This is helpful
- // in tracking import cycles across files/snippets by namespacing them. Without
- // this we end up with false-positives in cycle-detection.
+ // keep track of which snippet the token comes from. This is helpful
+ // in tracking import cycles across files/snippets by namespacing them.
+ // Without this, we end up with false-positives in cycle-detection.
for k, v := range tokens {
- v.inSnippet = true
v.snippetName = name
tokens[k] = v
}
@@ -198,7 +215,7 @@ func (p *parser) addresses() error {
// special case: import directive replaces tokens during parse-time
if tkn == "import" && p.isNewLine() {
- err := p.doImport()
+ err := p.doImport(0)
if err != nil {
return err
}
@@ -298,7 +315,7 @@ func (p *parser) directives() error {
// special case: import directive replaces tokens during parse-time
if p.Val() == "import" {
- err := p.doImport()
+ err := p.doImport(1)
if err != nil {
return err
}
@@ -324,7 +341,7 @@ func (p *parser) directives() error {
// is on the token before where the import directive was. In
// other words, call Next() to access the first token that was
// imported.
-func (p *parser) doImport() error {
+func (p *parser) doImport(nesting int) error {
// syntax checks
if !p.NextArg() {
return p.ArgErr()
@@ -337,11 +354,8 @@ func (p *parser) doImport() error {
// grab remaining args as placeholder replacements
args := p.RemainingArgs()
- // add args to the replacer
- repl := caddy.NewEmptyReplacer()
- for index, arg := range args {
- repl.Set("args."+strconv.Itoa(index), arg)
- }
+ // set up a replacer for non-variadic args replacement
+ repl := makeArgsReplacer(args)
// splice out the import directive and its arguments
// (2 tokens, plus the length of args)
@@ -417,7 +431,7 @@ func (p *parser) doImport() error {
}
nodeName := p.File()
- if p.Token().inSnippet {
+ if p.Token().snippetName != "" {
nodeName += fmt.Sprintf(":%s", p.Token().snippetName)
}
p.importGraph.addNode(nodeName)
@@ -428,13 +442,69 @@ func (p *parser) doImport() error {
}
// copy the tokens so we don't overwrite p.definedSnippets
- tokensCopy := make([]Token, len(importedTokens))
- copy(tokensCopy, importedTokens)
+ tokensCopy := make([]Token, 0, len(importedTokens))
+
+ var (
+ maybeSnippet bool
+ maybeSnippetId bool
+ index int
+ )
// run the argument replacer on the tokens
- for index, token := range tokensCopy {
- token.Text = repl.ReplaceKnown(token.Text, "")
- tokensCopy[index] = token
+ // golang for range slice return a copy of value
+ // similarly, append also copy value
+ for i, token := range importedTokens {
+ // update the token's imports to refer to import directive filename, line number and snippet name if there is one
+ if token.snippetName != "" {
+ token.imports = append(token.imports, fmt.Sprintf("%s:%d (import %s)", p.File(), p.Line(), token.snippetName))
+ } else {
+ token.imports = append(token.imports, fmt.Sprintf("%s:%d (import)", p.File(), p.Line()))
+ }
+
+ // naive way of determine snippets, as snippets definition can only follow name + block
+ // format, won't check for nesting correctness or any other error, that's what parser does.
+ if !maybeSnippet && nesting == 0 {
+ // first of the line
+ if i == 0 || isNextOnNewLine(tokensCopy[i-1], token) {
+ index = 0
+ } else {
+ index++
+ }
+
+ if index == 0 && len(token.Text) >= 3 && strings.HasPrefix(token.Text, "(") && strings.HasSuffix(token.Text, ")") {
+ maybeSnippetId = true
+ }
+ }
+
+ switch token.Text {
+ case "{":
+ nesting++
+ if index == 1 && maybeSnippetId && nesting == 1 {
+ maybeSnippet = true
+ maybeSnippetId = false
+ }
+ case "}":
+ nesting--
+ if nesting == 0 && maybeSnippet {
+ maybeSnippet = false
+ }
+ }
+
+ if maybeSnippet {
+ tokensCopy = append(tokensCopy, token)
+ continue
+ }
+
+ foundVariadic, startIndex, endIndex := parseVariadic(token, len(args))
+ if foundVariadic {
+ for _, arg := range args[startIndex:endIndex] {
+ token.Text = arg
+ tokensCopy = append(tokensCopy, token)
+ }
+ } else {
+ token.Text = repl.ReplaceKnown(token.Text, "")
+ tokensCopy = append(tokensCopy, token)
+ }
}
// splice the imported tokens in the place of the import statement
@@ -496,7 +566,6 @@ func (p *parser) doSingleImport(importFile string) ([]Token, error) {
// are loaded into the current server block for later use
// by directive setup functions.
func (p *parser) directive() error {
-
// a segment is a list of tokens associated with this directive
var segment Segment
@@ -509,6 +578,9 @@ func (p *parser) directive() error {
if !p.isNextOnNewLine() && p.Token().wasQuoted == 0 {
return p.Err("Unexpected next token after '{' on same line")
}
+ if p.isNewLine() {
+ return p.Err("Unexpected '{' on a new line; did you mean to place the '{' on the previous line?")
+ }
} else if p.Val() == "{}" {
if p.isNextOnNewLine() && p.Token().wasQuoted == 0 {
return p.Err("Unexpected '{}' at end of line")
@@ -521,7 +593,7 @@ func (p *parser) directive() error {
} else if p.Val() == "}" && p.nesting == 0 {
return p.Err("Unexpected '}' because no matching opening brace")
} else if p.Val() == "import" && p.isNewLine() {
- if err := p.doImport(); err != nil {
+ if err := p.doImport(1); err != nil {
return err
}
p.cursor-- // cursor is advanced when we continue, so roll back one more
@@ -562,6 +634,15 @@ func (p *parser) closeCurlyBrace() error {
return nil
}
+func (p *parser) isNamedRoute() (bool, string) {
+ keys := p.block.Keys
+ // A named route block is a single key with parens, prefixed with &.
+ if len(keys) == 1 && strings.HasPrefix(keys[0], "&(") && strings.HasSuffix(keys[0], ")") {
+ return true, strings.TrimSuffix(keys[0][2:], ")")
+ }
+ return false, ""
+}
+
func (p *parser) isSnippet() (bool, string) {
keys := p.block.Keys
// A snippet block is a single key with parens. Nothing else qualifies.
@@ -572,18 +653,24 @@ func (p *parser) isSnippet() (bool, string) {
}
// read and store everything in a block for later replay.
-func (p *parser) snippetTokens() ([]Token, error) {
- // snippet must have curlies.
+func (p *parser) blockTokens(retainCurlies bool) ([]Token, error) {
+ // block must have curlies.
err := p.openCurlyBrace()
if err != nil {
return nil, err
}
- nesting := 1 // count our own nesting in snippets
+ nesting := 1 // count our own nesting
tokens := []Token{}
+ if retainCurlies {
+ tokens = append(tokens, p.Token())
+ }
for p.Next() {
if p.Val() == "}" {
nesting--
if nesting == 0 {
+ if retainCurlies {
+ tokens = append(tokens, p.Token())
+ }
break
}
}
@@ -603,9 +690,10 @@ func (p *parser) snippetTokens() ([]Token, error) {
// head of the server block with tokens, which are
// grouped by segments.
type ServerBlock struct {
- HasBraces bool
- Keys []string
- Segments []Segment
+ HasBraces bool
+ Keys []string
+ Segments []Segment
+ IsNamedRoute bool
}
// DispenseDirective returns a dispenser that contains