Support expansion of environment variables in value expressions and filenames.
diff --git a/load.go b/load.go index e543d40..3f6c414 100644 --- a/load.go +++ b/load.go
@@ -62,7 +62,12 @@ buff := make([]byte, 0, 4096) for _, filename := range filenames { - buf, err := ioutil.ReadFile(filename) + f, err := expandFilename(filename) + if err != nil { + return nil, err + } + + buf, err := ioutil.ReadFile(f) if err != nil { if ignoreMissing && os.IsNotExist(err) { // TODO(frank): should we log that we are skipping the file? @@ -87,6 +92,14 @@ return p } +// expandFilename expands ${ENV_VAR} expressions in a filename. +// If the environment variable does not exist then it will be replaced +// with an empty string. Malformed expressions like "${ENV_VAR" will +// be reported as error. +func expandFilename(filename string) (string, error) { + return expand(filename, make(map[string]bool), "${", "}", make(map[string]string)) +} + // Interprets a byte buffer either as an ISO-8859-1 or UTF-8 encoded string. // For ISO-8859-1 we can convert each byte straight into a rune since the // first 256 unicode code points cover ISO-8859-1.
diff --git a/load_test.go b/load_test.go index 0d9ef75..e2086a4 100644 --- a/load_test.go +++ b/load_test.go
@@ -8,6 +8,7 @@ "fmt" "io/ioutil" "os" + "strings" . "launchpad.net/gocheck" ) @@ -65,6 +66,15 @@ // ---------------------------------------------------------------------------- +func (s *LoadSuite) TestLoadExpandedFile(c *C) { + filename := s.makeFilePrefix(c, os.Getenv("USER"), "key=value") + filename = strings.Replace(filename, os.Getenv("USER"), "${USER}", -1) + p := MustLoadFile(filename, ISO_8859_1) + assertKeyValues(c, "", p, "key", "value") +} + +// ---------------------------------------------------------------------------- + func (s *LoadSuite) TestLoadFilesAndIgnoreMissing(c *C) { filename := s.makeFile(c, "key=value") filename2 := s.makeFile(c, "key2=value2") @@ -92,7 +102,13 @@ // ---------------------------------------------------------------------------- func (s *LoadSuite) makeFile(c *C, data string) string { - f, err := ioutil.TempFile("", "properties") + return s.makeFilePrefix(c, "properties", data) +} + +// ---------------------------------------------------------------------------- + +func (s *LoadSuite) makeFilePrefix(c *C, prefix, data string) string { + f, err := ioutil.TempFile("", prefix) if err != nil { fmt.Printf("ioutil.TempFile: %v", err) c.FailNow()
diff --git a/properties.go b/properties.go index 944b2ee..daa03db 100644 --- a/properties.go +++ b/properties.go
@@ -10,6 +10,7 @@ import ( "fmt" "io" + "os" "strconv" "strings" "unicode/utf8" @@ -171,44 +172,46 @@ return nil } -// expand recursively expands expressions of '(prefix)key(postfix)' to their corresponding values. -// The function keeps track of the keys that were already expanded and stops if it -// detects a circular reference or a malformed expression. func (p *Properties) expand(input string) (string, error) { // no pre/postfix -> nothing to expand if p.Prefix == "" && p.Postfix == "" { return input, nil } - return p.doExpand(input, make(map[string]bool)) + return expand(input, make(map[string]bool), p.Prefix, p.Postfix, p.m) } -func (p *Properties) doExpand(s string, keys map[string]bool) (string, error) { - a := strings.Index(s, p.Prefix) +// expand recursively expands expressions of '(prefix)key(postfix)' to their corresponding values. +// The function keeps track of the keys that were already expanded and stops if it +// detects a circular reference or a malformed expression of the form '(prefix)key'. +func expand(s string, keys map[string]bool, prefix, postfix string, values map[string]string) (string, error) { + a := strings.Index(s, prefix) if a == -1 { return s, nil } - b := strings.Index(s[a:], p.Postfix) + b := strings.Index(s[a:], postfix) if b == -1 { return "", fmt.Errorf("Malformed expression") } - key := s[a+len(p.Prefix) : b-len(p.Postfix)+1] + keyStart := a + len(prefix) + keyEnd := keyStart + b - len(postfix) - 1 + key := s[keyStart:keyEnd] if _, ok := keys[key]; ok { return "", fmt.Errorf("Circular reference") } - val, ok := p.m[key] + val, ok := values[key] if !ok { - val = "" + val = os.Getenv(key) } // remember that we've seen the key keys[key] = true - return p.doExpand(s[:a]+val+s[b+1:], keys) + return expand(s[:a]+val+s[a+b+1:], keys, prefix, postfix, values) } // encode encodes a UTF-8 string to ISO-8859-1 and escapes some characters.
diff --git a/properties_test.go b/properties_test.go index da11855..69e1cfc 100644 --- a/properties_test.go +++ b/properties_test.go
@@ -85,6 +85,7 @@ // expansion tests {"key=value\nkey2=${key}", "key", "value", "key2", "value"}, {"key=value\nkey2=${key}\nkey3=${key2}", "key", "value", "key2", "value", "key3", "value"}, + {"key=${USER}", "key", os.Getenv("USER")}, } // define error test cases in the form of @@ -240,7 +241,7 @@ // ---------------------------------------------------------------------------- -// TestBasic tests basic single key/value combinations with all possible +// TestBasic tests basic single key/value combinations with all possible // whitespace, delimiter and newline permutations. func (l *TestSuite) TestBasic(c *C) { testWhitespaceAndDelimiterCombinations(c, "key", "")