diff options
Diffstat (limited to 'vendor/github.com/zclconf/go-cty-yaml/resolve.go')
-rw-r--r-- | vendor/github.com/zclconf/go-cty-yaml/resolve.go | 288 |
1 files changed, 288 insertions, 0 deletions
diff --git a/vendor/github.com/zclconf/go-cty-yaml/resolve.go b/vendor/github.com/zclconf/go-cty-yaml/resolve.go new file mode 100644 index 0000000..0f64383 --- /dev/null +++ b/vendor/github.com/zclconf/go-cty-yaml/resolve.go | |||
@@ -0,0 +1,288 @@ | |||
1 | package yaml | ||
2 | |||
3 | import ( | ||
4 | "encoding/base64" | ||
5 | "fmt" | ||
6 | "reflect" | ||
7 | "regexp" | ||
8 | "strconv" | ||
9 | "strings" | ||
10 | "time" | ||
11 | |||
12 | "github.com/zclconf/go-cty/cty" | ||
13 | ) | ||
14 | |||
15 | type resolveMapItem struct { | ||
16 | value cty.Value | ||
17 | tag string | ||
18 | } | ||
19 | |||
20 | var resolveTable = make([]byte, 256) | ||
21 | var resolveMap = make(map[string]resolveMapItem) | ||
22 | |||
23 | func init() { | ||
24 | t := resolveTable | ||
25 | t[int('+')] = 'S' // Sign | ||
26 | t[int('-')] = 'S' | ||
27 | for _, c := range "0123456789" { | ||
28 | t[int(c)] = 'D' // Digit | ||
29 | } | ||
30 | for _, c := range "yYnNtTfFoO~" { | ||
31 | t[int(c)] = 'M' // In map | ||
32 | } | ||
33 | t[int('.')] = '.' // Float (potentially in map) | ||
34 | |||
35 | var resolveMapList = []struct { | ||
36 | v cty.Value | ||
37 | tag string | ||
38 | l []string | ||
39 | }{ | ||
40 | {cty.True, yaml_BOOL_TAG, []string{"y", "Y", "yes", "Yes", "YES"}}, | ||
41 | {cty.True, yaml_BOOL_TAG, []string{"true", "True", "TRUE"}}, | ||
42 | {cty.True, yaml_BOOL_TAG, []string{"on", "On", "ON"}}, | ||
43 | {cty.False, yaml_BOOL_TAG, []string{"n", "N", "no", "No", "NO"}}, | ||
44 | {cty.False, yaml_BOOL_TAG, []string{"false", "False", "FALSE"}}, | ||
45 | {cty.False, yaml_BOOL_TAG, []string{"off", "Off", "OFF"}}, | ||
46 | {cty.NullVal(cty.DynamicPseudoType), yaml_NULL_TAG, []string{"", "~", "null", "Null", "NULL"}}, | ||
47 | {cty.PositiveInfinity, yaml_FLOAT_TAG, []string{".inf", ".Inf", ".INF"}}, | ||
48 | {cty.PositiveInfinity, yaml_FLOAT_TAG, []string{"+.inf", "+.Inf", "+.INF"}}, | ||
49 | {cty.NegativeInfinity, yaml_FLOAT_TAG, []string{"-.inf", "-.Inf", "-.INF"}}, | ||
50 | } | ||
51 | |||
52 | m := resolveMap | ||
53 | for _, item := range resolveMapList { | ||
54 | for _, s := range item.l { | ||
55 | m[s] = resolveMapItem{item.v, item.tag} | ||
56 | } | ||
57 | } | ||
58 | } | ||
59 | |||
60 | const longTagPrefix = "tag:yaml.org,2002:" | ||
61 | |||
62 | func shortTag(tag string) string { | ||
63 | // TODO This can easily be made faster and produce less garbage. | ||
64 | if strings.HasPrefix(tag, longTagPrefix) { | ||
65 | return "!!" + tag[len(longTagPrefix):] | ||
66 | } | ||
67 | return tag | ||
68 | } | ||
69 | |||
70 | func longTag(tag string) string { | ||
71 | if strings.HasPrefix(tag, "!!") { | ||
72 | return longTagPrefix + tag[2:] | ||
73 | } | ||
74 | return tag | ||
75 | } | ||
76 | |||
77 | func resolvableTag(tag string) bool { | ||
78 | switch tag { | ||
79 | case "", yaml_STR_TAG, yaml_BOOL_TAG, yaml_INT_TAG, yaml_FLOAT_TAG, yaml_NULL_TAG, yaml_TIMESTAMP_TAG, yaml_BINARY_TAG: | ||
80 | return true | ||
81 | } | ||
82 | return false | ||
83 | } | ||
84 | |||
85 | var yamlStyleFloat = regexp.MustCompile(`^[-+]?(\.[0-9]+|[0-9]+(\.[0-9]*)?)([eE][-+]?[0-9]+)?$`) | ||
86 | |||
87 | func (c *Converter) resolveScalar(tag string, src string, style yaml_scalar_style_t) (cty.Value, error) { | ||
88 | if !resolvableTag(tag) { | ||
89 | return cty.NilVal, fmt.Errorf("unsupported tag %q", tag) | ||
90 | } | ||
91 | |||
92 | // Any data is accepted as a !!str or !!binary. | ||
93 | // Otherwise, the prefix is enough of a hint about what it might be. | ||
94 | hint := byte('N') | ||
95 | if src != "" { | ||
96 | hint = resolveTable[src[0]] | ||
97 | } | ||
98 | if hint != 0 && tag != yaml_STR_TAG && tag != yaml_BINARY_TAG { | ||
99 | if style == yaml_SINGLE_QUOTED_SCALAR_STYLE || style == yaml_DOUBLE_QUOTED_SCALAR_STYLE { | ||
100 | return cty.StringVal(src), nil | ||
101 | } | ||
102 | |||
103 | // Handle things we can lookup in a map. | ||
104 | if item, ok := resolveMap[src]; ok { | ||
105 | return item.value, nil | ||
106 | } | ||
107 | |||
108 | if tag == "" { | ||
109 | for _, nan := range []string{".nan", ".NaN", ".NAN"} { | ||
110 | if src == nan { | ||
111 | // cty cannot represent NaN, so this is an error | ||
112 | return cty.NilVal, fmt.Errorf("floating point NaN is not supported") | ||
113 | } | ||
114 | } | ||
115 | } | ||
116 | |||
117 | // Base 60 floats are intentionally not supported. | ||
118 | |||
119 | switch hint { | ||
120 | case 'M': | ||
121 | // We've already checked the map above. | ||
122 | |||
123 | case '.': | ||
124 | // Not in the map, so maybe a normal float. | ||
125 | if numberVal, err := cty.ParseNumberVal(src); err == nil { | ||
126 | return numberVal, nil | ||
127 | } | ||
128 | |||
129 | case 'D', 'S': | ||
130 | // Int, float, or timestamp. | ||
131 | // Only try values as a timestamp if the value is unquoted or there's an explicit | ||
132 | // !!timestamp tag. | ||
133 | if tag == "" || tag == yaml_TIMESTAMP_TAG { | ||
134 | t, ok := parseTimestamp(src) | ||
135 | if ok { | ||
136 | // cty has no timestamp type, but its functions stdlib | ||
137 | // conventionally uses strings in an RFC3339 encoding | ||
138 | // to represent time, so we'll follow that convention here. | ||
139 | return cty.StringVal(t.Format(time.RFC3339)), nil | ||
140 | } | ||
141 | } | ||
142 | |||
143 | plain := strings.Replace(src, "_", "", -1) | ||
144 | if numberVal, err := cty.ParseNumberVal(plain); err == nil { | ||
145 | return numberVal, nil | ||
146 | } | ||
147 | if strings.HasPrefix(plain, "0b") || strings.HasPrefix(plain, "-0b") { | ||
148 | tag = yaml_INT_TAG // will handle parsing below in our tag switch | ||
149 | } | ||
150 | default: | ||
151 | panic(fmt.Sprintf("cannot resolve tag %q with source %q", tag, src)) | ||
152 | } | ||
153 | } | ||
154 | |||
155 | if tag == "" && src == "<<" { | ||
156 | return mergeMappingVal, nil | ||
157 | } | ||
158 | |||
159 | switch tag { | ||
160 | case yaml_STR_TAG, yaml_BINARY_TAG: | ||
161 | // If it's binary then we want to keep the base64 representation, because | ||
162 | // cty has no binary type, but we will check that it's actually base64. | ||
163 | if tag == yaml_BINARY_TAG { | ||
164 | _, err := base64.StdEncoding.DecodeString(src) | ||
165 | if err != nil { | ||
166 | return cty.NilVal, fmt.Errorf("cannot parse %q as %s: not valid base64", src, tag) | ||
167 | } | ||
168 | } | ||
169 | return cty.StringVal(src), nil | ||
170 | case yaml_BOOL_TAG: | ||
171 | item, ok := resolveMap[src] | ||
172 | if !ok || item.tag != yaml_BOOL_TAG { | ||
173 | return cty.NilVal, fmt.Errorf("cannot parse %q as %s", src, tag) | ||
174 | } | ||
175 | return item.value, nil | ||
176 | case yaml_FLOAT_TAG, yaml_INT_TAG: | ||
177 | // Note: We don't actually check that a value tagged INT is a whole | ||
178 | // number here. We could, but cty generally doesn't care about the | ||
179 | // int/float distinction, so we'll just be generous and accept it. | ||
180 | plain := strings.Replace(src, "_", "", -1) | ||
181 | if numberVal, err := cty.ParseNumberVal(plain); err == nil { // handles decimal integers and floats | ||
182 | return numberVal, nil | ||
183 | } | ||
184 | if intv, err := strconv.ParseInt(plain, 0, 64); err == nil { // handles 0x and 00 prefixes | ||
185 | return cty.NumberIntVal(intv), nil | ||
186 | } | ||
187 | if uintv, err := strconv.ParseUint(plain, 0, 64); err == nil { // handles 0x and 00 prefixes | ||
188 | return cty.NumberUIntVal(uintv), nil | ||
189 | } | ||
190 | if strings.HasPrefix(plain, "0b") { | ||
191 | intv, err := strconv.ParseInt(plain[2:], 2, 64) | ||
192 | if err == nil { | ||
193 | return cty.NumberIntVal(intv), nil | ||
194 | } | ||
195 | uintv, err := strconv.ParseUint(plain[2:], 2, 64) | ||
196 | if err == nil { | ||
197 | return cty.NumberUIntVal(uintv), nil | ||
198 | } | ||
199 | } else if strings.HasPrefix(plain, "-0b") { | ||
200 | intv, err := strconv.ParseInt("-"+plain[3:], 2, 64) | ||
201 | if err == nil { | ||
202 | return cty.NumberIntVal(intv), nil | ||
203 | } | ||
204 | } | ||
205 | return cty.NilVal, fmt.Errorf("cannot parse %q as %s", src, tag) | ||
206 | case yaml_TIMESTAMP_TAG: | ||
207 | t, ok := parseTimestamp(src) | ||
208 | if ok { | ||
209 | // cty has no timestamp type, but its functions stdlib | ||
210 | // conventionally uses strings in an RFC3339 encoding | ||
211 | // to represent time, so we'll follow that convention here. | ||
212 | return cty.StringVal(t.Format(time.RFC3339)), nil | ||
213 | } | ||
214 | return cty.NilVal, fmt.Errorf("cannot parse %q as %s", src, tag) | ||
215 | case yaml_NULL_TAG: | ||
216 | return cty.NullVal(cty.DynamicPseudoType), nil | ||
217 | case "": | ||
218 | return cty.StringVal(src), nil | ||
219 | default: | ||
220 | return cty.NilVal, fmt.Errorf("unsupported tag %q", tag) | ||
221 | } | ||
222 | } | ||
223 | |||
224 | // encodeBase64 encodes s as base64 that is broken up into multiple lines | ||
225 | // as appropriate for the resulting length. | ||
226 | func encodeBase64(s string) string { | ||
227 | const lineLen = 70 | ||
228 | encLen := base64.StdEncoding.EncodedLen(len(s)) | ||
229 | lines := encLen/lineLen + 1 | ||
230 | buf := make([]byte, encLen*2+lines) | ||
231 | in := buf[0:encLen] | ||
232 | out := buf[encLen:] | ||
233 | base64.StdEncoding.Encode(in, []byte(s)) | ||
234 | k := 0 | ||
235 | for i := 0; i < len(in); i += lineLen { | ||
236 | j := i + lineLen | ||
237 | if j > len(in) { | ||
238 | j = len(in) | ||
239 | } | ||
240 | k += copy(out[k:], in[i:j]) | ||
241 | if lines > 1 { | ||
242 | out[k] = '\n' | ||
243 | k++ | ||
244 | } | ||
245 | } | ||
246 | return string(out[:k]) | ||
247 | } | ||
248 | |||
249 | // This is a subset of the formats allowed by the regular expression | ||
250 | // defined at http://yaml.org/type/timestamp.html. | ||
251 | var allowedTimestampFormats = []string{ | ||
252 | "2006-1-2T15:4:5.999999999Z07:00", // RCF3339Nano with short date fields. | ||
253 | "2006-1-2t15:4:5.999999999Z07:00", // RFC3339Nano with short date fields and lower-case "t". | ||
254 | "2006-1-2 15:4:5.999999999", // space separated with no time zone | ||
255 | "2006-1-2", // date only | ||
256 | // Notable exception: time.Parse cannot handle: "2001-12-14 21:59:43.10 -5" | ||
257 | // from the set of examples. | ||
258 | } | ||
259 | |||
260 | // parseTimestamp parses s as a timestamp string and | ||
261 | // returns the timestamp and reports whether it succeeded. | ||
262 | // Timestamp formats are defined at http://yaml.org/type/timestamp.html | ||
263 | func parseTimestamp(s string) (time.Time, bool) { | ||
264 | // TODO write code to check all the formats supported by | ||
265 | // http://yaml.org/type/timestamp.html instead of using time.Parse. | ||
266 | |||
267 | // Quick check: all date formats start with YYYY-. | ||
268 | i := 0 | ||
269 | for ; i < len(s); i++ { | ||
270 | if c := s[i]; c < '0' || c > '9' { | ||
271 | break | ||
272 | } | ||
273 | } | ||
274 | if i != 4 || i == len(s) || s[i] != '-' { | ||
275 | return time.Time{}, false | ||
276 | } | ||
277 | for _, format := range allowedTimestampFormats { | ||
278 | if t, err := time.Parse(format, s); err == nil { | ||
279 | return t, true | ||
280 | } | ||
281 | } | ||
282 | return time.Time{}, false | ||
283 | } | ||
284 | |||
285 | type mergeMapping struct{} | ||
286 | |||
287 | var mergeMappingTy = cty.Capsule("merge mapping", reflect.TypeOf(mergeMapping{})) | ||
288 | var mergeMappingVal = cty.CapsuleVal(mergeMappingTy, &mergeMapping{}) | ||