expand patterns
This commit is contained in:
parent
d0bed6e6ce
commit
be5568d530
187
pkg/config/expand.go
Normal file
187
pkg/config/expand.go
Normal file
@ -0,0 +1,187 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Compile input pattern regex
|
||||
var (
|
||||
expandMatchPlaceholder = regexp.MustCompile(`(?U:{.*}+?)`)
|
||||
expandMatchWildcardShorthard = regexp.MustCompile(`(?U:{{.*}}+?)`)
|
||||
)
|
||||
|
||||
// Extract all matches from the input string.
|
||||
// The pattern to find is {INPUT}. The input string
|
||||
// itself can contain new matches.
|
||||
func expandFindPlaceholders(s string) []string {
|
||||
|
||||
// Find all matches
|
||||
results := expandMatchPlaceholder.FindAllString(s, -1)
|
||||
if len(results) == 0 {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
matches := []string{}
|
||||
for _, result := range results {
|
||||
key := expandGetKey(result)
|
||||
subP := expandFindPlaceholders(key)
|
||||
matches = append(matches, result)
|
||||
matches = append(matches, subP...)
|
||||
}
|
||||
|
||||
return matches
|
||||
}
|
||||
|
||||
// Extract the key from the placeholder
|
||||
func expandGetKey(s string) string {
|
||||
// Strip the enclosing curly braces
|
||||
s = strings.TrimPrefix(s, "{")
|
||||
s = strings.TrimSuffix(s, "}")
|
||||
return s
|
||||
}
|
||||
|
||||
// ExpandMap holds the current state of variables
|
||||
type ExpandMap map[string]string
|
||||
|
||||
// Retrieve a set of matching variables, by iterating variables.
|
||||
// Whenever a key matches the wildcard, the prefix is removed.
|
||||
// Example:
|
||||
// pattern = "AS*", key = "AS2342", value = "2342"
|
||||
func (e ExpandMap) matchWildcard(pattern string) []string {
|
||||
matches := []string{}
|
||||
|
||||
// Strip the wildcard from the pattern.
|
||||
pattern = strings.TrimSuffix(pattern, "*")
|
||||
|
||||
// Iterate variables and add match to result set
|
||||
for k, _ := range e {
|
||||
if strings.HasPrefix(k, pattern) {
|
||||
key := strings.TrimPrefix(k, pattern)
|
||||
matches = append(matches, key)
|
||||
}
|
||||
}
|
||||
return matches
|
||||
}
|
||||
|
||||
// Get all substitutions for a given key.
|
||||
// This method will return an error, if a placeholder
|
||||
// does not match.
|
||||
func (e ExpandMap) getSubstitutions(key string) []string {
|
||||
// Check if the placeholder is a wildcard
|
||||
if strings.HasSuffix(key, "*") {
|
||||
return e.matchWildcard(key)
|
||||
}
|
||||
|
||||
// Check if the placeholder is direct match
|
||||
if val, ok := e[key]; ok {
|
||||
return []string{val}
|
||||
}
|
||||
|
||||
return []string{}
|
||||
}
|
||||
|
||||
// Get placeholder level. This is the number of opening
|
||||
// curly braces in the placeholder.
|
||||
func expandGetLevel(s string) int {
|
||||
level := 0
|
||||
for _, c := range s {
|
||||
if c == '{' {
|
||||
level++
|
||||
}
|
||||
}
|
||||
return level
|
||||
}
|
||||
|
||||
// Preprocess input string and resolve syntactic sugar.
|
||||
// Replace {{VAR}} with {VAR{VAR}} to make it easier
|
||||
// to access the wildcard value.
|
||||
func expandPreprocess(s string) string {
|
||||
// Find all access shorthands and replace them
|
||||
// with the full syntax
|
||||
results := expandMatchWildcardShorthard.FindAllString(s, -1)
|
||||
for _, match := range results {
|
||||
// Wildcard {{KEY*}} -> KEY
|
||||
key := match[2 : len(match)-3]
|
||||
expr := fmt.Sprintf("{%s{%s*}}", key, key)
|
||||
s = strings.Replace(s, match, expr, -1)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Expand variables by recursive substitution and expansion
|
||||
func (e ExpandMap) Expand(s string) ([]string, error) {
|
||||
// Preprocess syntactic sugar: replace {{VAR}}
|
||||
// with {VAR{VAR}}
|
||||
s = expandPreprocess(s)
|
||||
|
||||
// Find all placeholders and substitute them
|
||||
placeholders := expandFindPlaceholders(s)
|
||||
if len(placeholders) == 0 {
|
||||
return []string{s}, nil
|
||||
}
|
||||
|
||||
// Find substitutions for each placeholder
|
||||
substitutions := map[string][]string{}
|
||||
for _, p := range placeholders {
|
||||
key := expandGetKey(p)
|
||||
subs := e.getSubstitutions(key)
|
||||
if len(subs) == 0 {
|
||||
level := expandGetLevel(p)
|
||||
if level == 1 {
|
||||
err := fmt.Errorf("No substitution for %s in '%s'", p, s)
|
||||
return []string{}, err
|
||||
}
|
||||
continue
|
||||
}
|
||||
substitutions[p] = subs
|
||||
}
|
||||
|
||||
// Apply substitutions
|
||||
subsRes := []string{s}
|
||||
for p, subs := range substitutions {
|
||||
subsExp := []string{}
|
||||
for _, s := range subsRes {
|
||||
for _, sub := range subs {
|
||||
res := strings.Replace(s, p, sub, -1)
|
||||
subsExp = append(subsExp, res)
|
||||
}
|
||||
}
|
||||
subsRes = subsExp
|
||||
}
|
||||
|
||||
// Expand recursively
|
||||
results := []string{}
|
||||
for _, s := range subsRes {
|
||||
res, err := e.Expand(s)
|
||||
if err != nil {
|
||||
return []string{}, err
|
||||
}
|
||||
results = append(results, res...)
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
// Add a new variable to the map. Key and value are
|
||||
// expanded.
|
||||
func (e ExpandMap) AddExpr(expr string) error {
|
||||
// Expand expression
|
||||
res, err := e.Expand(expr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, exp := range res {
|
||||
// Split key and value
|
||||
parts := strings.SplitN(exp, "=", 2)
|
||||
if len(parts) != 2 {
|
||||
return fmt.Errorf("Invalid expression '%s'", expr)
|
||||
}
|
||||
key := strings.TrimSpace(parts[0])
|
||||
val := strings.TrimSpace(parts[1])
|
||||
e[key] = val
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
117
pkg/config/expand_test.go
Normal file
117
pkg/config/expand_test.go
Normal file
@ -0,0 +1,117 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Text variable pattern matching
|
||||
func TestExpandMatch(t *testing.T) {
|
||||
exp := ExpandMap{
|
||||
"AS2342": "",
|
||||
"AS1111": "",
|
||||
"FOOBAR": "foo",
|
||||
}
|
||||
|
||||
matches := exp.matchWildcard("AS*")
|
||||
if len(matches) != 2 {
|
||||
t.Errorf("Expected 2 matches, got %d", len(matches))
|
||||
}
|
||||
|
||||
for _, m := range matches {
|
||||
t.Log("Match wildcard:", m)
|
||||
}
|
||||
}
|
||||
|
||||
// Test variable expansion / substitution
|
||||
func TestFindPlaceholders(t *testing.T) {
|
||||
s := "{FOO} BAR {AS{AS*}}"
|
||||
placeholders := expandFindPlaceholders(s)
|
||||
if len(placeholders) != 3 {
|
||||
t.Errorf("Expected 3 placeholders, got %d", len(placeholders))
|
||||
}
|
||||
t.Log(placeholders)
|
||||
}
|
||||
|
||||
// Test variable expansion / substitution
|
||||
func TestExpand(t *testing.T) {
|
||||
s := "{FOO} BAR {AS{AS*}} AS {AS*}"
|
||||
exp := ExpandMap{
|
||||
"AS2342": "AS2342",
|
||||
"AS1111": "AS1111",
|
||||
"FOO": "foo",
|
||||
}
|
||||
|
||||
results, err := exp.Expand(s)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
t.Log(results)
|
||||
}
|
||||
|
||||
func TestExpandErr(t *testing.T) {
|
||||
s := "{FOO} BAR {AS{AS*}} AS {AS*} {UNKNOWN}"
|
||||
exp := ExpandMap{
|
||||
"AS2342": "AS2342",
|
||||
"AS1111": "AS1111",
|
||||
"FOO": "foo",
|
||||
"FN": "fn",
|
||||
"FA": "fa",
|
||||
}
|
||||
|
||||
_, err := exp.Expand(s)
|
||||
t.Log(err)
|
||||
if err == nil {
|
||||
t.Error("Expected error, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestExpandPreprocess(t *testing.T) {
|
||||
s := "FOO {FOO} {{AS*}} {F*} {{F*}} {X{X*}}"
|
||||
expect := "FOO {FOO} {AS{AS*}} {F*} {F{F*}} {X{X*}}"
|
||||
s = expandPreprocess(s)
|
||||
if s != expect {
|
||||
t.Errorf("Expected '%s', got '%s'", expect, s)
|
||||
}
|
||||
t.Log(s)
|
||||
|
||||
s = "TEST {{FN}}"
|
||||
s = expandPreprocess(s)
|
||||
t.Log(s)
|
||||
|
||||
}
|
||||
|
||||
func TestExpandAddExpr(t *testing.T) {
|
||||
e := ExpandMap{
|
||||
"FOO": "foo23",
|
||||
"BAR": "bar42",
|
||||
"bar42": "BAM",
|
||||
}
|
||||
|
||||
if err := e.AddExpr("FOOBAR = {FOO}{BAR}{{BAR}}"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(e)
|
||||
|
||||
if e["FOOBAR"] != "foo23bar42BAM" {
|
||||
t.Error("Expected 'foo23bar42BAM', got", e["FOOBAR"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestExpandBgpCommunities(t *testing.T) {
|
||||
e := ExpandMap{
|
||||
"ASRS01": "6695",
|
||||
"ASRS02": "4617",
|
||||
"SW1001": "edge01.fra2",
|
||||
"SW1002": "edge01.fra6",
|
||||
"SW2038": "edge01.nyc1",
|
||||
}
|
||||
|
||||
// Some large communities:
|
||||
expr := "{{AS*}}:911:{SW*} = Redistribute to {{SW*}}"
|
||||
exp, err := e.Expand(expr)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(exp)
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user