gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

resolve.go (6889B)


      1 package yaml
      2 
      3 import (
      4 	"encoding/base64"
      5 	"math"
      6 	"regexp"
      7 	"strconv"
      8 	"strings"
      9 	"time"
     10 )
     11 
     12 type resolveMapItem struct {
     13 	value interface{}
     14 	tag   string
     15 }
     16 
     17 var resolveTable = make([]byte, 256)
     18 var resolveMap = make(map[string]resolveMapItem)
     19 
     20 func init() {
     21 	t := resolveTable
     22 	t[int('+')] = 'S' // Sign
     23 	t[int('-')] = 'S'
     24 	for _, c := range "0123456789" {
     25 		t[int(c)] = 'D' // Digit
     26 	}
     27 	for _, c := range "yYnNtTfFoO~" {
     28 		t[int(c)] = 'M' // In map
     29 	}
     30 	t[int('.')] = '.' // Float (potentially in map)
     31 
     32 	var resolveMapList = []struct {
     33 		v   interface{}
     34 		tag string
     35 		l   []string
     36 	}{
     37 		{true, yaml_BOOL_TAG, []string{"y", "Y", "yes", "Yes", "YES"}},
     38 		{true, yaml_BOOL_TAG, []string{"true", "True", "TRUE"}},
     39 		{true, yaml_BOOL_TAG, []string{"on", "On", "ON"}},
     40 		{false, yaml_BOOL_TAG, []string{"n", "N", "no", "No", "NO"}},
     41 		{false, yaml_BOOL_TAG, []string{"false", "False", "FALSE"}},
     42 		{false, yaml_BOOL_TAG, []string{"off", "Off", "OFF"}},
     43 		{nil, yaml_NULL_TAG, []string{"", "~", "null", "Null", "NULL"}},
     44 		{math.NaN(), yaml_FLOAT_TAG, []string{".nan", ".NaN", ".NAN"}},
     45 		{math.Inf(+1), yaml_FLOAT_TAG, []string{".inf", ".Inf", ".INF"}},
     46 		{math.Inf(+1), yaml_FLOAT_TAG, []string{"+.inf", "+.Inf", "+.INF"}},
     47 		{math.Inf(-1), yaml_FLOAT_TAG, []string{"-.inf", "-.Inf", "-.INF"}},
     48 		{"<<", yaml_MERGE_TAG, []string{"<<"}},
     49 	}
     50 
     51 	m := resolveMap
     52 	for _, item := range resolveMapList {
     53 		for _, s := range item.l {
     54 			m[s] = resolveMapItem{item.v, item.tag}
     55 		}
     56 	}
     57 }
     58 
     59 const longTagPrefix = "tag:yaml.org,2002:"
     60 
     61 func shortTag(tag string) string {
     62 	// TODO This can easily be made faster and produce less garbage.
     63 	if strings.HasPrefix(tag, longTagPrefix) {
     64 		return "!!" + tag[len(longTagPrefix):]
     65 	}
     66 	return tag
     67 }
     68 
     69 func longTag(tag string) string {
     70 	if strings.HasPrefix(tag, "!!") {
     71 		return longTagPrefix + tag[2:]
     72 	}
     73 	return tag
     74 }
     75 
     76 func resolvableTag(tag string) bool {
     77 	switch tag {
     78 	case "", yaml_STR_TAG, yaml_BOOL_TAG, yaml_INT_TAG, yaml_FLOAT_TAG, yaml_NULL_TAG, yaml_TIMESTAMP_TAG:
     79 		return true
     80 	}
     81 	return false
     82 }
     83 
     84 var yamlStyleFloat = regexp.MustCompile(`^[-+]?(\.[0-9]+|[0-9]+(\.[0-9]*)?)([eE][-+]?[0-9]+)?$`)
     85 
     86 func resolve(tag string, in string) (rtag string, out interface{}) {
     87 	if !resolvableTag(tag) {
     88 		return tag, in
     89 	}
     90 
     91 	defer func() {
     92 		switch tag {
     93 		case "", rtag, yaml_STR_TAG, yaml_BINARY_TAG:
     94 			return
     95 		case yaml_FLOAT_TAG:
     96 			if rtag == yaml_INT_TAG {
     97 				switch v := out.(type) {
     98 				case int64:
     99 					rtag = yaml_FLOAT_TAG
    100 					out = float64(v)
    101 					return
    102 				case int:
    103 					rtag = yaml_FLOAT_TAG
    104 					out = float64(v)
    105 					return
    106 				}
    107 			}
    108 		}
    109 		failf("cannot decode %s `%s` as a %s", shortTag(rtag), in, shortTag(tag))
    110 	}()
    111 
    112 	// Any data is accepted as a !!str or !!binary.
    113 	// Otherwise, the prefix is enough of a hint about what it might be.
    114 	hint := byte('N')
    115 	if in != "" {
    116 		hint = resolveTable[in[0]]
    117 	}
    118 	if hint != 0 && tag != yaml_STR_TAG && tag != yaml_BINARY_TAG {
    119 		// Handle things we can lookup in a map.
    120 		if item, ok := resolveMap[in]; ok {
    121 			return item.tag, item.value
    122 		}
    123 
    124 		// Base 60 floats are a bad idea, were dropped in YAML 1.2, and
    125 		// are purposefully unsupported here. They're still quoted on
    126 		// the way out for compatibility with other parser, though.
    127 
    128 		switch hint {
    129 		case 'M':
    130 			// We've already checked the map above.
    131 
    132 		case '.':
    133 			// Not in the map, so maybe a normal float.
    134 			floatv, err := strconv.ParseFloat(in, 64)
    135 			if err == nil {
    136 				return yaml_FLOAT_TAG, floatv
    137 			}
    138 
    139 		case 'D', 'S':
    140 			// Int, float, or timestamp.
    141 			// Only try values as a timestamp if the value is unquoted or there's an explicit
    142 			// !!timestamp tag.
    143 			if tag == "" || tag == yaml_TIMESTAMP_TAG {
    144 				t, ok := parseTimestamp(in)
    145 				if ok {
    146 					return yaml_TIMESTAMP_TAG, t
    147 				}
    148 			}
    149 
    150 			plain := strings.Replace(in, "_", "", -1)
    151 			intv, err := strconv.ParseInt(plain, 0, 64)
    152 			if err == nil {
    153 				if intv == int64(int(intv)) {
    154 					return yaml_INT_TAG, int(intv)
    155 				} else {
    156 					return yaml_INT_TAG, intv
    157 				}
    158 			}
    159 			uintv, err := strconv.ParseUint(plain, 0, 64)
    160 			if err == nil {
    161 				return yaml_INT_TAG, uintv
    162 			}
    163 			if yamlStyleFloat.MatchString(plain) {
    164 				floatv, err := strconv.ParseFloat(plain, 64)
    165 				if err == nil {
    166 					return yaml_FLOAT_TAG, floatv
    167 				}
    168 			}
    169 			if strings.HasPrefix(plain, "0b") {
    170 				intv, err := strconv.ParseInt(plain[2:], 2, 64)
    171 				if err == nil {
    172 					if intv == int64(int(intv)) {
    173 						return yaml_INT_TAG, int(intv)
    174 					} else {
    175 						return yaml_INT_TAG, intv
    176 					}
    177 				}
    178 				uintv, err := strconv.ParseUint(plain[2:], 2, 64)
    179 				if err == nil {
    180 					return yaml_INT_TAG, uintv
    181 				}
    182 			} else if strings.HasPrefix(plain, "-0b") {
    183 				intv, err := strconv.ParseInt("-" + plain[3:], 2, 64)
    184 				if err == nil {
    185 					if true || intv == int64(int(intv)) {
    186 						return yaml_INT_TAG, int(intv)
    187 					} else {
    188 						return yaml_INT_TAG, intv
    189 					}
    190 				}
    191 			}
    192 		default:
    193 			panic("resolveTable item not yet handled: " + string(rune(hint)) + " (with " + in + ")")
    194 		}
    195 	}
    196 	return yaml_STR_TAG, in
    197 }
    198 
    199 // encodeBase64 encodes s as base64 that is broken up into multiple lines
    200 // as appropriate for the resulting length.
    201 func encodeBase64(s string) string {
    202 	const lineLen = 70
    203 	encLen := base64.StdEncoding.EncodedLen(len(s))
    204 	lines := encLen/lineLen + 1
    205 	buf := make([]byte, encLen*2+lines)
    206 	in := buf[0:encLen]
    207 	out := buf[encLen:]
    208 	base64.StdEncoding.Encode(in, []byte(s))
    209 	k := 0
    210 	for i := 0; i < len(in); i += lineLen {
    211 		j := i + lineLen
    212 		if j > len(in) {
    213 			j = len(in)
    214 		}
    215 		k += copy(out[k:], in[i:j])
    216 		if lines > 1 {
    217 			out[k] = '\n'
    218 			k++
    219 		}
    220 	}
    221 	return string(out[:k])
    222 }
    223 
    224 // This is a subset of the formats allowed by the regular expression
    225 // defined at http://yaml.org/type/timestamp.html.
    226 var allowedTimestampFormats = []string{
    227 	"2006-1-2T15:4:5.999999999Z07:00", // RCF3339Nano with short date fields.
    228 	"2006-1-2t15:4:5.999999999Z07:00", // RFC3339Nano with short date fields and lower-case "t".
    229 	"2006-1-2 15:4:5.999999999",       // space separated with no time zone
    230 	"2006-1-2",                        // date only
    231 	// Notable exception: time.Parse cannot handle: "2001-12-14 21:59:43.10 -5"
    232 	// from the set of examples.
    233 }
    234 
    235 // parseTimestamp parses s as a timestamp string and
    236 // returns the timestamp and reports whether it succeeded.
    237 // Timestamp formats are defined at http://yaml.org/type/timestamp.html
    238 func parseTimestamp(s string) (time.Time, bool) {
    239 	// TODO write code to check all the formats supported by
    240 	// http://yaml.org/type/timestamp.html instead of using time.Parse.
    241 
    242 	// Quick check: all date formats start with YYYY-.
    243 	i := 0
    244 	for ; i < len(s); i++ {
    245 		if c := s[i]; c < '0' || c > '9' {
    246 			break
    247 		}
    248 	}
    249 	if i != 4 || i == len(s) || s[i] != '-' {
    250 		return time.Time{}, false
    251 	}
    252 	for _, format := range allowedTimestampFormats {
    253 		if t, err := time.Parse(format, s); err == nil {
    254 			return t, true
    255 		}
    256 	}
    257 	return time.Time{}, false
    258 }