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