open-consul/vendor/github.com/hashicorp/go-discover/config.go
Frank Schroeder 3d4fa7f65d Update github.com/hashicorp/go-discover to pull in new config parser
This patch updates the go-discover library to use the new config parser
which uses a different encoding scheme for the go-discover config DSL.
Values are no longer URL encoded but taken literally unless they contain
spaces, backslashes or double quotes. To support keys or values with
these special characters the string needs to be double quoted and usual
escaping rules apply.

Fixes #3417
2017-10-04 19:12:28 +02:00

227 lines
4.6 KiB
Go

package discover
import (
"fmt"
"sort"
"strconv"
"strings"
)
// Config stores key/value pairs for the discovery
// functions to use.
type Config map[string]string
// Parse parses a "key=val key=val ..." string into a config map. Keys
// and values which contain spaces, backslashes or double-quotes must be
// quoted with double quotes. Use the backslash to escape special
// characters within quoted strings, e.g. "some key"="some \"value\"".
func Parse(s string) (Config, error) {
return parse(s)
}
// String formats a config map into the "key=val key=val ..."
// understood by Parse. The order of the keys is stable.
func (c Config) String() string {
// sort 'provider' to the front and keep the keys stable.
var keys []string
for k := range c {
if k != "provider" {
keys = append(keys, k)
}
}
sort.Strings(keys)
keys = append([]string{"provider"}, keys...)
quote := func(s string) string {
if strings.ContainsAny(s, ` "\`) {
return strconv.Quote(s)
}
return s
}
var vals []string
for _, k := range keys {
v := c[k]
if v == "" {
continue
}
vals = append(vals, quote(k)+"="+quote(v))
}
return strings.Join(vals, " ")
}
func parse(in string) (Config, error) {
m := Config{}
s := []rune(strings.TrimSpace(in))
state := stateKey
key := ""
for {
// exit condition
if len(s) == 0 {
break
}
// get the next token
item, val, n := lex(s)
s = s[n:]
// fmt.Printf("parse: state: %q item: %q val: '%s' n: %d rest: '%s'\n", state, item, val, n, string(s))
switch state {
case stateKey:
switch item {
case itemText:
key = val
if _, exists := m[key]; exists {
return nil, fmt.Errorf("%s: duplicate key", key)
}
state = stateEqual
default:
return nil, fmt.Errorf("%s: %s", key, val)
}
case stateEqual:
switch item {
case itemEqual:
state = stateVal
default:
return nil, fmt.Errorf("%s: missing '='", key)
}
case stateVal:
switch item {
case itemText:
m[key] = val
state = stateKey
case itemError:
return nil, fmt.Errorf("%s: %s", key, val)
default:
return nil, fmt.Errorf("%s: missing value", key)
}
}
}
//fmt.Printf("parse: state: %q rest: '%s'\n", state, string(s))
switch state {
case stateEqual:
return nil, fmt.Errorf("%s: missing '='", key)
case stateVal:
return nil, fmt.Errorf("%s: missing value", key)
}
if len(m) == 0 {
return nil, nil
}
return m, nil
}
type itemType string
const (
itemText itemType = "TEXT"
itemEqual = "EQUAL"
itemError = "ERROR"
)
func (t itemType) String() string {
return string(t)
}
type state string
const (
// lexer states
stateStart state = "start"
stateEqual = "equal"
stateText = "text"
stateQText = "qtext"
stateQTextEnd = "qtextend"
stateQTextEsc = "qtextesc"
// parser states
stateKey = "key"
stateVal = "val"
)
func lex(s []rune) (itemType, string, int) {
isEqual := func(r rune) bool { return r == '=' }
isEscape := func(r rune) bool { return r == '\\' }
isQuote := func(r rune) bool { return r == '"' }
isSpace := func(r rune) bool { return r == ' ' }
unquote := func(r []rune) (string, error) {
v := strings.TrimSpace(string(r))
return strconv.Unquote(v)
}
var quote rune
state := stateStart
for i, r := range s {
// fmt.Println("lex:", "i:", i, "r:", string(r), "state:", string(state), "head:", string(s[:i]), "tail:", string(s[i:]))
switch state {
case stateStart:
switch {
case isSpace(r):
// state = stateStart
case isEqual(r):
state = stateEqual
case isQuote(r):
quote = r
state = stateQText
default:
state = stateText
}
case stateEqual:
return itemEqual, "", i
case stateText:
switch {
case isEqual(r) || isSpace(r):
v := strings.TrimSpace(string(s[:i]))
return itemText, v, i
default:
// state = stateText
}
case stateQText:
switch {
case r == quote:
state = stateQTextEnd
case isEscape(r):
state = stateQTextEsc
default:
// state = stateQText
}
case stateQTextEsc:
state = stateQText
case stateQTextEnd:
v, err := unquote(s[:i])
if err != nil {
return itemError, err.Error(), i
}
return itemText, v, i
}
}
// fmt.Println("lex:", "state:", string(state))
switch state {
case stateEqual:
return itemEqual, "", len(s)
case stateQText:
return itemError, "unbalanced quotes", len(s)
case stateQTextEsc:
return itemError, "unterminated escape sequence", len(s)
case stateQTextEnd:
v, err := unquote(s)
if err != nil {
return itemError, err.Error(), len(s)
}
return itemText, v, len(s)
default:
return itemText, strings.TrimSpace(string(s)), len(s)
}
}