Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright 2017 The LUCI Authors. All rights reserved. | |
| 2 // Use of this source code is governed under the Apache License, Version 2.0 | |
| 3 // that can be found in the LICENSE file. | |
| 4 | |
| 5 // Package stringtemplate implements Python string.Template-like substitution. | |
|
nodir
2017/02/18 07:00:54
this is a nice package, thanks
| |
| 6 package stringtemplate | |
| 7 | |
| 8 import ( | |
| 9 "regexp" | |
| 10 "strings" | |
| 11 | |
| 12 "github.com/luci/luci-go/common/errors" | |
| 13 ) | |
| 14 | |
| 15 const idPattern = "[_a-z][_a-z0-9]*" | |
| 16 | |
| 17 // We're looking for: | |
| 18 // | |
| 19 // Submatch indices: | |
| 20 // [0:1] Full match | |
| 21 // [2:3] $$ (escaped) | |
| 22 // [4:5] $key (without braces) | |
| 23 // [6:7] ${key} (with braces) | |
| 24 // [8:9] $... (Invalid) | |
| 25 var namedFormatMatcher = regexp.MustCompile( | |
| 26 `\$(?:` + | |
| 27 `(\$)|` + // Escaped ($$) | |
| 28 `(` + idPattern + `)|` + // Without braces: $key | |
| 29 `(?:\{(` + idPattern + `)\})|` + // With braces: ${key} | |
| 30 `(.*)` + // Invalid | |
| 31 `)`) | |
| 32 | |
| 33 // Resolve resolves substitutions in v using the supplied substitution map, | |
| 34 // subst. | |
| 35 // | |
| 36 // A substitution can have the form: | |
| 37 // | |
| 38 // $key | |
| 39 // ${key} | |
| 40 // | |
| 41 // The substitution can also be escaped using a second "$", such as "$$". | |
| 42 // | |
| 43 // If the string includes an erroneous substitution, or if a referenced | |
| 44 // template variable isn't included in the "substitutions" map, Resolve will | |
| 45 // return an error. | |
| 46 func Resolve(v string, subst map[string]string) (string, error) { | |
| 47 smi := namedFormatMatcher.FindAllStringSubmatchIndex(v, -1) | |
| 48 if len(smi) == 0 { | |
| 49 // No substitutions. | |
| 50 return v, nil | |
| 51 } | |
| 52 | |
| 53 var ( | |
| 54 parts = make([]string, 0, (len(smi)*2)+1) | |
| 55 pos = 0 | |
| 56 ) | |
| 57 | |
| 58 for _, match := range smi { | |
| 59 key := "" | |
| 60 switch { | |
| 61 case match[8] >= 0: | |
| 62 // Invalid. | |
| 63 return "", errors.Reason("invalid template: %(template)q "). | |
| 64 D("template", v). | |
| 65 Err() | |
| 66 | |
| 67 case match[2] >= 0: | |
| 68 // Escaped. | |
| 69 parts = append(parts, v[pos:match[2]]) | |
| 70 pos = match[3] | |
| 71 continue | |
| 72 | |
| 73 case match[4] >= 0: | |
| 74 // Key (without braces) | |
| 75 key = v[match[4]:match[5]] | |
| 76 case match[6] >= 0: | |
| 77 // Key (with braces) | |
| 78 key = v[match[6]:match[7]] | |
| 79 } | |
|
nodir
2017/02/18 07:00:54
nit: can you add `panic("impossible") in the defau
dnj
2017/02/18 07:42:48
Done.
| |
| 80 | |
| 81 // Add anything in between the previous match and the current. I f our match | |
| 82 // includes a non-escape character, add that too. | |
| 83 if pos < match[0] { | |
| 84 parts = append(parts, v[pos:match[0]]) | |
| 85 } | |
| 86 pos = match[1] | |
| 87 | |
| 88 subst, ok := subst[key] | |
| 89 if !ok { | |
| 90 return "", errors.Reason("no substitution for %(key)q"). | |
| 91 D("key", key). | |
| 92 Err() | |
| 93 } | |
| 94 parts = append(parts, subst) | |
| 95 } | |
| 96 | |
| 97 // Append any trailing string. | |
| 98 parts = append(parts, v[pos:]) | |
| 99 | |
| 100 // Join the parts. | |
| 101 return strings.Join(parts, ""), nil | |
| 102 } | |
| OLD | NEW |