mirror of
https://github.com/caddyserver/caddy.git
synced 2025-01-07 11:28:48 +03:00
commit
1125a236ea
2 changed files with 152 additions and 54 deletions
|
@ -54,6 +54,7 @@ type parser struct {
|
||||||
block ServerBlock // current server block being parsed
|
block ServerBlock // current server block being parsed
|
||||||
validDirectives []string // a directive must be valid or it's an error
|
validDirectives []string // a directive must be valid or it's an error
|
||||||
eof bool // if we encounter a valid EOF in a hard place
|
eof bool // if we encounter a valid EOF in a hard place
|
||||||
|
definedSnippets map[string][]Token
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *parser) parseAll() ([]ServerBlock, error) {
|
func (p *parser) parseAll() ([]ServerBlock, error) {
|
||||||
|
@ -95,6 +96,24 @@ func (p *parser) begin() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ok, name := p.isSnippet(); ok {
|
||||||
|
if p.definedSnippets == nil {
|
||||||
|
p.definedSnippets = map[string][]Token{}
|
||||||
|
}
|
||||||
|
if _, found := p.definedSnippets[name]; found {
|
||||||
|
return p.Errf("redeclaration of previously declared snippet %s", name)
|
||||||
|
}
|
||||||
|
// consume all tokens til matched close brace
|
||||||
|
tokens, err := p.snippetTokens()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.definedSnippets[name] = tokens
|
||||||
|
// empty block keys so we don't save this block as a real server.
|
||||||
|
p.block.Keys = nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
return p.blockContents()
|
return p.blockContents()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -221,70 +240,75 @@ func (p *parser) doImport() error {
|
||||||
if p.NextArg() {
|
if p.NextArg() {
|
||||||
return p.Err("Import takes only one argument (glob pattern or file)")
|
return p.Err("Import takes only one argument (glob pattern or file)")
|
||||||
}
|
}
|
||||||
|
|
||||||
// make path relative to Caddyfile rather than current working directory (issue #867)
|
|
||||||
// and then use glob to get list of matching filenames
|
|
||||||
absFile, err := filepath.Abs(p.Dispenser.filename)
|
|
||||||
if err != nil {
|
|
||||||
return p.Errf("Failed to get absolute path of file: %s: %v", p.Dispenser.filename, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var matches []string
|
|
||||||
var globPattern string
|
|
||||||
if !filepath.IsAbs(importPattern) {
|
|
||||||
globPattern = filepath.Join(filepath.Dir(absFile), importPattern)
|
|
||||||
} else {
|
|
||||||
globPattern = importPattern
|
|
||||||
}
|
|
||||||
matches, err = filepath.Glob(globPattern)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return p.Errf("Failed to use import pattern %s: %v", importPattern, err)
|
|
||||||
}
|
|
||||||
if len(matches) == 0 {
|
|
||||||
if strings.Contains(globPattern, "*") {
|
|
||||||
log.Printf("[WARNING] No files matching import pattern: %s", importPattern)
|
|
||||||
} else {
|
|
||||||
return p.Errf("File to import not found: %s", importPattern)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// splice out the import directive and its argument (2 tokens total)
|
// splice out the import directive and its argument (2 tokens total)
|
||||||
tokensBefore := p.tokens[:p.cursor-1]
|
tokensBefore := p.tokens[:p.cursor-1]
|
||||||
tokensAfter := p.tokens[p.cursor+1:]
|
tokensAfter := p.tokens[p.cursor+1:]
|
||||||
|
|
||||||
// collect all the imported tokens
|
|
||||||
var importedTokens []Token
|
var importedTokens []Token
|
||||||
for _, importFile := range matches {
|
|
||||||
newTokens, err := p.doSingleImport(importFile)
|
// first check snippets. That is a simple, non-recursive replacement
|
||||||
|
if p.definedSnippets != nil && p.definedSnippets[importPattern] != nil {
|
||||||
|
importedTokens = p.definedSnippets[importPattern]
|
||||||
|
} else {
|
||||||
|
// make path relative to Caddyfile rather than current working directory (issue #867)
|
||||||
|
// and then use glob to get list of matching filenames
|
||||||
|
absFile, err := filepath.Abs(p.Dispenser.filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return p.Errf("Failed to get absolute path of file: %s: %v", p.Dispenser.filename, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var importLine int
|
var matches []string
|
||||||
for i, token := range newTokens {
|
var globPattern string
|
||||||
if token.Text == "import" {
|
if !filepath.IsAbs(importPattern) {
|
||||||
importLine = token.Line
|
globPattern = filepath.Join(filepath.Dir(absFile), importPattern)
|
||||||
continue
|
} else {
|
||||||
}
|
globPattern = importPattern
|
||||||
if token.Line == importLine {
|
}
|
||||||
var abs string
|
matches, err = filepath.Glob(globPattern)
|
||||||
if filepath.IsAbs(token.Text) {
|
|
||||||
abs = token.Text
|
if err != nil {
|
||||||
} else if !filepath.IsAbs(importFile) {
|
return p.Errf("Failed to use import pattern %s: %v", importPattern, err)
|
||||||
abs = filepath.Join(filepath.Dir(absFile), token.Text)
|
}
|
||||||
} else {
|
if len(matches) == 0 {
|
||||||
abs = filepath.Join(filepath.Dir(importFile), token.Text)
|
if strings.Contains(globPattern, "*") {
|
||||||
}
|
log.Printf("[WARNING] No files matching import pattern: %s", importPattern)
|
||||||
newTokens[i] = Token{
|
} else {
|
||||||
Text: abs,
|
return p.Errf("File to import not found: %s", importPattern)
|
||||||
Line: token.Line,
|
|
||||||
File: token.File,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
importedTokens = append(importedTokens, newTokens...)
|
// collect all the imported tokens
|
||||||
|
|
||||||
|
for _, importFile := range matches {
|
||||||
|
newTokens, err := p.doSingleImport(importFile)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var importLine int
|
||||||
|
for i, token := range newTokens {
|
||||||
|
if token.Text == "import" {
|
||||||
|
importLine = token.Line
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if token.Line == importLine {
|
||||||
|
var abs string
|
||||||
|
if filepath.IsAbs(token.Text) {
|
||||||
|
abs = token.Text
|
||||||
|
} else if !filepath.IsAbs(importFile) {
|
||||||
|
abs = filepath.Join(filepath.Dir(absFile), token.Text)
|
||||||
|
} else {
|
||||||
|
abs = filepath.Join(filepath.Dir(importFile), token.Text)
|
||||||
|
}
|
||||||
|
newTokens[i] = Token{
|
||||||
|
Text: abs,
|
||||||
|
Line: token.Line,
|
||||||
|
File: token.File,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
importedTokens = append(importedTokens, newTokens...)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// splice the imported tokens in the place of the import statement
|
// splice the imported tokens in the place of the import statement
|
||||||
|
@ -433,3 +457,41 @@ type ServerBlock struct {
|
||||||
Keys []string
|
Keys []string
|
||||||
Tokens map[string][]Token
|
Tokens map[string][]Token
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *parser) isSnippet() (bool, string) {
|
||||||
|
keys := p.block.Keys
|
||||||
|
// A snippet block is a single key with parens. Nothing else qualifies.
|
||||||
|
if len(keys) == 1 && strings.HasPrefix(keys[0], "(") && strings.HasSuffix(keys[0], ")") {
|
||||||
|
return true, strings.TrimSuffix(keys[0][1:], ")")
|
||||||
|
}
|
||||||
|
return false, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// read and store everything in a block for later replay.
|
||||||
|
func (p *parser) snippetTokens() ([]Token, error) {
|
||||||
|
// TODO: disallow imports in snippets for simplicity at import time
|
||||||
|
// snippet must have curlies.
|
||||||
|
err := p.openCurlyBrace()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
count := 1
|
||||||
|
tokens := []Token{}
|
||||||
|
for p.Next() {
|
||||||
|
if p.Val() == "}" {
|
||||||
|
count--
|
||||||
|
if count == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if p.Val() == "{" {
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
tokens = append(tokens, p.tokens[p.cursor])
|
||||||
|
}
|
||||||
|
// make sure we're matched up
|
||||||
|
if count != 0 {
|
||||||
|
return nil, p.SyntaxErr("}")
|
||||||
|
}
|
||||||
|
return tokens, nil
|
||||||
|
}
|
||||||
|
|
|
@ -514,3 +514,39 @@ func testParser(input string) parser {
|
||||||
p := parser{Dispenser: NewDispenser("Caddyfile", buf)}
|
p := parser{Dispenser: NewDispenser("Caddyfile", buf)}
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSnippets(t *testing.T) {
|
||||||
|
p := testParser(`(common) {
|
||||||
|
gzip foo
|
||||||
|
errors stderr
|
||||||
|
|
||||||
|
}
|
||||||
|
http://example.com {
|
||||||
|
import common
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
blocks, err := p.parseAll()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
for _, b := range blocks {
|
||||||
|
t.Log(b.Keys)
|
||||||
|
t.Log(b.Tokens)
|
||||||
|
}
|
||||||
|
if len(blocks) != 1 {
|
||||||
|
t.Fatalf("Expect exactly one server block. Got %d.", len(blocks))
|
||||||
|
}
|
||||||
|
if actual, expected := blocks[0].Keys[0], "http://example.com"; expected != actual {
|
||||||
|
t.Errorf("Expected server name to be '%s' but was '%s'", expected, actual)
|
||||||
|
}
|
||||||
|
if len(blocks[0].Tokens) != 2 {
|
||||||
|
t.Fatalf("Server block should have tokens from import")
|
||||||
|
}
|
||||||
|
if actual, expected := blocks[0].Tokens["gzip"][0].Text, "gzip"; expected != actual {
|
||||||
|
t.Errorf("Expected argument to be '%s' but was '%s'", expected, actual)
|
||||||
|
}
|
||||||
|
if actual, expected := blocks[0].Tokens["errors"][1].Text, "stderr"; expected != actual {
|
||||||
|
t.Errorf("Expected argument to be '%s' but was '%s'", expected, actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue