199 lines
4.1 KiB
Go
199 lines
4.1 KiB
Go
package config
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
var defaultValues = make(map[string]string)
|
|
|
|
func isNum(c uint8) bool {
|
|
return c >= '0' && c <= '9'
|
|
}
|
|
|
|
func validVariableDefault(c uint8, line string, pos int) bool {
|
|
return (c == ':' && line[pos+1] == '-') || (c == '-')
|
|
}
|
|
|
|
func validVariableNameChar(c uint8) bool {
|
|
return c == '_' ||
|
|
c >= 'A' && c <= 'Z' ||
|
|
c >= 'a' && c <= 'z' ||
|
|
isNum(c)
|
|
}
|
|
|
|
func parseVariable(line string, pos int, mapping func(string) string) (string, int, bool) {
|
|
var buffer bytes.Buffer
|
|
|
|
for ; pos < len(line); pos++ {
|
|
c := line[pos]
|
|
|
|
switch {
|
|
case validVariableNameChar(c):
|
|
buffer.WriteByte(c)
|
|
default:
|
|
return mapping(buffer.String()), pos - 1, true
|
|
}
|
|
}
|
|
|
|
return mapping(buffer.String()), pos, true
|
|
}
|
|
|
|
func parseDefaultValue(line string, pos int) (string, int, bool) {
|
|
var buffer bytes.Buffer
|
|
|
|
// only skip :, :- and - at the beginning
|
|
for ; pos < len(line); pos++ {
|
|
c := line[pos]
|
|
if c == ':' || c == '-' {
|
|
continue
|
|
}
|
|
break
|
|
}
|
|
for ; pos < len(line); pos++ {
|
|
c := line[pos]
|
|
if c == '}' {
|
|
return buffer.String(), pos - 1, true
|
|
}
|
|
err := buffer.WriteByte(c)
|
|
if err != nil {
|
|
return "", pos, false
|
|
}
|
|
}
|
|
return "", 0, false
|
|
}
|
|
|
|
func parseVariableWithBraces(line string, pos int, mapping func(string) string) (string, int, bool) {
|
|
var buffer bytes.Buffer
|
|
|
|
for ; pos < len(line); pos++ {
|
|
c := line[pos]
|
|
|
|
switch {
|
|
case c == '}':
|
|
bufferString := buffer.String()
|
|
|
|
if bufferString == "" {
|
|
return "", 0, false
|
|
}
|
|
return mapping(buffer.String()), pos, true
|
|
case validVariableNameChar(c):
|
|
buffer.WriteByte(c)
|
|
case validVariableDefault(c, line, pos):
|
|
defaultValue := ""
|
|
defaultValue, pos, _ = parseDefaultValue(line, pos)
|
|
defaultValues[buffer.String()] = defaultValue
|
|
default:
|
|
return "", 0, false
|
|
}
|
|
}
|
|
|
|
return "", 0, false
|
|
}
|
|
|
|
func parseInterpolationExpression(line string, pos int, mapping func(string) string) (string, int, bool) {
|
|
c := line[pos]
|
|
|
|
switch {
|
|
case c == '$':
|
|
return "$", pos, true
|
|
case c == '{':
|
|
return parseVariableWithBraces(line, pos+1, mapping)
|
|
case !isNum(c) && validVariableNameChar(c):
|
|
// Variables can't start with a number
|
|
return parseVariable(line, pos, mapping)
|
|
default:
|
|
return "", 0, false
|
|
}
|
|
}
|
|
|
|
func parseLine(line string, mapping func(string) string) (string, bool) {
|
|
var buffer bytes.Buffer
|
|
|
|
for pos := 0; pos < len(line); pos++ {
|
|
c := line[pos]
|
|
switch {
|
|
case c == '$':
|
|
var replaced string
|
|
var success bool
|
|
|
|
replaced, pos, success = parseInterpolationExpression(line, pos+1, mapping)
|
|
|
|
if !success {
|
|
return "", false
|
|
}
|
|
|
|
buffer.WriteString(replaced)
|
|
default:
|
|
buffer.WriteByte(c)
|
|
}
|
|
}
|
|
|
|
return buffer.String(), true
|
|
}
|
|
|
|
func parseConfig(key string, data *interface{}, mapping func(string) string) error {
|
|
switch typedData := (*data).(type) {
|
|
case string:
|
|
var success bool
|
|
|
|
*data, success = parseLine(typedData, mapping)
|
|
|
|
if !success {
|
|
return fmt.Errorf("Invalid interpolation format for key \"%s\": \"%s\"", key, typedData)
|
|
}
|
|
case []interface{}:
|
|
for k, v := range typedData {
|
|
err := parseConfig(key, &v, mapping)
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
typedData[k] = v
|
|
}
|
|
case map[interface{}]interface{}:
|
|
for k, v := range typedData {
|
|
err := parseConfig(key, &v, mapping)
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
typedData[k] = v
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Interpolate replaces variables in a map entry
|
|
func Interpolate(key string, data *interface{}, environmentLookup EnvironmentLookup) error {
|
|
return parseConfig(key, data, func(s string) string {
|
|
values := environmentLookup.Lookup(s, nil)
|
|
|
|
if len(values) == 0 {
|
|
if val, ok := defaultValues[s]; ok {
|
|
return val
|
|
}
|
|
logrus.Warnf("The %s variable is not set. Substituting a blank string.", s)
|
|
return ""
|
|
}
|
|
|
|
if strings.SplitN(values[0], "=", 2)[1] == "" {
|
|
if val, ok := defaultValues[s]; ok {
|
|
return val
|
|
}
|
|
}
|
|
|
|
// Use first result if many are given
|
|
value := values[0]
|
|
|
|
// Environment variables come in key=value format
|
|
// Return everything past first '='
|
|
return strings.SplitN(value, "=", 2)[1]
|
|
})
|
|
}
|