consul/vendor/github.com/hashicorp/go-discover/config.go

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)
}
}