mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2024-11-30 22:01:01 +00:00
450 lines
10 KiB
Go
450 lines
10 KiB
Go
|
/*
|
||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
you may not use this file except in compliance with the License.
|
||
|
You may obtain a copy of the License at
|
||
|
|
||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||
|
|
||
|
Unless required by applicable law or agreed to in writing, software
|
||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
See the License for the specific language governing permissions and
|
||
|
limitations under the License.
|
||
|
*/
|
||
|
|
||
|
package candiedyaml
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"encoding/base64"
|
||
|
"fmt"
|
||
|
"math"
|
||
|
"reflect"
|
||
|
"regexp"
|
||
|
"strconv"
|
||
|
"strings"
|
||
|
"time"
|
||
|
)
|
||
|
|
||
|
var byteSliceType = reflect.TypeOf([]byte(nil))
|
||
|
|
||
|
var binary_tags = [][]byte{[]byte("!binary"), []byte(yaml_BINARY_TAG)}
|
||
|
var bool_values map[string]bool
|
||
|
var null_values map[string]bool
|
||
|
|
||
|
var signs = []byte{'-', '+'}
|
||
|
var nulls = []byte{'~', 'n', 'N'}
|
||
|
var bools = []byte{'t', 'T', 'f', 'F', 'y', 'Y', 'n', 'N', 'o', 'O'}
|
||
|
|
||
|
var timestamp_regexp *regexp.Regexp
|
||
|
var ymd_regexp *regexp.Regexp
|
||
|
|
||
|
func init() {
|
||
|
bool_values = make(map[string]bool)
|
||
|
bool_values["y"] = true
|
||
|
bool_values["yes"] = true
|
||
|
bool_values["n"] = false
|
||
|
bool_values["no"] = false
|
||
|
bool_values["true"] = true
|
||
|
bool_values["false"] = false
|
||
|
bool_values["on"] = true
|
||
|
bool_values["off"] = false
|
||
|
|
||
|
null_values = make(map[string]bool)
|
||
|
null_values["~"] = true
|
||
|
null_values["null"] = true
|
||
|
null_values["Null"] = true
|
||
|
null_values["NULL"] = true
|
||
|
|
||
|
timestamp_regexp = regexp.MustCompile("^([0-9][0-9][0-9][0-9])-([0-9][0-9]?)-([0-9][0-9]?)(?:(?:[Tt]|[ \t]+)([0-9][0-9]?):([0-9][0-9]):([0-9][0-9])(?:\\.([0-9]*))?(?:[ \t]*(?:Z|([-+][0-9][0-9]?)(?::([0-9][0-9])?)?))?)?$")
|
||
|
ymd_regexp = regexp.MustCompile("^([0-9][0-9][0-9][0-9])-([0-9][0-9]?)-([0-9][0-9]?)$")
|
||
|
}
|
||
|
|
||
|
func resolve(event yaml_event_t, v reflect.Value, useNumber bool) (string, error) {
|
||
|
val := string(event.value)
|
||
|
|
||
|
if null_values[val] {
|
||
|
v.Set(reflect.Zero(v.Type()))
|
||
|
return yaml_NULL_TAG, nil
|
||
|
}
|
||
|
|
||
|
switch v.Kind() {
|
||
|
case reflect.String:
|
||
|
if useNumber && v.Type() == numberType {
|
||
|
tag, i := resolveInterface(event, useNumber)
|
||
|
if n, ok := i.(Number); ok {
|
||
|
v.Set(reflect.ValueOf(n))
|
||
|
return tag, nil
|
||
|
}
|
||
|
return "", fmt.Errorf("Not a number: '%s' at %s", event.value, event.start_mark)
|
||
|
}
|
||
|
|
||
|
return resolve_string(val, v, event)
|
||
|
case reflect.Bool:
|
||
|
return resolve_bool(val, v, event)
|
||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||
|
return resolve_int(val, v, useNumber, event)
|
||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||
|
return resolve_uint(val, v, useNumber, event)
|
||
|
case reflect.Float32, reflect.Float64:
|
||
|
return resolve_float(val, v, useNumber, event)
|
||
|
case reflect.Interface:
|
||
|
_, i := resolveInterface(event, useNumber)
|
||
|
if i != nil {
|
||
|
v.Set(reflect.ValueOf(i))
|
||
|
} else {
|
||
|
v.Set(reflect.Zero(v.Type()))
|
||
|
}
|
||
|
|
||
|
case reflect.Struct:
|
||
|
return resolve_time(val, v, event)
|
||
|
case reflect.Slice:
|
||
|
if v.Type() != byteSliceType {
|
||
|
return "", fmt.Errorf("Cannot resolve %s into %s at %s", val, v.String(), event.start_mark)
|
||
|
}
|
||
|
b, err := decode_binary(event.value, event)
|
||
|
if err != nil {
|
||
|
return "", err
|
||
|
}
|
||
|
|
||
|
v.Set(reflect.ValueOf(b))
|
||
|
default:
|
||
|
return "", fmt.Errorf("Unknown resolution for '%s' using %s at %s", val, v.String(), event.start_mark)
|
||
|
}
|
||
|
|
||
|
return yaml_STR_TAG, nil
|
||
|
}
|
||
|
|
||
|
func hasBinaryTag(event yaml_event_t) bool {
|
||
|
for _, tag := range binary_tags {
|
||
|
if bytes.Equal(event.tag, tag) {
|
||
|
return true
|
||
|
}
|
||
|
}
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
func decode_binary(value []byte, event yaml_event_t) ([]byte, error) {
|
||
|
b := make([]byte, base64.StdEncoding.DecodedLen(len(value)))
|
||
|
n, err := base64.StdEncoding.Decode(b, value)
|
||
|
if err != nil {
|
||
|
return nil, fmt.Errorf("Invalid base64 text: '%s' at %s", string(b), event.start_mark)
|
||
|
}
|
||
|
return b[:n], nil
|
||
|
}
|
||
|
|
||
|
func resolve_string(val string, v reflect.Value, event yaml_event_t) (string, error) {
|
||
|
if len(event.tag) > 0 {
|
||
|
if hasBinaryTag(event) {
|
||
|
b, err := decode_binary(event.value, event)
|
||
|
if err != nil {
|
||
|
return "", err
|
||
|
}
|
||
|
val = string(b)
|
||
|
}
|
||
|
}
|
||
|
v.SetString(val)
|
||
|
return yaml_STR_TAG, nil
|
||
|
}
|
||
|
|
||
|
func resolve_bool(val string, v reflect.Value, event yaml_event_t) (string, error) {
|
||
|
b, found := bool_values[strings.ToLower(val)]
|
||
|
if !found {
|
||
|
return "", fmt.Errorf("Invalid boolean: '%s' at %s", val, event.start_mark)
|
||
|
}
|
||
|
|
||
|
v.SetBool(b)
|
||
|
return yaml_BOOL_TAG, nil
|
||
|
}
|
||
|
|
||
|
func resolve_int(val string, v reflect.Value, useNumber bool, event yaml_event_t) (string, error) {
|
||
|
original := val
|
||
|
val = strings.Replace(val, "_", "", -1)
|
||
|
var value uint64
|
||
|
|
||
|
isNumberValue := v.Type() == numberType
|
||
|
|
||
|
sign := int64(1)
|
||
|
if val[0] == '-' {
|
||
|
sign = -1
|
||
|
val = val[1:]
|
||
|
} else if val[0] == '+' {
|
||
|
val = val[1:]
|
||
|
}
|
||
|
|
||
|
base := 0
|
||
|
if val == "0" {
|
||
|
if isNumberValue {
|
||
|
v.SetString("0")
|
||
|
} else {
|
||
|
v.Set(reflect.Zero(v.Type()))
|
||
|
}
|
||
|
|
||
|
return yaml_INT_TAG, nil
|
||
|
}
|
||
|
|
||
|
if strings.HasPrefix(val, "0o") {
|
||
|
base = 8
|
||
|
val = val[2:]
|
||
|
}
|
||
|
|
||
|
value, err := strconv.ParseUint(val, base, 64)
|
||
|
if err != nil {
|
||
|
return "", fmt.Errorf("Invalid integer: '%s' at %s", original, event.start_mark)
|
||
|
}
|
||
|
|
||
|
var val64 int64
|
||
|
if value <= math.MaxInt64 {
|
||
|
val64 = int64(value)
|
||
|
if sign == -1 {
|
||
|
val64 = -val64
|
||
|
}
|
||
|
} else if sign == -1 && value == uint64(math.MaxInt64)+1 {
|
||
|
val64 = math.MinInt64
|
||
|
} else {
|
||
|
return "", fmt.Errorf("Invalid integer: '%s' at %s", original, event.start_mark)
|
||
|
}
|
||
|
|
||
|
if isNumberValue {
|
||
|
v.SetString(strconv.FormatInt(val64, 10))
|
||
|
} else {
|
||
|
if v.OverflowInt(val64) {
|
||
|
return "", fmt.Errorf("Invalid integer: '%s' at %s", original, event.start_mark)
|
||
|
}
|
||
|
v.SetInt(val64)
|
||
|
}
|
||
|
|
||
|
return yaml_INT_TAG, nil
|
||
|
}
|
||
|
|
||
|
func resolve_uint(val string, v reflect.Value, useNumber bool, event yaml_event_t) (string, error) {
|
||
|
original := val
|
||
|
val = strings.Replace(val, "_", "", -1)
|
||
|
var value uint64
|
||
|
|
||
|
isNumberValue := v.Type() == numberType
|
||
|
|
||
|
if val[0] == '-' {
|
||
|
return "", fmt.Errorf("Unsigned int with negative value: '%s' at %s", original, event.start_mark)
|
||
|
}
|
||
|
|
||
|
if val[0] == '+' {
|
||
|
val = val[1:]
|
||
|
}
|
||
|
|
||
|
base := 0
|
||
|
if val == "0" {
|
||
|
if isNumberValue {
|
||
|
v.SetString("0")
|
||
|
} else {
|
||
|
v.Set(reflect.Zero(v.Type()))
|
||
|
}
|
||
|
|
||
|
return yaml_INT_TAG, nil
|
||
|
}
|
||
|
|
||
|
if strings.HasPrefix(val, "0o") {
|
||
|
base = 8
|
||
|
val = val[2:]
|
||
|
}
|
||
|
|
||
|
value, err := strconv.ParseUint(val, base, 64)
|
||
|
if err != nil {
|
||
|
return "", fmt.Errorf("Invalid unsigned integer: '%s' at %s", val, event.start_mark)
|
||
|
}
|
||
|
|
||
|
if isNumberValue {
|
||
|
v.SetString(strconv.FormatUint(value, 10))
|
||
|
} else {
|
||
|
if v.OverflowUint(value) {
|
||
|
return "", fmt.Errorf("Invalid unsigned integer: '%s' at %s", val, event.start_mark)
|
||
|
}
|
||
|
|
||
|
v.SetUint(value)
|
||
|
}
|
||
|
|
||
|
return yaml_INT_TAG, nil
|
||
|
}
|
||
|
|
||
|
func resolve_float(val string, v reflect.Value, useNumber bool, event yaml_event_t) (string, error) {
|
||
|
val = strings.Replace(val, "_", "", -1)
|
||
|
var value float64
|
||
|
|
||
|
isNumberValue := v.Type() == numberType
|
||
|
typeBits := 64
|
||
|
if !isNumberValue {
|
||
|
typeBits = v.Type().Bits()
|
||
|
}
|
||
|
|
||
|
sign := 1
|
||
|
if val[0] == '-' {
|
||
|
sign = -1
|
||
|
val = val[1:]
|
||
|
} else if val[0] == '+' {
|
||
|
val = val[1:]
|
||
|
}
|
||
|
|
||
|
valLower := strings.ToLower(val)
|
||
|
if valLower == ".inf" {
|
||
|
value = math.Inf(sign)
|
||
|
} else if valLower == ".nan" {
|
||
|
value = math.NaN()
|
||
|
} else {
|
||
|
var err error
|
||
|
value, err = strconv.ParseFloat(val, typeBits)
|
||
|
value *= float64(sign)
|
||
|
|
||
|
if err != nil {
|
||
|
return "", fmt.Errorf("Invalid float: '%s' at %s", val, event.start_mark)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if isNumberValue {
|
||
|
v.SetString(strconv.FormatFloat(value, 'g', -1, typeBits))
|
||
|
} else {
|
||
|
if v.OverflowFloat(value) {
|
||
|
return "", fmt.Errorf("Invalid float: '%s' at %s", val, event.start_mark)
|
||
|
}
|
||
|
|
||
|
v.SetFloat(value)
|
||
|
}
|
||
|
|
||
|
return yaml_FLOAT_TAG, nil
|
||
|
}
|
||
|
|
||
|
func resolve_time(val string, v reflect.Value, event yaml_event_t) (string, error) {
|
||
|
var parsedTime time.Time
|
||
|
matches := ymd_regexp.FindStringSubmatch(val)
|
||
|
if len(matches) > 0 {
|
||
|
year, _ := strconv.Atoi(matches[1])
|
||
|
month, _ := strconv.Atoi(matches[2])
|
||
|
day, _ := strconv.Atoi(matches[3])
|
||
|
parsedTime = time.Date(year, time.Month(month), day, 0, 0, 0, 0, time.UTC)
|
||
|
} else {
|
||
|
matches = timestamp_regexp.FindStringSubmatch(val)
|
||
|
if len(matches) == 0 {
|
||
|
return "", fmt.Errorf("Invalid timestamp: '%s' at %s", val, event.start_mark)
|
||
|
}
|
||
|
|
||
|
year, _ := strconv.Atoi(matches[1])
|
||
|
month, _ := strconv.Atoi(matches[2])
|
||
|
day, _ := strconv.Atoi(matches[3])
|
||
|
hour, _ := strconv.Atoi(matches[4])
|
||
|
min, _ := strconv.Atoi(matches[5])
|
||
|
sec, _ := strconv.Atoi(matches[6])
|
||
|
|
||
|
nsec := 0
|
||
|
if matches[7] != "" {
|
||
|
millis, _ := strconv.Atoi(matches[7])
|
||
|
nsec = int(time.Duration(millis) * time.Millisecond)
|
||
|
}
|
||
|
|
||
|
loc := time.UTC
|
||
|
if matches[8] != "" {
|
||
|
sign := matches[8][0]
|
||
|
hr, _ := strconv.Atoi(matches[8][1:])
|
||
|
min := 0
|
||
|
if matches[9] != "" {
|
||
|
min, _ = strconv.Atoi(matches[9])
|
||
|
}
|
||
|
|
||
|
zoneOffset := (hr*60 + min) * 60
|
||
|
if sign == '-' {
|
||
|
zoneOffset = -zoneOffset
|
||
|
}
|
||
|
|
||
|
loc = time.FixedZone("", zoneOffset)
|
||
|
}
|
||
|
parsedTime = time.Date(year, time.Month(month), day, hour, min, sec, nsec, loc)
|
||
|
}
|
||
|
|
||
|
v.Set(reflect.ValueOf(parsedTime))
|
||
|
return "", nil
|
||
|
}
|
||
|
|
||
|
func resolveInterface(event yaml_event_t, useNumber bool) (string, interface{}) {
|
||
|
val := string(event.value)
|
||
|
if len(event.tag) == 0 && !event.implicit {
|
||
|
return "", val
|
||
|
}
|
||
|
|
||
|
if len(val) == 0 {
|
||
|
return yaml_NULL_TAG, nil
|
||
|
}
|
||
|
|
||
|
var result interface{}
|
||
|
|
||
|
sign := false
|
||
|
c := val[0]
|
||
|
switch {
|
||
|
case bytes.IndexByte(signs, c) != -1:
|
||
|
sign = true
|
||
|
fallthrough
|
||
|
case c >= '0' && c <= '9':
|
||
|
i := int64(0)
|
||
|
result = &i
|
||
|
if useNumber {
|
||
|
var n Number
|
||
|
result = &n
|
||
|
}
|
||
|
|
||
|
v := reflect.ValueOf(result).Elem()
|
||
|
if _, err := resolve_int(val, v, useNumber, event); err == nil {
|
||
|
return yaml_INT_TAG, v.Interface()
|
||
|
}
|
||
|
|
||
|
f := float64(0)
|
||
|
result = &f
|
||
|
if useNumber {
|
||
|
var n Number
|
||
|
result = &n
|
||
|
}
|
||
|
|
||
|
v = reflect.ValueOf(result).Elem()
|
||
|
if _, err := resolve_float(val, v, useNumber, event); err == nil {
|
||
|
return yaml_FLOAT_TAG, v.Interface()
|
||
|
}
|
||
|
|
||
|
if !sign {
|
||
|
t := time.Time{}
|
||
|
if _, err := resolve_time(val, reflect.ValueOf(&t).Elem(), event); err == nil {
|
||
|
return "", t
|
||
|
}
|
||
|
}
|
||
|
case bytes.IndexByte(nulls, c) != -1:
|
||
|
if null_values[val] {
|
||
|
return yaml_NULL_TAG, nil
|
||
|
}
|
||
|
b := false
|
||
|
if _, err := resolve_bool(val, reflect.ValueOf(&b).Elem(), event); err == nil {
|
||
|
return yaml_BOOL_TAG, b
|
||
|
}
|
||
|
case c == '.':
|
||
|
f := float64(0)
|
||
|
result = &f
|
||
|
if useNumber {
|
||
|
var n Number
|
||
|
result = &n
|
||
|
}
|
||
|
|
||
|
v := reflect.ValueOf(result).Elem()
|
||
|
if _, err := resolve_float(val, v, useNumber, event); err == nil {
|
||
|
return yaml_FLOAT_TAG, v.Interface()
|
||
|
}
|
||
|
case bytes.IndexByte(bools, c) != -1:
|
||
|
b := false
|
||
|
if _, err := resolve_bool(val, reflect.ValueOf(&b).Elem(), event); err == nil {
|
||
|
return yaml_BOOL_TAG, b
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if hasBinaryTag(event) {
|
||
|
bytes, err := decode_binary(event.value, event)
|
||
|
if err == nil {
|
||
|
return yaml_BINARY_TAG, bytes
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return yaml_STR_TAG, val
|
||
|
}
|