Платформа ЦРНП "Мирокод" для разработки проектов
https://git.mirocod.ru
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
833 lines
22 KiB
833 lines
22 KiB
// Copyright 2018 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" |
|
) |
|
|
|
const maxExpansionDepth = 64 |
|
|
|
// 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{}, |
|
} |
|
} |
|
|
|
// Load reads a buffer into the given Properties struct. |
|
func (p *Properties) Load(buf []byte, enc Encoding) error { |
|
l := &Loader{Encoding: enc, DisableExpansion: p.DisableExpansion} |
|
newProperties, err := l.LoadBytes(buf) |
|
if err != nil { |
|
return err |
|
} |
|
p.Merge(newProperties) |
|
return nil |
|
} |
|
|
|
// 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(key, 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 |
|
if !ok { |
|
p.k = append(p.k, key) |
|
} |
|
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(key, 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 |
|
} |
|
|
|
// SetValue sets property key to the default string value |
|
// as defined by fmt.Sprintf("%v"). |
|
func (p *Properties) SetValue(key string, value interface{}) error { |
|
_, _, err := p.Set(key, fmt.Sprintf("%v", value)) |
|
return err |
|
} |
|
|
|
// 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 |
|
} |
|
|
|
// Map returns a copy of the properties as a map. |
|
func (p *Properties) Map() map[string]string { |
|
m := make(map[string]string) |
|
for k, v := range p.m { |
|
m[k] = v |
|
} |
|
return m |
|
} |
|
|
|
// FilterFunc returns a copy of the properties which includes the values which passed all filters. |
|
func (p *Properties) FilterFunc(filters ...func(k, v string) bool) *Properties { |
|
pp := NewProperties() |
|
outer: |
|
for k, v := range p.m { |
|
for _, f := range filters { |
|
if !f(k, v) { |
|
continue outer |
|
} |
|
pp.Set(k, v) |
|
} |
|
} |
|
return pp |
|
} |
|
|
|
// ---------------------------------------------------------------------------- |
|
|
|
// 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 key, value := range p.m { |
|
if _, err := p.expand(key, value); err != nil { |
|
return err |
|
} |
|
} |
|
return nil |
|
} |
|
|
|
func (p *Properties) expand(key, input string) (string, error) { |
|
// no pre/postfix -> nothing to expand |
|
if p.Prefix == "" && p.Postfix == "" { |
|
return input, nil |
|
} |
|
|
|
return expand(input, []string{key}, 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 []string, prefix, postfix string, values map[string]string) (string, error) { |
|
if len(keys) > maxExpansionDepth { |
|
return "", fmt.Errorf("expansion too deep") |
|
} |
|
|
|
for { |
|
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) |
|
|
|
for _, k := range keys { |
|
if key == k { |
|
return "", fmt.Errorf("circular reference") |
|
} |
|
} |
|
|
|
val, ok := values[key] |
|
if !ok { |
|
val = os.Getenv(key) |
|
} |
|
new_val, err := expand(val, append(keys, key), prefix, postfix, values) |
|
if err != nil { |
|
return "", err |
|
} |
|
s = s[:start] + new_val + s[end+1:] |
|
} |
|
return s, nil |
|
} |
|
|
|
// 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) |
|
}
|
|
|