| // Copyright 2013-2014 Frank Schroeder. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package properties |
| |
| // BUG(frank): Set() does not check for invalid unicode literals since this is currently handled by the lexer. |
| // BUG(frank): Write() does not allow to configure the newline character. Therefore, on Windows LF is used. |
| |
| import ( |
| "fmt" |
| "io" |
| "log" |
| "os" |
| "strconv" |
| "strings" |
| "time" |
| "unicode/utf8" |
| ) |
| |
| // ErrorHandlerFunc defines the type of function which handles failures |
| // of the MustXXX() functions. An error handler function must exit |
| // the application after handling the error. |
| type ErrorHandlerFunc func(error) |
| |
| // ErrorHandler is the function which handles failures of the MustXXX() |
| // functions. The default is LogFatalHandler. |
| var ErrorHandler ErrorHandlerFunc = LogFatalHandler |
| |
| // LogFatalHandler handles the error by logging a fatal error and exiting. |
| func LogFatalHandler(err error) { |
| log.Fatal(err) |
| } |
| |
| // PanicHandler handles the error by panicking. |
| func PanicHandler(err error) { |
| panic(err) |
| } |
| |
| // ----------------------------------------------------------------------------- |
| |
| type Properties struct { |
| // Pre-/Postfix for property expansion. |
| Prefix string |
| Postfix string |
| |
| m map[string]string |
| } |
| |
| // NewProperties creates a new Properties struct with the default |
| // configuration for "${key}" expressions. |
| func NewProperties() *Properties { |
| return &Properties{ |
| Prefix: "${", |
| Postfix: "}", |
| m: make(map[string]string), |
| } |
| } |
| |
| // Get returns the expanded value for the given key if exists. |
| // Otherwise, ok is false. |
| func (p *Properties) Get(key string) (value string, ok bool) { |
| v, ok := p.m[key] |
| if !ok { |
| return "", false |
| } |
| |
| expanded, err := p.expand(v) |
| |
| // we guarantee that the expanded value is free of |
| // circular references and malformed expressions |
| // so we panic if we still get an error here. |
| if err != nil { |
| ErrorHandler(fmt.Errorf("%s in %q", err, key+" = "+v)) |
| } |
| |
| return expanded, true |
| } |
| |
| // MustGet returns the expanded value for the given key if exists. |
| // Otherwise, it panics. |
| func (p *Properties) MustGet(key string) string { |
| if v, ok := p.Get(key); ok { |
| return v |
| } |
| ErrorHandler(invalidKeyError(key)) |
| panic("ErrorHandler should exit") |
| } |
| |
| // ---------------------------------------------------------------------------- |
| |
| // GetBool checks if the expanded value is one of '1', 'yes', |
| // 'true' or 'on' if the key exists. The comparison is case-insensitive. |
| // If the key does not exist the default value is returned. |
| func (p *Properties) GetBool(key string, def bool) bool { |
| v, err := p.getBool(key) |
| if err != nil { |
| return def |
| } |
| return v |
| } |
| |
| // MustGetBool checks if the expanded value is one of '1', 'yes', |
| // 'true' or 'on' if the key exists. The comparison is case-insensitive. |
| // If the key does not exist the function panics. |
| func (p *Properties) MustGetBool(key string) bool { |
| v, err := p.getBool(key) |
| if err != nil { |
| ErrorHandler(err) |
| } |
| return v |
| } |
| |
| func (p *Properties) getBool(key string) (value bool, err error) { |
| if v, ok := p.Get(key); ok { |
| v = strings.ToLower(v) |
| return v == "1" || v == "true" || v == "yes" || v == "on", nil |
| } |
| return false, invalidKeyError(key) |
| } |
| |
| // ---------------------------------------------------------------------------- |
| |
| // GetDuration parses the expanded value as an time.Duration if the key exists. |
| // If key does not exist or the value cannot be parsed the default |
| // value is returned. |
| func (p *Properties) GetDuration(key string, def time.Duration) time.Duration { |
| v, err := p.getInt64(key) |
| if err != nil { |
| return def |
| } |
| return time.Duration(v) |
| } |
| |
| // MustGetDuration parses the expanded value as an time.Duration if the key exists. |
| // If key does not exist or the value cannot be parsed the function panics. |
| func (p *Properties) MustGetDuration(key string) time.Duration { |
| v, err := p.getInt64(key) |
| if err != nil { |
| ErrorHandler(err) |
| } |
| return time.Duration(v) |
| } |
| |
| // ---------------------------------------------------------------------------- |
| |
| // GetFloat64 parses the expanded value as a float64 if the key exists. |
| // If key does not exist or the value cannot be parsed the default |
| // value is returned. |
| func (p *Properties) GetFloat64(key string, def float64) float64 { |
| v, err := p.getFloat64(key) |
| if err != nil { |
| return def |
| } |
| return v |
| } |
| |
| // GetFloat64 parses the expanded value as a float64 if the key exists. |
| // If key does not exist or the value cannot be parsed the function panics. |
| func (p *Properties) MustGetFloat64(key string) float64 { |
| v, err := p.getFloat64(key) |
| if err != nil { |
| ErrorHandler(err) |
| } |
| return v |
| } |
| |
| func (p *Properties) getFloat64(key string) (value float64, err error) { |
| if v, ok := p.Get(key); ok { |
| value, err = strconv.ParseFloat(v, 64) |
| if err != nil { |
| return 0, err |
| } |
| return value, nil |
| } |
| return 0, invalidKeyError(key) |
| } |
| |
| // ---------------------------------------------------------------------------- |
| |
| // GetInt parses the expanded value as an int if the key exists. |
| // If key does not exist or the value cannot be parsed the default |
| // value is returned. If the value does not fit into an int the |
| // function panics with an out of range error. |
| func (p *Properties) GetInt(key string, def int) int { |
| v, err := p.getInt64(key) |
| if err != nil { |
| return def |
| } |
| return intRangeCheck(key, v) |
| } |
| |
| // MustGetInt parses the expanded value as an int if the key exists. |
| // If key does not exist or the value cannot be parsed the function panics. |
| // If the value does not fit into an int the function panics with |
| // an out of range error. |
| func (p *Properties) MustGetInt(key string) int { |
| v, err := p.getInt64(key) |
| if err != nil { |
| ErrorHandler(err) |
| } |
| return intRangeCheck(key, v) |
| } |
| |
| // ---------------------------------------------------------------------------- |
| |
| // GetInt64 parses the expanded value as an int64 if the key exists. |
| // If key does not exist or the value cannot be parsed the default |
| // value is returned. |
| func (p *Properties) GetInt64(key string, def int64) int64 { |
| v, err := p.getInt64(key) |
| if err != nil { |
| return def |
| } |
| return v |
| } |
| |
| // MustGetInt64 parses the expanded value as an int if the key exists. |
| // If key does not exist or the value cannot be parsed the function panics. |
| func (p *Properties) MustGetInt64(key string) int64 { |
| v, err := p.getInt64(key) |
| if err != nil { |
| ErrorHandler(err) |
| } |
| return v |
| } |
| |
| func (p *Properties) getInt64(key string) (value int64, err error) { |
| if v, ok := p.Get(key); ok { |
| value, err = strconv.ParseInt(v, 10, 64) |
| if err != nil { |
| return 0, err |
| } |
| return value, nil |
| } |
| return 0, invalidKeyError(key) |
| } |
| |
| // ---------------------------------------------------------------------------- |
| |
| // GetUint parses the expanded value as an uint if the key exists. |
| // If key does not exist or the value cannot be parsed the default |
| // value is returned. If the value does not fit into an int the |
| // function panics with an out of range error. |
| func (p *Properties) GetUint(key string, def uint) uint { |
| v, err := p.getUint64(key) |
| if err != nil { |
| return def |
| } |
| return uintRangeCheck(key, v) |
| } |
| |
| // MustGetUint parses the expanded value as an int if the key exists. |
| // If key does not exist or the value cannot be parsed the function panics. |
| // If the value does not fit into an int the function panics with |
| // an out of range error. |
| func (p *Properties) MustGetUint(key string) uint { |
| v, err := p.getUint64(key) |
| if err != nil { |
| ErrorHandler(err) |
| } |
| return uintRangeCheck(key, v) |
| } |
| |
| // ---------------------------------------------------------------------------- |
| |
| // GetUint64 parses the expanded value as an uint64 if the key exists. |
| // If key does not exist or the value cannot be parsed the default |
| // value is returned. |
| func (p *Properties) GetUint64(key string, def uint64) uint64 { |
| v, err := p.getUint64(key) |
| if err != nil { |
| return def |
| } |
| return v |
| } |
| |
| // MustGetUint64 parses the expanded value as an int if the key exists. |
| // If key does not exist or the value cannot be parsed the function panics. |
| func (p *Properties) MustGetUint64(key string) uint64 { |
| v, err := p.getUint64(key) |
| if err != nil { |
| ErrorHandler(err) |
| } |
| return v |
| } |
| |
| func (p *Properties) getUint64(key string) (value uint64, err error) { |
| if v, ok := p.Get(key); ok { |
| value, err = strconv.ParseUint(v, 10, 64) |
| if err != nil { |
| return 0, err |
| } |
| return value, nil |
| } |
| return 0, invalidKeyError(key) |
| } |
| |
| // ---------------------------------------------------------------------------- |
| |
| // GetString returns the expanded value for the given key if exists or |
| // the default value otherwise. |
| func (p *Properties) GetString(key, def string) string { |
| if v, ok := p.Get(key); ok { |
| return v |
| } |
| return def |
| } |
| |
| // MustGetString returns the expanded value for the given key if exists or |
| // panics otherwise. |
| func (p *Properties) MustGetString(key string) string { |
| if v, ok := p.Get(key); ok { |
| return v |
| } |
| ErrorHandler(invalidKeyError(key)) |
| panic("ErrorHandler should exit") |
| } |
| |
| // ---------------------------------------------------------------------------- |
| |
| // Len returns the number of keys. |
| func (p *Properties) Len() int { |
| return len(p.m) |
| } |
| |
| // Set sets the property key to the corresponding value. |
| // If a value for key existed before then ok is true and prev |
| // contains the previous value. If the value contains a |
| // circular reference or a malformed expression then |
| // an error is returned. |
| func (p *Properties) Set(key, value string) (prev string, ok bool, err error) { |
| _, err = p.expand(value) |
| if err != nil { |
| return "", false, err |
| } |
| |
| v, ok := p.Get(key) |
| p.m[key] = value |
| return v, ok, nil |
| } |
| |
| // String returns a string of all expanded 'key = value' pairs. |
| func (p *Properties) String() string { |
| var s string |
| for key, _ := range p.m { |
| value, _ := p.Get(key) |
| s = fmt.Sprintf("%s%s = %s\n", s, key, value) |
| } |
| return s |
| } |
| |
| // Write writes all unexpanded 'key = value' pairs to the given writer. |
| func (p *Properties) Write(w io.Writer, enc Encoding) (int, error) { |
| total := 0 |
| for key, value := range p.m { |
| s := fmt.Sprintf("%s = %s\n", encode(key, " :", enc), encode(value, "", enc)) |
| n, err := w.Write([]byte(s)) |
| if err != nil { |
| return total, err |
| } |
| total += n |
| } |
| return total, nil |
| } |
| |
| // ---------------------------------------------------------------------------- |
| |
| // check expands all values and returns an error if a circular reference or |
| // a malformed expression was found. |
| func (p *Properties) check() error { |
| for _, value := range p.m { |
| if _, err := p.expand(value); err != nil { |
| return err |
| } |
| } |
| return nil |
| } |
| |
| func (p *Properties) expand(input string) (string, error) { |
| // no pre/postfix -> nothing to expand |
| if p.Prefix == "" && p.Postfix == "" { |
| return input, nil |
| } |
| |
| return expand(input, make(map[string]bool), p.Prefix, p.Postfix, p.m) |
| } |
| |
| // 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) { |
| start := strings.Index(s, prefix) |
| if start == -1 { |
| return s, nil |
| } |
| |
| keyStart := start + len(prefix) |
| keyLen := strings.Index(s[keyStart:], postfix) |
| if keyLen == -1 { |
| return "", fmt.Errorf("Malformed expression") |
| } |
| |
| end := keyStart + keyLen + len(postfix) - 1 |
| key := s[keyStart : keyStart+keyLen] |
| |
| // fmt.Printf("s:%q pp:%q start:%d end:%d keyStart:%d keyLen:%d key:%q\n", s, prefix + "..." + postfix, start, end, keyStart, keyLen, key) |
| |
| if _, ok := keys[key]; ok { |
| return "", fmt.Errorf("Circular reference") |
| } |
| |
| val, ok := values[key] |
| if !ok { |
| val = os.Getenv(key) |
| } |
| |
| // remember that we've seen the key |
| keys[key] = true |
| |
| return expand(s[:start]+val+s[end+1:], keys, prefix, postfix, values) |
| } |
| |
| // encode encodes a UTF-8 string to ISO-8859-1 and escapes some characters. |
| func encode(s string, special string, enc Encoding) string { |
| switch enc { |
| case UTF8: |
| return encodeUtf8(s, special) |
| case ISO_8859_1: |
| return encodeIso(s, special) |
| default: |
| panic(fmt.Sprintf("Unsupported encoding %v", enc)) |
| } |
| } |
| |
| func encodeUtf8(s string, special string) string { |
| v := "" |
| for pos := 0; pos < len(s); { |
| r, w := utf8.DecodeRuneInString(s[pos:]) |
| pos += w |
| v += escape(r, special) |
| } |
| return v |
| } |
| |
| func encodeIso(s string, special string) string { |
| var r rune |
| var w int |
| var v string |
| for pos := 0; pos < len(s); { |
| switch r, w = utf8.DecodeRuneInString(s[pos:]); { |
| case r < 1<<8: // single byte rune -> escape special chars only |
| v += escape(r, special) |
| case r < 1<<16: // two byte rune -> unicode literal |
| v += fmt.Sprintf("\\u%04x", r) |
| default: // more than two bytes per rune -> can't encode |
| v += "?" |
| } |
| pos += w |
| } |
| return v |
| } |
| |
| func escape(r rune, special string) string { |
| switch r { |
| case '\f': |
| return "\\f" |
| case '\n': |
| return "\\n" |
| case '\r': |
| return "\\r" |
| case '\t': |
| return "\\t" |
| default: |
| if strings.ContainsRune(special, r) { |
| return "\\" + string(r) |
| } |
| return string(r) |
| } |
| } |
| |
| func invalidKeyError(key string) error { |
| return fmt.Errorf("invalid key: %s", key) |
| } |