Commit d3c22937 authored by Matthew Holt's avatar Matthew Holt

Fixed import command, added tests

parent c82d7c2d
...@@ -37,7 +37,7 @@ func NewDispenserTokens(filename string, tokens []token) Dispenser { ...@@ -37,7 +37,7 @@ func NewDispenserTokens(filename string, tokens []token) Dispenser {
// Next loads the next token. Returns true if a token // Next loads the next token. Returns true if a token
// was loaded; false otherwise. If false, all tokens // was loaded; false otherwise. If false, all tokens
// have already been consumed. // have been consumed.
func (d *Dispenser) Next() bool { func (d *Dispenser) Next() bool {
if d.cursor < len(d.tokens)-1 { if d.cursor < len(d.tokens)-1 {
d.cursor++ d.cursor++
...@@ -49,7 +49,7 @@ func (d *Dispenser) Next() bool { ...@@ -49,7 +49,7 @@ func (d *Dispenser) Next() bool {
// NextArg loads the next token if it is on the same // NextArg loads the next token if it is on the same
// line. Returns true if a token was loaded; false // line. Returns true if a token was loaded; false
// otherwise. If false, all tokens on the line have // otherwise. If false, all tokens on the line have
// been consumed. // been consumed. It handles imported tokens correctly.
func (d *Dispenser) NextArg() bool { func (d *Dispenser) NextArg() bool {
if d.cursor < 0 { if d.cursor < 0 {
d.cursor++ d.cursor++
...@@ -59,7 +59,8 @@ func (d *Dispenser) NextArg() bool { ...@@ -59,7 +59,8 @@ func (d *Dispenser) NextArg() bool {
return false return false
} }
if d.cursor < len(d.tokens)-1 && if d.cursor < len(d.tokens)-1 &&
(d.tokens[d.cursor].line+d.numLineBreaks(d.cursor) == d.tokens[d.cursor+1].line) { d.tokens[d.cursor].file == d.tokens[d.cursor+1].file &&
d.tokens[d.cursor].line+d.numLineBreaks(d.cursor) == d.tokens[d.cursor+1].line {
d.cursor++ d.cursor++
return true return true
} }
...@@ -69,7 +70,7 @@ func (d *Dispenser) NextArg() bool { ...@@ -69,7 +70,7 @@ func (d *Dispenser) NextArg() bool {
// NextLine loads the next token only if it is not on the same // NextLine loads the next token only if it is not on the same
// line as the current token, and returns true if a token was // line as the current token, and returns true if a token was
// loaded; false otherwise. If false, there is not another token // loaded; false otherwise. If false, there is not another token
// or it is on the same line. // or it is on the same line. It handles imported tokens correctly.
func (d *Dispenser) NextLine() bool { func (d *Dispenser) NextLine() bool {
if d.cursor < 0 { if d.cursor < 0 {
d.cursor++ d.cursor++
...@@ -79,7 +80,8 @@ func (d *Dispenser) NextLine() bool { ...@@ -79,7 +80,8 @@ func (d *Dispenser) NextLine() bool {
return false return false
} }
if d.cursor < len(d.tokens)-1 && if d.cursor < len(d.tokens)-1 &&
d.tokens[d.cursor].line+d.numLineBreaks(d.cursor) < d.tokens[d.cursor+1].line { (d.tokens[d.cursor].file != d.tokens[d.cursor+1].file ||
d.tokens[d.cursor].line+d.numLineBreaks(d.cursor) < d.tokens[d.cursor+1].line) {
d.cursor++ d.cursor++
return true return true
} }
...@@ -135,6 +137,18 @@ func (d *Dispenser) Line() int { ...@@ -135,6 +137,18 @@ func (d *Dispenser) Line() int {
return d.tokens[d.cursor].line return d.tokens[d.cursor].line
} }
// File gets the filename of the current token. If there is no token loaded,
// it returns the filename originally given when parsing started.
func (d *Dispenser) File() string {
if d.cursor < 0 || d.cursor >= len(d.tokens) {
return d.filename
}
if tokenFilename := d.tokens[d.cursor].file; tokenFilename != "" {
return tokenFilename
}
return d.filename
}
// Args is a convenience function that loads the next arguments // Args is a convenience function that loads the next arguments
// (tokens on the same line) into an arbitrary number of strings // (tokens on the same line) into an arbitrary number of strings
// pointed to in targets. If there are fewer tokens available // pointed to in targets. If there are fewer tokens available
...@@ -185,7 +199,7 @@ func (d *Dispenser) ArgErr() error { ...@@ -185,7 +199,7 @@ func (d *Dispenser) ArgErr() error {
// SyntaxErr creates a generic syntax error which explains what was // SyntaxErr creates a generic syntax error which explains what was
// found and what was expected. // found and what was expected.
func (d *Dispenser) SyntaxErr(expected string) error { func (d *Dispenser) SyntaxErr(expected string) error {
msg := fmt.Sprintf("%s:%d - Syntax error: Unexpected token '%s', expecting '%s'", d.filename, d.Line(), d.Val(), expected) msg := fmt.Sprintf("%s:%d - Syntax error: Unexpected token '%s', expecting '%s'", d.File(), d.Line(), d.Val(), expected)
return errors.New(msg) return errors.New(msg)
} }
...@@ -197,7 +211,7 @@ func (d *Dispenser) EofErr() error { ...@@ -197,7 +211,7 @@ func (d *Dispenser) EofErr() error {
// Err generates a custom parse error with a message of msg. // Err generates a custom parse error with a message of msg.
func (d *Dispenser) Err(msg string) error { func (d *Dispenser) Err(msg string) error {
msg = fmt.Sprintf("%s:%d - Parse error: %s", d.filename, d.Line(), msg) msg = fmt.Sprintf("%s:%d - Parse error: %s", d.File(), d.Line(), msg)
return errors.New(msg) return errors.New(msg)
} }
...@@ -215,3 +229,17 @@ func (d *Dispenser) numLineBreaks(tknIdx int) int { ...@@ -215,3 +229,17 @@ func (d *Dispenser) numLineBreaks(tknIdx int) int {
} }
return strings.Count(d.tokens[tknIdx].text, "\n") return strings.Count(d.tokens[tknIdx].text, "\n")
} }
// isNewLine determines whether the current token is on a different
// line (higher line number) than the previous token. It handles imported
// tokens correctly. If there isn't a previous token, it returns true.
func (d *Dispenser) isNewLine() bool {
if d.cursor < 1 {
return true
}
if d.cursor > len(d.tokens)-1 {
return false
}
return d.tokens[d.cursor-1].file != d.tokens[d.cursor].file ||
d.tokens[d.cursor-1].line+d.numLineBreaks(d.cursor-1) < d.tokens[d.cursor].line
}
dir2 arg1 arg2
dir3
\ No newline at end of file
host1 {
dir1
dir2 arg1
}
\ No newline at end of file
...@@ -19,6 +19,7 @@ type ( ...@@ -19,6 +19,7 @@ type (
// token represents a single parsable unit. // token represents a single parsable unit.
token struct { token struct {
file string
line int line int
text string text string
} }
......
...@@ -3,6 +3,7 @@ package parse ...@@ -3,6 +3,7 @@ package parse
import ( import (
"net" "net"
"os" "os"
"path/filepath"
"strings" "strings"
) )
...@@ -73,7 +74,16 @@ func (p *parser) addresses() error { ...@@ -73,7 +74,16 @@ func (p *parser) addresses() error {
var expectingAnother bool var expectingAnother bool
for { for {
tkn, startLine := p.Val(), p.Line() tkn := p.Val()
// special case: import directive replaces tokens during parse-time
if tkn == "import" && p.isNewLine() {
err := p.doImport()
if err != nil {
return err
}
continue
}
// Open brace definitely indicates end of addresses // Open brace definitely indicates end of addresses
if tkn == "{" { if tkn == "{" {
...@@ -104,13 +114,13 @@ func (p *parser) addresses() error { ...@@ -104,13 +114,13 @@ func (p *parser) addresses() error {
if expectingAnother && !hasNext { if expectingAnother && !hasNext {
return p.EofErr() return p.EofErr()
} }
if !expectingAnother && p.Line() > startLine {
break
}
if !hasNext { if !hasNext {
p.eof = true p.eof = true
break // EOF break // EOF
} }
if !expectingAnother && p.isNewLine() {
break
}
} }
return nil return nil
...@@ -156,6 +166,7 @@ func (p *parser) directives() error { ...@@ -156,6 +166,7 @@ func (p *parser) directives() error {
if err != nil { if err != nil {
return err return err
} }
p.cursor-- // cursor is advanced when we continue, so roll back one more
continue continue
} }
...@@ -188,12 +199,17 @@ func (p *parser) doImport() error { ...@@ -188,12 +199,17 @@ func (p *parser) doImport() error {
defer file.Close() defer file.Close()
importedTokens := allTokens(file) importedTokens := allTokens(file)
// Tack the filename onto these tokens so any errors show the imported file's name
for i := 0; i < len(importedTokens); i++ {
importedTokens[i].file = filepath.Base(importFile)
}
// Splice out the import directive and its argument (2 tokens total) // Splice out the import directive and its argument (2 tokens total)
// and insert the imported tokens. // and insert the imported tokens in their place.
tokensBefore := p.tokens[:p.cursor-1] tokensBefore := p.tokens[:p.cursor-1]
tokensAfter := p.tokens[p.cursor+1:] tokensAfter := p.tokens[p.cursor+1:]
p.tokens = append(tokensBefore, append(importedTokens, tokensAfter...)...) p.tokens = append(tokensBefore, append(importedTokens, tokensAfter...)...)
p.cursor -= 2 p.cursor-- // cursor was advanced one position to read the filename; rewind it
return nil return nil
} }
...@@ -206,7 +222,6 @@ func (p *parser) doImport() error { ...@@ -206,7 +222,6 @@ func (p *parser) doImport() error {
// by directive setup functions. // by directive setup functions.
func (p *parser) directive() error { func (p *parser) directive() error {
dir := p.Val() dir := p.Val()
line := p.Line()
nesting := 0 nesting := 0
if _, ok := ValidDirectives[dir]; !ok { if _, ok := ValidDirectives[dir]; !ok {
...@@ -219,7 +234,7 @@ func (p *parser) directive() error { ...@@ -219,7 +234,7 @@ func (p *parser) directive() error {
for p.Next() { for p.Next() {
if p.Val() == "{" { if p.Val() == "{" {
nesting++ nesting++
} else if p.Line()+p.numLineBreaks(p.cursor) > line && nesting == 0 { } else if p.isNewLine() && nesting == 0 {
p.cursor-- // read too far p.cursor-- // read too far
break break
} else if p.Val() == "}" && nesting > 0 { } else if p.Val() == "}" && nesting > 0 {
...@@ -239,7 +254,7 @@ func (p *parser) directive() error { ...@@ -239,7 +254,7 @@ func (p *parser) directive() error {
// openCurlyBrace expects the current token to be an // openCurlyBrace expects the current token to be an
// opening curly brace. This acts like an assertion // opening curly brace. This acts like an assertion
// because it returns an error if the token is not // because it returns an error if the token is not
// a opening curly brace. It does not advance the token. // a opening curly brace. It does NOT advance the token.
func (p *parser) openCurlyBrace() error { func (p *parser) openCurlyBrace() error {
if p.Val() != "{" { if p.Val() != "{" {
return p.SyntaxErr("{") return p.SyntaxErr("{")
...@@ -250,7 +265,7 @@ func (p *parser) openCurlyBrace() error { ...@@ -250,7 +265,7 @@ func (p *parser) openCurlyBrace() error {
// closeCurlyBrace expects the current token to be // closeCurlyBrace expects the current token to be
// a closing curly brace. This acts like an assertion // a closing curly brace. This acts like an assertion
// because it returns an error if the token is not // because it returns an error if the token is not
// a closing curly brace. It does not advance the token. // a closing curly brace. It does NOT advance the token.
func (p *parser) closeCurlyBrace() error { func (p *parser) closeCurlyBrace() error {
if p.Val() != "}" { if p.Val() != "}" {
return p.SyntaxErr("}") return p.SyntaxErr("}")
......
...@@ -57,7 +57,7 @@ func TestStandardAddress(t *testing.T) { ...@@ -57,7 +57,7 @@ func TestStandardAddress(t *testing.T) {
} }
} }
func TestParseOne(t *testing.T) { func TestParseOneAndImport(t *testing.T) {
setupParseTests() setupParseTests()
testParseOne := func(input string) (multiServerBlock, error) { testParseOne := func(input string) (multiServerBlock, error) {
...@@ -218,6 +218,23 @@ func TestParseOne(t *testing.T) { ...@@ -218,6 +218,23 @@ func TestParseOne(t *testing.T) {
}}, }},
{``, false, []address{}, map[string]int{}}, {``, false, []address{}, map[string]int{}},
{`localhost
dir1 arg1
import import_test1.txt`, false, []address{
{"localhost", ""},
}, map[string]int{
"dir1": 2,
"dir2": 3,
"dir3": 1,
}},
{`import import_test2.txt`, false, []address{
{"host1", ""},
}, map[string]int{
"dir1": 1,
"dir2": 2,
}},
} { } {
result, err := testParseOne(test.input) result, err := testParseOne(test.input)
......
CHANGES CHANGES
<master>
- errors: Error log now includes timestamp with each entry
- gzip: Default filtering is by extension (fixes bug); removed MIME type filter
- import: Fixed; works inside and outside server blocks
- templates: Restricted or missing files result in proper 403 or 404 error
0.7.2 (July 1, 2015) 0.7.2 (July 1, 2015)
- Custom builds through caddyserver.com - extend Caddy by writing addons - Custom builds through caddyserver.com - extend Caddy by writing addons
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment