1// Copyright 2018 Frank Schroeder. All rights reserved. 2// Use of this source code is governed by a BSD-style 3// license that can be found in the LICENSE file. 4 5package properties 6 7// BUG(frank): Set() does not check for invalid unicode literals since this is currently handled by the lexer. 8// BUG(frank): Write() does not allow to configure the newline character. Therefore, on Windows LF is used. 9 10import ( 11 "fmt" 12 "io" 13 "log" 14 "os" 15 "regexp" 16 "strconv" 17 "strings" 18 "time" 19 "unicode/utf8" 20) 21 22const maxExpansionDepth = 64 23 24// ErrorHandlerFunc defines the type of function which handles failures 25// of the MustXXX() functions. An error handler function must exit 26// the application after handling the error. 27type ErrorHandlerFunc func(error) 28 29// ErrorHandler is the function which handles failures of the MustXXX() 30// functions. The default is LogFatalHandler. 31var ErrorHandler ErrorHandlerFunc = LogFatalHandler 32 33// LogHandlerFunc defines the function prototype for logging errors. 34type LogHandlerFunc func(fmt string, args ...interface{}) 35 36// LogPrintf defines a log handler which uses log.Printf. 37var LogPrintf LogHandlerFunc = log.Printf 38 39// LogFatalHandler handles the error by logging a fatal error and exiting. 40func LogFatalHandler(err error) { 41 log.Fatal(err) 42} 43 44// PanicHandler handles the error by panicking. 45func PanicHandler(err error) { 46 panic(err) 47} 48 49// ----------------------------------------------------------------------------- 50 51// A Properties contains the key/value pairs from the properties input. 52// All values are stored in unexpanded form and are expanded at runtime 53type Properties struct { 54 // Pre-/Postfix for property expansion. 55 Prefix string 56 Postfix string 57 58 // DisableExpansion controls the expansion of properties on Get() 59 // and the check for circular references on Set(). When set to 60 // true Properties behaves like a simple key/value store and does 61 // not check for circular references on Get() or on Set(). 62 DisableExpansion bool 63 64 // Stores the key/value pairs 65 m map[string]string 66 67 // Stores the comments per key. 68 c map[string][]string 69 70 // Stores the keys in order of appearance. 71 k []string 72} 73 74// NewProperties creates a new Properties struct with the default 75// configuration for "${key}" expressions. 76func NewProperties() *Properties { 77 return &Properties{ 78 Prefix: "${", 79 Postfix: "}", 80 m: map[string]string{}, 81 c: map[string][]string{}, 82 k: []string{}, 83 } 84} 85 86// Load reads a buffer into the given Properties struct. 87func (p *Properties) Load(buf []byte, enc Encoding) error { 88 l := &Loader{Encoding: enc, DisableExpansion: p.DisableExpansion} 89 newProperties, err := l.LoadBytes(buf) 90 if err != nil { 91 return err 92 } 93 p.Merge(newProperties) 94 return nil 95} 96 97// Get returns the expanded value for the given key if exists. 98// Otherwise, ok is false. 99func (p *Properties) Get(key string) (value string, ok bool) { 100 v, ok := p.m[key] 101 if p.DisableExpansion { 102 return v, ok 103 } 104 if !ok { 105 return "", false 106 } 107 108 expanded, err := p.expand(key, v) 109 110 // we guarantee that the expanded value is free of 111 // circular references and malformed expressions 112 // so we panic if we still get an error here. 113 if err != nil { 114 ErrorHandler(fmt.Errorf("%s in %q", err, key+" = "+v)) 115 } 116 117 return expanded, true 118} 119 120// MustGet returns the expanded value for the given key if exists. 121// Otherwise, it panics. 122func (p *Properties) MustGet(key string) string { 123 if v, ok := p.Get(key); ok { 124 return v 125 } 126 ErrorHandler(invalidKeyError(key)) 127 panic("ErrorHandler should exit") 128} 129 130// ---------------------------------------------------------------------------- 131 132// ClearComments removes the comments for all keys. 133func (p *Properties) ClearComments() { 134 p.c = map[string][]string{} 135} 136 137// ---------------------------------------------------------------------------- 138 139// GetComment returns the last comment before the given key or an empty string. 140func (p *Properties) GetComment(key string) string { 141 comments, ok := p.c[key] 142 if !ok || len(comments) == 0 { 143 return "" 144 } 145 return comments[len(comments)-1] 146} 147 148// ---------------------------------------------------------------------------- 149 150// GetComments returns all comments that appeared before the given key or nil. 151func (p *Properties) GetComments(key string) []string { 152 if comments, ok := p.c[key]; ok { 153 return comments 154 } 155 return nil 156} 157 158// ---------------------------------------------------------------------------- 159 160// SetComment sets the comment for the key. 161func (p *Properties) SetComment(key, comment string) { 162 p.c[key] = []string{comment} 163} 164 165// ---------------------------------------------------------------------------- 166 167// SetComments sets the comments for the key. If the comments are nil then 168// all comments for this key are deleted. 169func (p *Properties) SetComments(key string, comments []string) { 170 if comments == nil { 171 delete(p.c, key) 172 return 173 } 174 p.c[key] = comments 175} 176 177// ---------------------------------------------------------------------------- 178 179// GetBool checks if the expanded value is one of '1', 'yes', 180// 'true' or 'on' if the key exists. The comparison is case-insensitive. 181// If the key does not exist the default value is returned. 182func (p *Properties) GetBool(key string, def bool) bool { 183 v, err := p.getBool(key) 184 if err != nil { 185 return def 186 } 187 return v 188} 189 190// MustGetBool checks if the expanded value is one of '1', 'yes', 191// 'true' or 'on' if the key exists. The comparison is case-insensitive. 192// If the key does not exist the function panics. 193func (p *Properties) MustGetBool(key string) bool { 194 v, err := p.getBool(key) 195 if err != nil { 196 ErrorHandler(err) 197 } 198 return v 199} 200 201func (p *Properties) getBool(key string) (value bool, err error) { 202 if v, ok := p.Get(key); ok { 203 return boolVal(v), nil 204 } 205 return false, invalidKeyError(key) 206} 207 208func boolVal(v string) bool { 209 v = strings.ToLower(v) 210 return v == "1" || v == "true" || v == "yes" || v == "on" 211} 212 213// ---------------------------------------------------------------------------- 214 215// GetDuration parses the expanded value as an time.Duration (in ns) if the 216// key exists. If key does not exist or the value cannot be parsed the default 217// value is returned. In almost all cases you want to use GetParsedDuration(). 218func (p *Properties) GetDuration(key string, def time.Duration) time.Duration { 219 v, err := p.getInt64(key) 220 if err != nil { 221 return def 222 } 223 return time.Duration(v) 224} 225 226// MustGetDuration parses the expanded value as an time.Duration (in ns) if 227// the key exists. If key does not exist or the value cannot be parsed the 228// function panics. In almost all cases you want to use MustGetParsedDuration(). 229func (p *Properties) MustGetDuration(key string) time.Duration { 230 v, err := p.getInt64(key) 231 if err != nil { 232 ErrorHandler(err) 233 } 234 return time.Duration(v) 235} 236 237// ---------------------------------------------------------------------------- 238 239// GetParsedDuration parses the expanded value with time.ParseDuration() if the key exists. 240// If key does not exist or the value cannot be parsed the default 241// value is returned. 242func (p *Properties) GetParsedDuration(key string, def time.Duration) time.Duration { 243 s, ok := p.Get(key) 244 if !ok { 245 return def 246 } 247 v, err := time.ParseDuration(s) 248 if err != nil { 249 return def 250 } 251 return v 252} 253 254// MustGetParsedDuration parses the expanded value with time.ParseDuration() if the key exists. 255// If key does not exist or the value cannot be parsed the function panics. 256func (p *Properties) MustGetParsedDuration(key string) time.Duration { 257 s, ok := p.Get(key) 258 if !ok { 259 ErrorHandler(invalidKeyError(key)) 260 } 261 v, err := time.ParseDuration(s) 262 if err != nil { 263 ErrorHandler(err) 264 } 265 return v 266} 267 268// ---------------------------------------------------------------------------- 269 270// GetFloat64 parses the expanded value as a float64 if the key exists. 271// If key does not exist or the value cannot be parsed the default 272// value is returned. 273func (p *Properties) GetFloat64(key string, def float64) float64 { 274 v, err := p.getFloat64(key) 275 if err != nil { 276 return def 277 } 278 return v 279} 280 281// MustGetFloat64 parses the expanded value as a float64 if the key exists. 282// If key does not exist or the value cannot be parsed the function panics. 283func (p *Properties) MustGetFloat64(key string) float64 { 284 v, err := p.getFloat64(key) 285 if err != nil { 286 ErrorHandler(err) 287 } 288 return v 289} 290 291func (p *Properties) getFloat64(key string) (value float64, err error) { 292 if v, ok := p.Get(key); ok { 293 value, err = strconv.ParseFloat(v, 64) 294 if err != nil { 295 return 0, err 296 } 297 return value, nil 298 } 299 return 0, invalidKeyError(key) 300} 301 302// ---------------------------------------------------------------------------- 303 304// GetInt parses the expanded value as an int if the key exists. 305// If key does not exist or the value cannot be parsed the default 306// value is returned. If the value does not fit into an int the 307// function panics with an out of range error. 308func (p *Properties) GetInt(key string, def int) int { 309 v, err := p.getInt64(key) 310 if err != nil { 311 return def 312 } 313 return intRangeCheck(key, v) 314} 315 316// MustGetInt parses the expanded value as an int if the key exists. 317// If key does not exist or the value cannot be parsed the function panics. 318// If the value does not fit into an int the function panics with 319// an out of range error. 320func (p *Properties) MustGetInt(key string) int { 321 v, err := p.getInt64(key) 322 if err != nil { 323 ErrorHandler(err) 324 } 325 return intRangeCheck(key, v) 326} 327 328// ---------------------------------------------------------------------------- 329 330// GetInt64 parses the expanded value as an int64 if the key exists. 331// If key does not exist or the value cannot be parsed the default 332// value is returned. 333func (p *Properties) GetInt64(key string, def int64) int64 { 334 v, err := p.getInt64(key) 335 if err != nil { 336 return def 337 } 338 return v 339} 340 341// MustGetInt64 parses the expanded value as an int if the key exists. 342// If key does not exist or the value cannot be parsed the function panics. 343func (p *Properties) MustGetInt64(key string) int64 { 344 v, err := p.getInt64(key) 345 if err != nil { 346 ErrorHandler(err) 347 } 348 return v 349} 350 351func (p *Properties) getInt64(key string) (value int64, err error) { 352 if v, ok := p.Get(key); ok { 353 value, err = strconv.ParseInt(v, 10, 64) 354 if err != nil { 355 return 0, err 356 } 357 return value, nil 358 } 359 return 0, invalidKeyError(key) 360} 361 362// ---------------------------------------------------------------------------- 363 364// GetUint parses the expanded value as an uint if the key exists. 365// If key does not exist or the value cannot be parsed the default 366// value is returned. If the value does not fit into an int the 367// function panics with an out of range error. 368func (p *Properties) GetUint(key string, def uint) uint { 369 v, err := p.getUint64(key) 370 if err != nil { 371 return def 372 } 373 return uintRangeCheck(key, v) 374} 375 376// MustGetUint parses the expanded value as an int if the key exists. 377// If key does not exist or the value cannot be parsed the function panics. 378// If the value does not fit into an int the function panics with 379// an out of range error. 380func (p *Properties) MustGetUint(key string) uint { 381 v, err := p.getUint64(key) 382 if err != nil { 383 ErrorHandler(err) 384 } 385 return uintRangeCheck(key, v) 386} 387 388// ---------------------------------------------------------------------------- 389 390// GetUint64 parses the expanded value as an uint64 if the key exists. 391// If key does not exist or the value cannot be parsed the default 392// value is returned. 393func (p *Properties) GetUint64(key string, def uint64) uint64 { 394 v, err := p.getUint64(key) 395 if err != nil { 396 return def 397 } 398 return v 399} 400 401// MustGetUint64 parses the expanded value as an int if the key exists. 402// If key does not exist or the value cannot be parsed the function panics. 403func (p *Properties) MustGetUint64(key string) uint64 { 404 v, err := p.getUint64(key) 405 if err != nil { 406 ErrorHandler(err) 407 } 408 return v 409} 410 411func (p *Properties) getUint64(key string) (value uint64, err error) { 412 if v, ok := p.Get(key); ok { 413 value, err = strconv.ParseUint(v, 10, 64) 414 if err != nil { 415 return 0, err 416 } 417 return value, nil 418 } 419 return 0, invalidKeyError(key) 420} 421 422// ---------------------------------------------------------------------------- 423 424// GetString returns the expanded value for the given key if exists or 425// the default value otherwise. 426func (p *Properties) GetString(key, def string) string { 427 if v, ok := p.Get(key); ok { 428 return v 429 } 430 return def 431} 432 433// MustGetString returns the expanded value for the given key if exists or 434// panics otherwise. 435func (p *Properties) MustGetString(key string) string { 436 if v, ok := p.Get(key); ok { 437 return v 438 } 439 ErrorHandler(invalidKeyError(key)) 440 panic("ErrorHandler should exit") 441} 442 443// ---------------------------------------------------------------------------- 444 445// Filter returns a new properties object which contains all properties 446// for which the key matches the pattern. 447func (p *Properties) Filter(pattern string) (*Properties, error) { 448 re, err := regexp.Compile(pattern) 449 if err != nil { 450 return nil, err 451 } 452 453 return p.FilterRegexp(re), nil 454} 455 456// FilterRegexp returns a new properties object which contains all properties 457// for which the key matches the regular expression. 458func (p *Properties) FilterRegexp(re *regexp.Regexp) *Properties { 459 pp := NewProperties() 460 for _, k := range p.k { 461 if re.MatchString(k) { 462 // TODO(fs): we are ignoring the error which flags a circular reference. 463 // TODO(fs): since we are just copying a subset of keys this cannot happen (fingers crossed) 464 pp.Set(k, p.m[k]) 465 } 466 } 467 return pp 468} 469 470// FilterPrefix returns a new properties object with a subset of all keys 471// with the given prefix. 472func (p *Properties) FilterPrefix(prefix string) *Properties { 473 pp := NewProperties() 474 for _, k := range p.k { 475 if strings.HasPrefix(k, prefix) { 476 // TODO(fs): we are ignoring the error which flags a circular reference. 477 // TODO(fs): since we are just copying a subset of keys this cannot happen (fingers crossed) 478 pp.Set(k, p.m[k]) 479 } 480 } 481 return pp 482} 483 484// FilterStripPrefix returns a new properties object with a subset of all keys 485// with the given prefix and the prefix removed from the keys. 486func (p *Properties) FilterStripPrefix(prefix string) *Properties { 487 pp := NewProperties() 488 n := len(prefix) 489 for _, k := range p.k { 490 if len(k) > len(prefix) && strings.HasPrefix(k, prefix) { 491 // TODO(fs): we are ignoring the error which flags a circular reference. 492 // TODO(fs): since we are modifying keys I am not entirely sure whether we can create a circular reference 493 // TODO(fs): this function should probably return an error but the signature is fixed 494 pp.Set(k[n:], p.m[k]) 495 } 496 } 497 return pp 498} 499 500// Len returns the number of keys. 501func (p *Properties) Len() int { 502 return len(p.m) 503} 504 505// Keys returns all keys in the same order as in the input. 506func (p *Properties) Keys() []string { 507 keys := make([]string, len(p.k)) 508 copy(keys, p.k) 509 return keys 510} 511 512// Set sets the property key to the corresponding value. 513// If a value for key existed before then ok is true and prev 514// contains the previous value. If the value contains a 515// circular reference or a malformed expression then 516// an error is returned. 517// An empty key is silently ignored. 518func (p *Properties) Set(key, value string) (prev string, ok bool, err error) { 519 if key == "" { 520 return "", false, nil 521 } 522 523 // if expansion is disabled we allow circular references 524 if p.DisableExpansion { 525 prev, ok = p.Get(key) 526 p.m[key] = value 527 if !ok { 528 p.k = append(p.k, key) 529 } 530 return prev, ok, nil 531 } 532 533 // to check for a circular reference we temporarily need 534 // to set the new value. If there is an error then revert 535 // to the previous state. Only if all tests are successful 536 // then we add the key to the p.k list. 537 prev, ok = p.Get(key) 538 p.m[key] = value 539 540 // now check for a circular reference 541 _, err = p.expand(key, value) 542 if err != nil { 543 544 // revert to the previous state 545 if ok { 546 p.m[key] = prev 547 } else { 548 delete(p.m, key) 549 } 550 551 return "", false, err 552 } 553 554 if !ok { 555 p.k = append(p.k, key) 556 } 557 558 return prev, ok, nil 559} 560 561// SetValue sets property key to the default string value 562// as defined by fmt.Sprintf("%v"). 563func (p *Properties) SetValue(key string, value interface{}) error { 564 _, _, err := p.Set(key, fmt.Sprintf("%v", value)) 565 return err 566} 567 568// MustSet sets the property key to the corresponding value. 569// If a value for key existed before then ok is true and prev 570// contains the previous value. An empty key is silently ignored. 571func (p *Properties) MustSet(key, value string) (prev string, ok bool) { 572 prev, ok, err := p.Set(key, value) 573 if err != nil { 574 ErrorHandler(err) 575 } 576 return prev, ok 577} 578 579// String returns a string of all expanded 'key = value' pairs. 580func (p *Properties) String() string { 581 var s string 582 for _, key := range p.k { 583 value, _ := p.Get(key) 584 s = fmt.Sprintf("%s%s = %s\n", s, key, value) 585 } 586 return s 587} 588 589// Write writes all unexpanded 'key = value' pairs to the given writer. 590// Write returns the number of bytes written and any write error encountered. 591func (p *Properties) Write(w io.Writer, enc Encoding) (n int, err error) { 592 return p.WriteComment(w, "", enc) 593} 594 595// WriteComment writes all unexpanced 'key = value' pairs to the given writer. 596// If prefix is not empty then comments are written with a blank line and the 597// given prefix. The prefix should be either "# " or "! " to be compatible with 598// the properties file format. Otherwise, the properties parser will not be 599// able to read the file back in. It returns the number of bytes written and 600// any write error encountered. 601func (p *Properties) WriteComment(w io.Writer, prefix string, enc Encoding) (n int, err error) { 602 var x int 603 604 for _, key := range p.k { 605 value := p.m[key] 606 607 if prefix != "" { 608 if comments, ok := p.c[key]; ok { 609 // don't print comments if they are all empty 610 allEmpty := true 611 for _, c := range comments { 612 if c != "" { 613 allEmpty = false 614 break 615 } 616 } 617 618 if !allEmpty { 619 // add a blank line between entries but not at the top 620 if len(comments) > 0 && n > 0 { 621 x, err = fmt.Fprintln(w) 622 if err != nil { 623 return 624 } 625 n += x 626 } 627 628 for _, c := range comments { 629 x, err = fmt.Fprintf(w, "%s%s\n", prefix, encode(c, "", enc)) 630 if err != nil { 631 return 632 } 633 n += x 634 } 635 } 636 } 637 } 638 639 x, err = fmt.Fprintf(w, "%s = %s\n", encode(key, " :", enc), encode(value, "", enc)) 640 if err != nil { 641 return 642 } 643 n += x 644 } 645 return 646} 647 648// Map returns a copy of the properties as a map. 649func (p *Properties) Map() map[string]string { 650 m := make(map[string]string) 651 for k, v := range p.m { 652 m[k] = v 653 } 654 return m 655} 656 657// FilterFunc returns a copy of the properties which includes the values which passed all filters. 658func (p *Properties) FilterFunc(filters ...func(k, v string) bool) *Properties { 659 pp := NewProperties() 660outer: 661 for k, v := range p.m { 662 for _, f := range filters { 663 if !f(k, v) { 664 continue outer 665 } 666 pp.Set(k, v) 667 } 668 } 669 return pp 670} 671 672// ---------------------------------------------------------------------------- 673 674// Delete removes the key and its comments. 675func (p *Properties) Delete(key string) { 676 delete(p.m, key) 677 delete(p.c, key) 678 newKeys := []string{} 679 for _, k := range p.k { 680 if k != key { 681 newKeys = append(newKeys, k) 682 } 683 } 684 p.k = newKeys 685} 686 687// Merge merges properties, comments and keys from other *Properties into p 688func (p *Properties) Merge(other *Properties) { 689 for k, v := range other.m { 690 p.m[k] = v 691 } 692 for k, v := range other.c { 693 p.c[k] = v 694 } 695 696outer: 697 for _, otherKey := range other.k { 698 for _, key := range p.k { 699 if otherKey == key { 700 continue outer 701 } 702 } 703 p.k = append(p.k, otherKey) 704 } 705} 706 707// ---------------------------------------------------------------------------- 708 709// check expands all values and returns an error if a circular reference or 710// a malformed expression was found. 711func (p *Properties) check() error { 712 for key, value := range p.m { 713 if _, err := p.expand(key, value); err != nil { 714 return err 715 } 716 } 717 return nil 718} 719 720func (p *Properties) expand(key, input string) (string, error) { 721 // no pre/postfix -> nothing to expand 722 if p.Prefix == "" && p.Postfix == "" { 723 return input, nil 724 } 725 726 return expand(input, []string{key}, p.Prefix, p.Postfix, p.m) 727} 728 729// expand recursively expands expressions of '(prefix)key(postfix)' to their corresponding values. 730// The function keeps track of the keys that were already expanded and stops if it 731// detects a circular reference or a malformed expression of the form '(prefix)key'. 732func expand(s string, keys []string, prefix, postfix string, values map[string]string) (string, error) { 733 if len(keys) > maxExpansionDepth { 734 return "", fmt.Errorf("expansion too deep") 735 } 736 737 for { 738 start := strings.Index(s, prefix) 739 if start == -1 { 740 return s, nil 741 } 742 743 keyStart := start + len(prefix) 744 keyLen := strings.Index(s[keyStart:], postfix) 745 if keyLen == -1 { 746 return "", fmt.Errorf("malformed expression") 747 } 748 749 end := keyStart + keyLen + len(postfix) - 1 750 key := s[keyStart : keyStart+keyLen] 751 752 // 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) 753 754 for _, k := range keys { 755 if key == k { 756 return "", fmt.Errorf("circular reference") 757 } 758 } 759 760 val, ok := values[key] 761 if !ok { 762 val = os.Getenv(key) 763 } 764 new_val, err := expand(val, append(keys, key), prefix, postfix, values) 765 if err != nil { 766 return "", err 767 } 768 s = s[:start] + new_val + s[end+1:] 769 } 770 return s, nil 771} 772 773// encode encodes a UTF-8 string to ISO-8859-1 and escapes some characters. 774func encode(s string, special string, enc Encoding) string { 775 switch enc { 776 case UTF8: 777 return encodeUtf8(s, special) 778 case ISO_8859_1: 779 return encodeIso(s, special) 780 default: 781 panic(fmt.Sprintf("unsupported encoding %v", enc)) 782 } 783} 784 785func encodeUtf8(s string, special string) string { 786 v := "" 787 for pos := 0; pos < len(s); { 788 r, w := utf8.DecodeRuneInString(s[pos:]) 789 pos += w 790 v += escape(r, special) 791 } 792 return v 793} 794 795func encodeIso(s string, special string) string { 796 var r rune 797 var w int 798 var v string 799 for pos := 0; pos < len(s); { 800 switch r, w = utf8.DecodeRuneInString(s[pos:]); { 801 case r < 1<<8: // single byte rune -> escape special chars only 802 v += escape(r, special) 803 case r < 1<<16: // two byte rune -> unicode literal 804 v += fmt.Sprintf("\\u%04x", r) 805 default: // more than two bytes per rune -> can't encode 806 v += "?" 807 } 808 pos += w 809 } 810 return v 811} 812 813func escape(r rune, special string) string { 814 switch r { 815 case '\f': 816 return "\\f" 817 case '\n': 818 return "\\n" 819 case '\r': 820 return "\\r" 821 case '\t': 822 return "\\t" 823 default: 824 if strings.ContainsRune(special, r) { 825 return "\\" + string(r) 826 } 827 return string(r) 828 } 829} 830 831func invalidKeyError(key string) error { 832 return fmt.Errorf("unknown property: %s", key) 833} 834