-
Notifications
You must be signed in to change notification settings - Fork 249
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
10 changed files
with
610 additions
and
424 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,230 @@ | ||
package config | ||
|
||
import ( | ||
"fmt" | ||
"regexp" | ||
"strconv" | ||
"strings" | ||
|
||
"inet.af/netaddr" | ||
) | ||
|
||
func typeof(val interface{}) string { | ||
switch val.(type) { | ||
case string: | ||
return "string" | ||
case int, int16, int32: | ||
return "int" | ||
} | ||
return "" | ||
} | ||
|
||
func hasInt(val interface{}) (int, bool) { | ||
if i, err := strconv.Atoi(fmt.Sprintf("%v", val)); err == nil { | ||
return i, true | ||
} | ||
return 0, false | ||
} | ||
|
||
func expectFunc(val interface{}, format string) (interface{}, error) { | ||
t := typeof(val) | ||
vals := fmt.Sprintf("%s", val) | ||
|
||
// known formats | ||
switch format { | ||
case "str", "string": | ||
if t == "string" { | ||
return "", nil | ||
} | ||
return "", fmt.Errorf("string expected, got %s (%v)", t, val) | ||
case "int": | ||
if _, ok := hasInt(val); ok { | ||
return "", nil | ||
} | ||
return "", fmt.Errorf("int expected, got %s (%v)", t, val) | ||
case "ip": | ||
if _, err := netaddr.ParseIPPrefix(vals); err == nil { | ||
return "", nil | ||
} | ||
return "", fmt.Errorf("IP/mask expected, got %v", val) | ||
} | ||
|
||
// try range | ||
if matched, _ := regexp.MatchString(`\d+-\d+`, format); matched { | ||
iv, ok := hasInt(val) | ||
if !ok { | ||
return "", fmt.Errorf("int expected, got %s (%v)", t, val) | ||
} | ||
r := strings.Split(format, "-") | ||
i0, _ := hasInt(r[0]) | ||
i1, _ := hasInt(r[1]) | ||
if i1 < i0 { | ||
i0, i1 = i1, i0 | ||
} | ||
if i0 <= iv && iv <= i1 { | ||
return "", nil | ||
} | ||
return "", fmt.Errorf("value (%d) expected to be in range %d-%d", iv, i0, i1) | ||
} | ||
|
||
// Try regex | ||
matched, err := regexp.MatchString(format, vals) | ||
if err != nil || !matched { | ||
return "", fmt.Errorf("value %s does not match regex %s %v", vals, format, err) | ||
} | ||
|
||
return "", nil | ||
} | ||
|
||
var funcMap = map[string]interface{}{ | ||
"optional": func(val interface{}, format string) (interface{}, error) { | ||
if val == nil { | ||
return "", nil | ||
} | ||
return expectFunc(val, format) | ||
}, | ||
"expect": expectFunc, | ||
// "require": func(val interface{}) (interface{}, error) { | ||
// if val == nil { | ||
// return nil, errors.New("required value not set") | ||
// } | ||
// return val, nil | ||
// }, | ||
"ip": func(val interface{}) (interface{}, error) { | ||
s := fmt.Sprintf("%v", val) | ||
a := strings.Split(s, "/") | ||
return a[0], nil | ||
}, | ||
"ipmask": func(val interface{}) (interface{}, error) { | ||
s := fmt.Sprintf("%v", val) | ||
a := strings.Split(s, "/") | ||
return a[1], nil | ||
}, | ||
"default": func(in ...interface{}) (interface{}, error) { | ||
if len(in) < 2 { | ||
return nil, fmt.Errorf("default value expected") | ||
} | ||
if len(in) > 2 { | ||
return nil, fmt.Errorf("too many arguments") | ||
} | ||
|
||
val := in[len(in)-1] | ||
def := in[0] | ||
|
||
switch v := val.(type) { | ||
case nil: | ||
return def, nil | ||
case string: | ||
if v == "" { | ||
return def, nil | ||
} | ||
case bool: | ||
if !v { | ||
return def, nil | ||
} | ||
} | ||
// if val == nil { | ||
// return def, nil | ||
// } | ||
|
||
// If we have a input value, do some type checking | ||
tval, tdef := typeof(val), typeof(def) | ||
if tval == "string" && tdef == "int" { | ||
if _, err := strconv.Atoi(val.(string)); err == nil { | ||
tval = "int" | ||
} | ||
if tdef == "str" { | ||
if _, err := strconv.Atoi(def.(string)); err == nil { | ||
tdef = "int" | ||
} | ||
} | ||
} | ||
if tdef != tval { | ||
return val, fmt.Errorf("expected type %v, got %v (value=%v)", tdef, tval, val) | ||
} | ||
|
||
// Return the value | ||
return val, nil | ||
}, | ||
"contains": func(substr string, str string) (interface{}, error) { | ||
return strings.Contains(fmt.Sprintf("%v", str), fmt.Sprintf("%v", substr)), nil | ||
}, | ||
"split": func(sep string, val interface{}) (interface{}, error) { | ||
// Start and end values | ||
if val == nil { | ||
return []interface{}{}, nil | ||
} | ||
if sep == "" { | ||
sep = " " | ||
} | ||
|
||
v := fmt.Sprintf("%v", val) | ||
|
||
res := strings.Split(v, sep) | ||
r := make([]interface{}, len(res)) | ||
for i, p := range res { | ||
r[i] = p | ||
} | ||
return r, nil | ||
}, | ||
"join": func(sep string, val interface{}) (interface{}, error) { | ||
if sep == "" { | ||
sep = " " | ||
} | ||
// Start and end values | ||
switch v := val.(type) { | ||
case []interface{}: | ||
if val == nil { | ||
return "", nil | ||
} | ||
res := make([]string, len(v)) | ||
for i, v := range v { | ||
res[i] = fmt.Sprintf("%v", v) | ||
} | ||
return strings.Join(res, sep), nil | ||
case []string: | ||
return strings.Join(v, sep), nil | ||
case []int, []int16, []int32: | ||
return strings.Trim(strings.Replace(fmt.Sprint(v), " ", sep, -1), "[]"), nil | ||
} | ||
return nil, fmt.Errorf("expected array [], got %v", val) | ||
}, | ||
"slice": func(start, end int, val interface{}) (interface{}, error) { | ||
// string or array | ||
switch v := val.(type) { | ||
case string: | ||
if start < 0 { | ||
start += len(v) | ||
} | ||
if end < 0 { | ||
end += len(v) | ||
} | ||
return v[start:end], nil | ||
case []interface{}: | ||
if start < 0 { | ||
start += len(v) | ||
} | ||
if end < 0 { | ||
end += len(v) | ||
} | ||
return v[start:end], nil | ||
} | ||
return nil, fmt.Errorf("not an array") | ||
}, | ||
"index": func(idx int, val interface{}) (interface{}, error) { | ||
// string or array | ||
switch v := val.(type) { | ||
case string: | ||
if idx < 0 { | ||
idx += len(v) | ||
} | ||
return v[idx], nil | ||
case []interface{}: | ||
if idx < 0 { | ||
idx += len(v) | ||
} | ||
return v[idx], nil | ||
} | ||
return nil, fmt.Errorf("not an array") | ||
}, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
package config | ||
|
||
import ( | ||
"bytes" | ||
"fmt" | ||
"strings" | ||
"testing" | ||
"text/template" | ||
) | ||
|
||
var test_set = map[string][]string{ | ||
// empty values | ||
"default 0 .x": {"0"}, | ||
".x | default 0": {"0"}, | ||
"default 0 \"\"": {"0"}, | ||
"default 0 false": {"0"}, | ||
"false | default 0": {"0"}, | ||
// ints pass through ok | ||
"default 1 0": {"0"}, | ||
// errors | ||
"default .x": {"", "default value expected"}, | ||
"default .x 1 1": {"", "too many arguments"}, | ||
// type check | ||
"default 0 .i5": {"5"}, | ||
"default 0 .sA": {"", "expected type int"}, | ||
`default "5" .sA`: {"A"}, | ||
|
||
`contains "." .sAAA`: {"true"}, | ||
`.sAAA | contains "."`: {"true"}, | ||
`contains "." .sA`: {"false"}, | ||
`.sA | contains "."`: {"false"}, | ||
|
||
`split "." "a.a"`: {"[a a]"}, | ||
`split " " "a bb"`: {"[a bb]"}, | ||
|
||
`ip "1.1.1.1/32"`: {"1.1.1.1"}, | ||
`"1.1.1.1" | ip`: {"1.1.1.1"}, | ||
`ipmask "1.1.1.1/32"`: {"32"}, | ||
`"1.1.1.1/32" | split "/" | slice 0 1 | join ""`: {"1.1.1.1"}, | ||
`"1.1.1.1/32" | split "/" | slice 1 2 | join ""`: {"32"}, | ||
|
||
`split " " "a bb" | join "-"`: {"a-bb"}, | ||
`split "" ""`: {"[]"}, | ||
`split "abc" ""`: {"[]"}, | ||
|
||
`"1.1.1.1/32" | split "/" | index 1`: {"32"}, | ||
`"1.1.1.1/32" | split "/" | index -1`: {"32"}, | ||
`"1.1.1.1/32" | split "/" | index -2`: {"1.1.1.1"}, | ||
`"1.1.1.1/32" | split "/" | index -3`: {"", "out of range"}, | ||
`"1.1.1.1/32" | split "/" | index 2`: {"", "out of range"}, | ||
|
||
`expect "1.1.1.1/32" "ip"`: {""}, | ||
`expect "1.1.1.1" "ip"`: {"", "IP/mask"}, | ||
`expect "1" "0-10"`: {""}, | ||
`expect "1" "10-10"`: {"", "range"}, | ||
`expect "1.1" "\\d+\\.\\d+"`: {""}, | ||
`expect 11 "\\d"`: {""}, | ||
`expect 11 "\\d+"`: {""}, | ||
`expect "abc" "^[a-z]+$"`: {""}, | ||
|
||
`expect 1 "int"`: {""}, | ||
`expect 1 "str"`: {"", "string expected"}, | ||
`expect 1 "string"`: {"", "string expected"}, | ||
`expect .i5 "int"`: {""}, | ||
`expect "5" "int"`: {""}, // hasInt | ||
`expect "aa" "int"`: {"", "int expected"}, | ||
|
||
`optional 1 "int"`: {""}, | ||
`optional .x "int"`: {""}, | ||
`optional .x "str"`: {""}, | ||
`optional .i5 "str"`: {""}, // corner case, although it hasInt everything is always a string | ||
} | ||
|
||
func render(templateS string, vars map[string]string) (string, error) { | ||
var err error | ||
buf := new(bytes.Buffer) | ||
ts := fmt.Sprintf("{{ %v }}", strings.Trim(templateS, "{} ")) | ||
tem, err := template.New("").Funcs(funcMap).Parse(ts) | ||
if err != nil { | ||
return "", fmt.Errorf("invalid template") | ||
} | ||
err = tem.Execute(buf, vars) | ||
return buf.String(), err | ||
} | ||
|
||
func TestRender1(t *testing.T) { | ||
|
||
l := map[string]string{ | ||
"i5": "5", | ||
"sA": "A", | ||
"sAAA": "aa.", | ||
"dot": ".", | ||
"space": " ", | ||
} | ||
|
||
for tem, exp := range test_set { | ||
res, err := render(tem, l) | ||
|
||
e := []string{fmt.Sprintf(`{{ %v }} = "%v", error=%v`, tem, res, err)} | ||
|
||
// Check value | ||
if res != exp[0] { | ||
e = append(e, fmt.Sprintf("- expected value = %v", exp[0])) | ||
} | ||
|
||
// Check errors | ||
if len(exp) > 1 { | ||
ee := fmt.Sprintf("- expected error with %s", exp[1]) | ||
if err == nil { | ||
e = append(e, ee) | ||
} else if !strings.Contains(err.Error(), exp[1]) { | ||
e = append(e, ee) | ||
} | ||
} else if err != nil { | ||
e = append(e, "- no error expected") | ||
} | ||
|
||
if len(e) > 1 { | ||
t.Error(strings.Join(e, "\n")) | ||
} | ||
} | ||
|
||
} |
Oops, something went wrong.