From 2459c292a4d6fb0552eb9be3cecd955093ed853b Mon Sep 17 00:00:00 2001 From: Matthew Holt Date: Tue, 10 Sep 2019 19:21:52 -0600 Subject: caddyfile: Improve Dispenser.NextBlock() to support nesting --- caddyconfig/caddyfile/dispenser.go | 75 ++++++++++++++++++++++++--------- caddyconfig/caddyfile/dispenser_test.go | 2 +- caddyconfig/httpcaddyfile/builtins.go | 2 +- caddyconfig/httpcaddyfile/handlers.go | 2 +- caddyconfig/httpcaddyfile/options.go | 4 +- 5 files changed, 59 insertions(+), 26 deletions(-) (limited to 'caddyconfig') diff --git a/caddyconfig/caddyfile/dispenser.go b/caddyconfig/caddyfile/dispenser.go index 938c985..de67ee1 100755 --- a/caddyconfig/caddyfile/dispenser.go +++ b/caddyconfig/caddyfile/dispenser.go @@ -123,18 +123,39 @@ func (d *Dispenser) NextLine() bool { // NextBlock can be used as the condition of a for loop // to load the next token as long as it opens a block or -// is already in a block. It returns true if a token was -// loaded, or false when the block's closing curly brace -// was loaded and thus the block ended. Nested blocks are -// not supported. -func (d *Dispenser) NextBlock() bool { - if d.nesting > 0 { - d.Next() +// is already in a block nested more than initialNestingLevel. +// In other words, a loop over NextBlock() will iterate +// all tokens in the block assuming the next token is an +// open curly brace, until the matching closing brace. +// The open and closing brace tokens for the outer-most +// block will be consumed internally and omitted from +// the iteration. +// +// Proper use of this method looks like this: +// +// for nesting := d.Nesting(); d.NextBlock(nesting); { +// } +// +// However, in simple cases where it is known that the +// Dispenser is new and has not already traversed state +// by a loop over NextBlock(), this will do: +// +// for d.NextBlock(0) { +// } +// +// As with other token parsing logic, a loop over +// NextBlock() should be contained within a loop over +// Next(), as it is usually prudent to skip the initial +// token. +func (d *Dispenser) NextBlock(initialNestingLevel int) bool { + if d.nesting > initialNestingLevel { + if !d.Next() { + return false // should be EOF error + } if d.Val() == "}" { d.nesting-- - return false } - return true + return d.nesting > initialNestingLevel } if !d.nextOnSameLine() { // block must open on same line return false @@ -143,19 +164,18 @@ func (d *Dispenser) NextBlock() bool { d.cursor-- // roll back if not opening brace return false } - d.Next() + d.Next() // consume open curly brace if d.Val() == "}" { - // open and then closed right away - return false + return false // open and then closed right away } d.nesting++ return true } -// Nested returns true if the token is currently nested -// inside a block (i.e. an open curly brace was consumed). -func (d *Dispenser) Nested() bool { - return d.nesting > 0 +// Nesting returns the current nesting level. Necessary +// if using NextBlock() +func (d *Dispenser) Nesting() int { + return d.nesting } // Val gets the text of the current token. If there is no token @@ -230,19 +250,32 @@ func (d *Dispenser) RemainingArgs() []string { // NewFromNextTokens returns a new dispenser with a copy of // the tokens from the current token until the end of the // "directive" whether that be to the end of the line or -// the end of a block that starts at the end of the line. +// the end of a block that starts at the end of the line; +// in other words, until the end of the segment. func (d *Dispenser) NewFromNextTokens() *Dispenser { tkns := []Token{d.Token()} for d.NextArg() { tkns = append(tkns, d.Token()) } - for d.NextBlock() { - for d.Nested() { + var openedBlock bool + for nesting := d.Nesting(); d.NextBlock(nesting); { + if !openedBlock { + // because NextBlock() consumes the initial open + // curly brace, we rewind here to append it, since + // our case is special in that we want to include + // all the tokens including surrounding curly braces + // for a new dispenser to have + d.Prev() tkns = append(tkns, d.Token()) - d.NextBlock() + d.Next() + openedBlock = true } + tkns = append(tkns, d.Token()) + } + if openedBlock { + // include closing brace accordingly + tkns = append(tkns, d.Token()) } - tkns = append(tkns, d.Token()) return NewDispenser(tkns) } diff --git a/caddyconfig/caddyfile/dispenser_test.go b/caddyconfig/caddyfile/dispenser_test.go index 07fbbee..494db36 100755 --- a/caddyconfig/caddyfile/dispenser_test.go +++ b/caddyconfig/caddyfile/dispenser_test.go @@ -148,7 +148,7 @@ func TestDispenser_NextBlock(t *testing.T) { d := newTestDispenser(input) assertNextBlock := func(shouldLoad bool, expectedCursor, expectedNesting int) { - if loaded := d.NextBlock(); loaded != shouldLoad { + if loaded := d.NextBlock(0); loaded != shouldLoad { t.Errorf("NextBlock(): Should return %v but got %v", shouldLoad, loaded) } if d.cursor != expectedCursor { diff --git a/caddyconfig/httpcaddyfile/builtins.go b/caddyconfig/httpcaddyfile/builtins.go index db2eee5..0ac436e 100644 --- a/caddyconfig/httpcaddyfile/builtins.go +++ b/caddyconfig/httpcaddyfile/builtins.go @@ -107,7 +107,7 @@ func parseTLS(h Helper) ([]ConfigValue, error) { } var hasBlock bool - for h.NextBlock() { + for h.NextBlock(0) { hasBlock = true switch h.Val() { diff --git a/caddyconfig/httpcaddyfile/handlers.go b/caddyconfig/httpcaddyfile/handlers.go index 9a29e97..e133028 100644 --- a/caddyconfig/httpcaddyfile/handlers.go +++ b/caddyconfig/httpcaddyfile/handlers.go @@ -28,7 +28,7 @@ func (st *ServerType) parseMatcherDefinitions(d *caddyfile.Dispenser) (map[strin matchers := make(map[string]map[string]json.RawMessage) for d.Next() { definitionName := d.Val() - for d.NextBlock() { + for nesting := d.Nesting(); d.NextBlock(nesting); { matcherName := d.Val() mod, err := caddy.GetModule("http.matchers." + matcherName) if err != nil { diff --git a/caddyconfig/httpcaddyfile/options.go b/caddyconfig/httpcaddyfile/options.go index f99ce51..d9fb4b0 100644 --- a/caddyconfig/httpcaddyfile/options.go +++ b/caddyconfig/httpcaddyfile/options.go @@ -60,10 +60,10 @@ func parseHandlerOrder(d *caddyfile.Dispenser) ([]string, error) { if len(order) == 1 && order[0] == "appearance" { return []string{"appearance"}, nil } - if len(order) > 0 && d.NextBlock() { + if len(order) > 0 && d.NextBlock(0) { return nil, d.Err("cannot open block if there are arguments") } - for d.NextBlock() { + for d.NextBlock(0) { order = append(order, d.Val()) if d.NextArg() { return nil, d.ArgErr() -- cgit v1.2.3