|  | // Copyright 2017 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" | 
|  | "regexp" | 
|  | "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 | 
|  |  | 
|  | // LogHandlerFunc defines the function prototype for logging errors. | 
|  | type LogHandlerFunc func(fmt string, args ...interface{}) | 
|  |  | 
|  | // LogPrintf defines a log handler which uses log.Printf. | 
|  | var LogPrintf LogHandlerFunc = log.Printf | 
|  |  | 
|  | // 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) | 
|  | } | 
|  |  | 
|  | // ----------------------------------------------------------------------------- | 
|  |  | 
|  | // A Properties contains the key/value pairs from the properties input. | 
|  | // All values are stored in unexpanded form and are expanded at runtime | 
|  | type Properties struct { | 
|  | // Pre-/Postfix for property expansion. | 
|  | Prefix  string | 
|  | Postfix string | 
|  |  | 
|  | // DisableExpansion controls the expansion of properties on Get() | 
|  | // and the check for circular references on Set(). When set to | 
|  | // true Properties behaves like a simple key/value store and does | 
|  | // not check for circular references on Get() or on Set(). | 
|  | DisableExpansion bool | 
|  |  | 
|  | // Stores the key/value pairs | 
|  | m map[string]string | 
|  |  | 
|  | // Stores the comments per key. | 
|  | c map[string][]string | 
|  |  | 
|  | // Stores the keys in order of appearance. | 
|  | k []string | 
|  | } | 
|  |  | 
|  | // NewProperties creates a new Properties struct with the default | 
|  | // configuration for "${key}" expressions. | 
|  | func NewProperties() *Properties { | 
|  | return &Properties{ | 
|  | Prefix:  "${", | 
|  | Postfix: "}", | 
|  | m:       map[string]string{}, | 
|  | c:       map[string][]string{}, | 
|  | k:       []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 p.DisableExpansion { | 
|  | return v, ok | 
|  | } | 
|  | 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") | 
|  | } | 
|  |  | 
|  | // ---------------------------------------------------------------------------- | 
|  |  | 
|  | // ClearComments removes the comments for all keys. | 
|  | func (p *Properties) ClearComments() { | 
|  | p.c = map[string][]string{} | 
|  | } | 
|  |  | 
|  | // ---------------------------------------------------------------------------- | 
|  |  | 
|  | // GetComment returns the last comment before the given key or an empty string. | 
|  | func (p *Properties) GetComment(key string) string { | 
|  | comments, ok := p.c[key] | 
|  | if !ok || len(comments) == 0 { | 
|  | return "" | 
|  | } | 
|  | return comments[len(comments)-1] | 
|  | } | 
|  |  | 
|  | // ---------------------------------------------------------------------------- | 
|  |  | 
|  | // GetComments returns all comments that appeared before the given key or nil. | 
|  | func (p *Properties) GetComments(key string) []string { | 
|  | if comments, ok := p.c[key]; ok { | 
|  | return comments | 
|  | } | 
|  | return nil | 
|  | } | 
|  |  | 
|  | // ---------------------------------------------------------------------------- | 
|  |  | 
|  | // SetComment sets the comment for the key. | 
|  | func (p *Properties) SetComment(key, comment string) { | 
|  | p.c[key] = []string{comment} | 
|  | } | 
|  |  | 
|  | // ---------------------------------------------------------------------------- | 
|  |  | 
|  | // SetComments sets the comments for the key. If the comments are nil then | 
|  | // all comments for this key are deleted. | 
|  | func (p *Properties) SetComments(key string, comments []string) { | 
|  | if comments == nil { | 
|  | delete(p.c, key) | 
|  | return | 
|  | } | 
|  | p.c[key] = comments | 
|  | } | 
|  |  | 
|  | // ---------------------------------------------------------------------------- | 
|  |  | 
|  | // 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 { | 
|  | return boolVal(v), nil | 
|  | } | 
|  | return false, invalidKeyError(key) | 
|  | } | 
|  |  | 
|  | func boolVal(v string) bool { | 
|  | v = strings.ToLower(v) | 
|  | return v == "1" || v == "true" || v == "yes" || v == "on" | 
|  | } | 
|  |  | 
|  | // ---------------------------------------------------------------------------- | 
|  |  | 
|  | // GetDuration parses the expanded value as an time.Duration (in ns) if the | 
|  | // key exists. If key does not exist or the value cannot be parsed the default | 
|  | // value is returned. In almost all cases you want to use GetParsedDuration(). | 
|  | 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 (in ns) if | 
|  | // the key exists. If key does not exist or the value cannot be parsed the | 
|  | // function panics. In almost all cases you want to use MustGetParsedDuration(). | 
|  | func (p *Properties) MustGetDuration(key string) time.Duration { | 
|  | v, err := p.getInt64(key) | 
|  | if err != nil { | 
|  | ErrorHandler(err) | 
|  | } | 
|  | return time.Duration(v) | 
|  | } | 
|  |  | 
|  | // ---------------------------------------------------------------------------- | 
|  |  | 
|  | // GetParsedDuration parses the expanded value with time.ParseDuration() if the key exists. | 
|  | // If key does not exist or the value cannot be parsed the default | 
|  | // value is returned. | 
|  | func (p *Properties) GetParsedDuration(key string, def time.Duration) time.Duration { | 
|  | s, ok := p.Get(key) | 
|  | if !ok { | 
|  | return def | 
|  | } | 
|  | v, err := time.ParseDuration(s) | 
|  | if err != nil { | 
|  | return def | 
|  | } | 
|  | return v | 
|  | } | 
|  |  | 
|  | // MustGetParsedDuration parses the expanded value with time.ParseDuration() if the key exists. | 
|  | // If key does not exist or the value cannot be parsed the function panics. | 
|  | func (p *Properties) MustGetParsedDuration(key string) time.Duration { | 
|  | s, ok := p.Get(key) | 
|  | if !ok { | 
|  | ErrorHandler(invalidKeyError(key)) | 
|  | } | 
|  | v, err := time.ParseDuration(s) | 
|  | if err != nil { | 
|  | ErrorHandler(err) | 
|  | } | 
|  | 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 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 | 
|  | } | 
|  |  | 
|  | // MustGetFloat64 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") | 
|  | } | 
|  |  | 
|  | // ---------------------------------------------------------------------------- | 
|  |  | 
|  | // Filter returns a new properties object which contains all properties | 
|  | // for which the key matches the pattern. | 
|  | func (p *Properties) Filter(pattern string) (*Properties, error) { | 
|  | re, err := regexp.Compile(pattern) | 
|  | if err != nil { | 
|  | return nil, err | 
|  | } | 
|  |  | 
|  | return p.FilterRegexp(re), nil | 
|  | } | 
|  |  | 
|  | // FilterRegexp returns a new properties object which contains all properties | 
|  | // for which the key matches the regular expression. | 
|  | func (p *Properties) FilterRegexp(re *regexp.Regexp) *Properties { | 
|  | pp := NewProperties() | 
|  | for _, k := range p.k { | 
|  | if re.MatchString(k) { | 
|  | // TODO(fs): we are ignoring the error which flags a circular reference. | 
|  | // TODO(fs): since we are just copying a subset of keys this cannot happen (fingers crossed) | 
|  | pp.Set(k, p.m[k]) | 
|  | } | 
|  | } | 
|  | return pp | 
|  | } | 
|  |  | 
|  | // FilterPrefix returns a new properties object with a subset of all keys | 
|  | // with the given prefix. | 
|  | func (p *Properties) FilterPrefix(prefix string) *Properties { | 
|  | pp := NewProperties() | 
|  | for _, k := range p.k { | 
|  | if strings.HasPrefix(k, prefix) { | 
|  | // TODO(fs): we are ignoring the error which flags a circular reference. | 
|  | // TODO(fs): since we are just copying a subset of keys this cannot happen (fingers crossed) | 
|  | pp.Set(k, p.m[k]) | 
|  | } | 
|  | } | 
|  | return pp | 
|  | } | 
|  |  | 
|  | // FilterStripPrefix returns a new properties object with a subset of all keys | 
|  | // with the given prefix and the prefix removed from the keys. | 
|  | func (p *Properties) FilterStripPrefix(prefix string) *Properties { | 
|  | pp := NewProperties() | 
|  | n := len(prefix) | 
|  | for _, k := range p.k { | 
|  | if len(k) > len(prefix) && strings.HasPrefix(k, prefix) { | 
|  | // TODO(fs): we are ignoring the error which flags a circular reference. | 
|  | // TODO(fs): since we are modifying keys I am not entirely sure whether we can create a circular reference | 
|  | // TODO(fs): this function should probably return an error but the signature is fixed | 
|  | pp.Set(k[n:], p.m[k]) | 
|  | } | 
|  | } | 
|  | return pp | 
|  | } | 
|  |  | 
|  | // Len returns the number of keys. | 
|  | func (p *Properties) Len() int { | 
|  | return len(p.m) | 
|  | } | 
|  |  | 
|  | // Keys returns all keys in the same order as in the input. | 
|  | func (p *Properties) Keys() []string { | 
|  | keys := make([]string, len(p.k)) | 
|  | copy(keys, p.k) | 
|  | return keys | 
|  | } | 
|  |  | 
|  | // 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. | 
|  | // An empty key is silently ignored. | 
|  | func (p *Properties) Set(key, value string) (prev string, ok bool, err error) { | 
|  | if key == "" { | 
|  | return "", false, nil | 
|  | } | 
|  |  | 
|  | // if expansion is disabled we allow circular references | 
|  | if p.DisableExpansion { | 
|  | prev, ok = p.Get(key) | 
|  | p.m[key] = value | 
|  | return prev, ok, nil | 
|  | } | 
|  |  | 
|  | // to check for a circular reference we temporarily need | 
|  | // to set the new value. If there is an error then revert | 
|  | // to the previous state. Only if all tests are successful | 
|  | // then we add the key to the p.k list. | 
|  | prev, ok = p.Get(key) | 
|  | p.m[key] = value | 
|  |  | 
|  | // now check for a circular reference | 
|  | _, err = p.expand(value) | 
|  | if err != nil { | 
|  |  | 
|  | // revert to the previous state | 
|  | if ok { | 
|  | p.m[key] = prev | 
|  | } else { | 
|  | delete(p.m, key) | 
|  | } | 
|  |  | 
|  | return "", false, err | 
|  | } | 
|  |  | 
|  | if !ok { | 
|  | p.k = append(p.k, key) | 
|  | } | 
|  |  | 
|  | return prev, ok, nil | 
|  | } | 
|  |  | 
|  | // MustSet 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. An empty key is silently ignored. | 
|  | func (p *Properties) MustSet(key, value string) (prev string, ok bool) { | 
|  | prev, ok, err := p.Set(key, value) | 
|  | if err != nil { | 
|  | ErrorHandler(err) | 
|  | } | 
|  | return prev, ok | 
|  | } | 
|  |  | 
|  | // String returns a string of all expanded 'key = value' pairs. | 
|  | func (p *Properties) String() string { | 
|  | var s string | 
|  | for _, key := range p.k { | 
|  | 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. | 
|  | // Write returns the number of bytes written and any write error encountered. | 
|  | func (p *Properties) Write(w io.Writer, enc Encoding) (n int, err error) { | 
|  | return p.WriteComment(w, "", enc) | 
|  | } | 
|  |  | 
|  | // WriteComment writes all unexpanced 'key = value' pairs to the given writer. | 
|  | // If prefix is not empty then comments are written with a blank line and the | 
|  | // given prefix. The prefix should be either "# " or "! " to be compatible with | 
|  | // the properties file format. Otherwise, the properties parser will not be | 
|  | // able to read the file back in. It returns the number of bytes written and | 
|  | // any write error encountered. | 
|  | func (p *Properties) WriteComment(w io.Writer, prefix string, enc Encoding) (n int, err error) { | 
|  | var x int | 
|  |  | 
|  | for _, key := range p.k { | 
|  | value := p.m[key] | 
|  |  | 
|  | if prefix != "" { | 
|  | if comments, ok := p.c[key]; ok { | 
|  | // don't print comments if they are all empty | 
|  | allEmpty := true | 
|  | for _, c := range comments { | 
|  | if c != "" { | 
|  | allEmpty = false | 
|  | break | 
|  | } | 
|  | } | 
|  |  | 
|  | if !allEmpty { | 
|  | // add a blank line between entries but not at the top | 
|  | if len(comments) > 0 && n > 0 { | 
|  | x, err = fmt.Fprintln(w) | 
|  | if err != nil { | 
|  | return | 
|  | } | 
|  | n += x | 
|  | } | 
|  |  | 
|  | for _, c := range comments { | 
|  | x, err = fmt.Fprintf(w, "%s%s\n", prefix, encode(c, "", enc)) | 
|  | if err != nil { | 
|  | return | 
|  | } | 
|  | n += x | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | x, err = fmt.Fprintf(w, "%s = %s\n", encode(key, " :", enc), encode(value, "", enc)) | 
|  | if err != nil { | 
|  | return | 
|  | } | 
|  | n += x | 
|  | } | 
|  | return | 
|  | } | 
|  |  | 
|  | // ---------------------------------------------------------------------------- | 
|  |  | 
|  | // Delete removes the key and its comments. | 
|  | func (p *Properties) Delete(key string) { | 
|  | delete(p.m, key) | 
|  | delete(p.c, key) | 
|  | newKeys := []string{} | 
|  | for _, k := range p.k { | 
|  | if k != key { | 
|  | newKeys = append(newKeys, k) | 
|  | } | 
|  | } | 
|  | p.k = newKeys | 
|  | } | 
|  |  | 
|  | // Merge merges properties, comments and keys from other *Properties into p | 
|  | func (p *Properties) Merge(other *Properties) { | 
|  | for k, v := range other.m { | 
|  | p.m[k] = v | 
|  | } | 
|  | for k, v := range other.c { | 
|  | p.c[k] = v | 
|  | } | 
|  |  | 
|  | outer: | 
|  | for _, otherKey := range other.k { | 
|  | for _, key := range p.k { | 
|  | if otherKey == key { | 
|  | continue outer | 
|  | } | 
|  | } | 
|  | p.k = append(p.k, otherKey) | 
|  | } | 
|  | } | 
|  |  | 
|  | // ---------------------------------------------------------------------------- | 
|  |  | 
|  | // 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("unknown property: %s", key) | 
|  | } |