caddyfile: Improve Dispenser.NextBlock() to support nesting

This commit is contained in:
Matthew Holt 2019-09-10 19:21:52 -06:00
parent 0cf592fa2e
commit 2459c292a4
No known key found for this signature in database
GPG key ID: 2A349DD577D586A5
14 changed files with 95 additions and 58 deletions

View file

@ -123,18 +123,39 @@ func (d *Dispenser) NextLine() bool {
// NextBlock can be used as the condition of a for loop // NextBlock can be used as the condition of a for loop
// to load the next token as long as it opens a block or // 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 // is already in a block nested more than initialNestingLevel.
// loaded, or false when the block's closing curly brace // In other words, a loop over NextBlock() will iterate
// was loaded and thus the block ended. Nested blocks are // all tokens in the block assuming the next token is an
// not supported. // open curly brace, until the matching closing brace.
func (d *Dispenser) NextBlock() bool { // The open and closing brace tokens for the outer-most
if d.nesting > 0 { // block will be consumed internally and omitted from
d.Next() // 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() == "}" { if d.Val() == "}" {
d.nesting-- d.nesting--
return false
} }
return true return d.nesting > initialNestingLevel
} }
if !d.nextOnSameLine() { // block must open on same line if !d.nextOnSameLine() { // block must open on same line
return false return false
@ -143,19 +164,18 @@ func (d *Dispenser) NextBlock() bool {
d.cursor-- // roll back if not opening brace d.cursor-- // roll back if not opening brace
return false return false
} }
d.Next() d.Next() // consume open curly brace
if d.Val() == "}" { if d.Val() == "}" {
// open and then closed right away return false // open and then closed right away
return false
} }
d.nesting++ d.nesting++
return true return true
} }
// Nested returns true if the token is currently nested // Nesting returns the current nesting level. Necessary
// inside a block (i.e. an open curly brace was consumed). // if using NextBlock()
func (d *Dispenser) Nested() bool { func (d *Dispenser) Nesting() int {
return d.nesting > 0 return d.nesting
} }
// Val gets the text of the current token. If there is no token // 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 // NewFromNextTokens returns a new dispenser with a copy of
// the tokens from the current token until the end of the // the tokens from the current token until the end of the
// "directive" whether that be to the end of the line or // "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 { func (d *Dispenser) NewFromNextTokens() *Dispenser {
tkns := []Token{d.Token()} tkns := []Token{d.Token()}
for d.NextArg() { for d.NextArg() {
tkns = append(tkns, d.Token()) tkns = append(tkns, d.Token())
} }
for d.NextBlock() { var openedBlock bool
for d.Nested() { 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()) 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) return NewDispenser(tkns)
} }

View file

@ -148,7 +148,7 @@ func TestDispenser_NextBlock(t *testing.T) {
d := newTestDispenser(input) d := newTestDispenser(input)
assertNextBlock := func(shouldLoad bool, expectedCursor, expectedNesting int) { 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) t.Errorf("NextBlock(): Should return %v but got %v", shouldLoad, loaded)
} }
if d.cursor != expectedCursor { if d.cursor != expectedCursor {

View file

@ -107,7 +107,7 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
} }
var hasBlock bool var hasBlock bool
for h.NextBlock() { for h.NextBlock(0) {
hasBlock = true hasBlock = true
switch h.Val() { switch h.Val() {

View file

@ -28,7 +28,7 @@ func (st *ServerType) parseMatcherDefinitions(d *caddyfile.Dispenser) (map[strin
matchers := make(map[string]map[string]json.RawMessage) matchers := make(map[string]map[string]json.RawMessage)
for d.Next() { for d.Next() {
definitionName := d.Val() definitionName := d.Val()
for d.NextBlock() { for nesting := d.Nesting(); d.NextBlock(nesting); {
matcherName := d.Val() matcherName := d.Val()
mod, err := caddy.GetModule("http.matchers." + matcherName) mod, err := caddy.GetModule("http.matchers." + matcherName)
if err != nil { if err != nil {

View file

@ -60,10 +60,10 @@ func parseHandlerOrder(d *caddyfile.Dispenser) ([]string, error) {
if len(order) == 1 && order[0] == "appearance" { if len(order) == 1 && order[0] == "appearance" {
return []string{"appearance"}, nil 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") return nil, d.Err("cannot open block if there are arguments")
} }
for d.NextBlock() { for d.NextBlock(0) {
order = append(order, d.Val()) order = append(order, d.Val())
if d.NextArg() { if d.NextArg() {
return nil, d.ArgErr() return nil, d.ArgErr()

View file

@ -67,7 +67,7 @@ func (enc *Encode) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
enc.EncodingsRaw[arg] = caddyconfig.JSON(encoding, nil) enc.EncodingsRaw[arg] = caddyconfig.JSON(encoding, nil)
} }
for d.NextBlock() { for d.NextBlock(0) {
name := d.Val() name := d.Val()
mod, err := caddy.GetModule("http.encoders." + name) mod, err := caddy.GetModule("http.encoders." + name)
if err != nil { if err != nil {

View file

@ -43,7 +43,7 @@ func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error)
return nil, h.ArgErr() return nil, h.ArgErr()
} }
for h.NextBlock() { for h.NextBlock(0) {
switch h.Val() { switch h.Val() {
case "hide": case "hide":
fsrv.Hide = h.RemainingArgs() fsrv.Hide = h.RemainingArgs()

View file

@ -69,7 +69,7 @@ func (MatchFile) CaddyModule() caddy.ModuleInfo {
// //
func (m *MatchFile) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { func (m *MatchFile) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
for d.Next() { for d.Next() {
for d.NextBlock() { for d.NextBlock(0) {
switch d.Val() { switch d.Val() {
case "root": case "root":
if !d.NextArg() { if !d.NextArg() {

View file

@ -49,7 +49,7 @@ func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error)
} }
// if not, they should be in a block // if not, they should be in a block
for h.NextBlock() { for h.NextBlock(0) {
if hasArgs { if hasArgs {
return nil, h.Err("cannot specify headers in both arguments and block") return nil, h.Err("cannot specify headers in both arguments and block")
} }

View file

@ -258,6 +258,9 @@ func (MatchHeader) CaddyModule() caddy.ModuleInfo {
// UnmarshalCaddyfile implements caddyfile.Unmarshaler. // UnmarshalCaddyfile implements caddyfile.Unmarshaler.
func (m *MatchHeader) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { func (m *MatchHeader) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
if *m == nil {
*m = make(map[string][]string)
}
for d.Next() { for d.Next() {
var field, val string var field, val string
if !d.Args(&field, &val) { if !d.Args(&field, &val) {

View file

@ -81,7 +81,7 @@ func (h *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
}) })
} }
for d.NextBlock() { for d.NextBlock(0) {
switch d.Val() { switch d.Val() {
case "to": case "to":
args := d.RemainingArgs() args := d.RemainingArgs()
@ -343,7 +343,6 @@ func (h *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
if !ok { if !ok {
return d.Errf("transport module '%s' is not a Caddyfile unmarshaler", mod.Name) return d.Errf("transport module '%s' is not a Caddyfile unmarshaler", mod.Name)
} }
d.Next() // consume the module name token
err = unm.UnmarshalCaddyfile(d.NewFromNextTokens()) err = unm.UnmarshalCaddyfile(d.NewFromNextTokens())
if err != nil { if err != nil {
return err return err
@ -377,7 +376,7 @@ func (h *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
// } // }
// //
func (h *HTTPTransport) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { func (h *HTTPTransport) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
for d.NextBlock() { for d.NextBlock(0) {
switch d.Val() { switch d.Val() {
case "read_buffer": case "read_buffer":
if !d.NextArg() { if !d.NextArg() {

View file

@ -39,32 +39,34 @@ func init() {
// } // }
// //
func (t *Transport) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { func (t *Transport) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
for d.NextBlock() { for d.Next() {
switch d.Val() { for d.NextBlock(0) {
case "root": switch d.Val() {
if !d.NextArg() { case "root":
return d.ArgErr() if !d.NextArg() {
} return d.ArgErr()
t.Root = d.Val() }
t.Root = d.Val()
case "split": case "split":
if !d.NextArg() { if !d.NextArg() {
return d.ArgErr() return d.ArgErr()
} }
t.SplitPath = d.Val() t.SplitPath = d.Val()
case "env": case "env":
args := d.RemainingArgs() args := d.RemainingArgs()
if len(args) != 2 { if len(args) != 2 {
return d.ArgErr() return d.ArgErr()
} }
if t.EnvVars == nil { if t.EnvVars == nil {
t.EnvVars = make(map[string]string) t.EnvVars = make(map[string]string)
} }
t.EnvVars[args[0]] = args[1] t.EnvVars[args[0]] = args[1]
default: default:
return d.Errf("unrecognized subdirective %s", d.Val()) return d.Errf("unrecognized subdirective %s", d.Val())
}
} }
} }
return nil return nil

View file

@ -57,7 +57,7 @@ func (s *StaticResponse) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
if d.Args(&statusCodeStr) { if d.Args(&statusCodeStr) {
s.StatusCode = WeakString(statusCodeStr) s.StatusCode = WeakString(statusCodeStr)
} }
for d.NextBlock() { for d.NextBlock(0) {
switch d.Val() { switch d.Val() {
case "body": case "body":
if s.Body != "" { if s.Body != "" {

View file

@ -34,7 +34,7 @@ func init() {
func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) { func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) {
t := new(Templates) t := new(Templates)
for h.Next() { for h.Next() {
for h.NextBlock() { for h.NextBlock(0) {
switch h.Val() { switch h.Val() {
case "mime": case "mime":
t.MIMETypes = h.RemainingArgs() t.MIMETypes = h.RemainingArgs()