pax_global_header 0000666 0000000 0000000 00000000064 14167531274 0014524 g ustar 00root root 0000000 0000000 52 comment=e4f32a26722ce33ac79a7b4ad88c26fd0b9c2995
golang-github-goccy-go-yaml-1.9.5/ 0000775 0000000 0000000 00000000000 14167531274 0016734 5 ustar 00root root 0000000 0000000 golang-github-goccy-go-yaml-1.9.5/.codecov.yml 0000664 0000000 0000000 00000000634 14167531274 0021162 0 ustar 00root root 0000000 0000000 codecov:
require_ci_to_pass: yes
coverage:
precision: 2
round: down
range: "70...100"
status:
project:
default:
target: 75%
threshold: 2%
patch: off
changes: no
parsers:
gcov:
branch_detection:
conditional: yes
loop: yes
method: no
macro: no
comment:
layout: "header,diff"
behavior: default
require_changes: no
ignore:
- ast
golang-github-goccy-go-yaml-1.9.5/.github/ 0000775 0000000 0000000 00000000000 14167531274 0020274 5 ustar 00root root 0000000 0000000 golang-github-goccy-go-yaml-1.9.5/.github/FUNDING.yml 0000664 0000000 0000000 00000000020 14167531274 0022101 0 ustar 00root root 0000000 0000000 github: [goccy]
golang-github-goccy-go-yaml-1.9.5/.github/workflows/ 0000775 0000000 0000000 00000000000 14167531274 0022331 5 ustar 00root root 0000000 0000000 golang-github-goccy-go-yaml-1.9.5/.github/workflows/go.yml 0000664 0000000 0000000 00000002364 14167531274 0023466 0 ustar 00root root 0000000 0000000 name: Go
on:
push:
branches:
- master
pull_request:
jobs:
test:
name: Test
strategy:
matrix:
os: [ "ubuntu-latest", "macos-latest", "windows-latest" ]
go-version: [ "1.15", "1.16", "1.17" ]
runs-on: ${{ matrix.os }}
steps:
- name: setup Go ${{ matrix.go-version }}
uses: actions/setup-go@v2
with:
go-version: ${{ matrix.go-version }}
- name: checkout
uses: actions/checkout@v2
- name: test
run: |
make test
ycat:
name: ycat
runs-on: ubuntu-latest
steps:
- name: setup Go
uses: actions/setup-go@v2
with:
go-version: 1.17
- name: checkout
uses: actions/checkout@v2
- name: build
run: |
make ycat/build
- name: run
run: |
./ycat .github/workflows/go.yml
coverage:
name: Coverage
runs-on: ubuntu-latest
steps:
- name: setup Go
uses: actions/setup-go@v2
with:
go-version: 1.17
- name: checkout
uses: actions/checkout@v2
- name: measure coverage
run: |
make cover
- uses: codecov/codecov-action@v2
with:
fail_ci_if_error: true
verbose: true
golang-github-goccy-go-yaml-1.9.5/CHANGELOG.md 0000664 0000000 0000000 00000006100 14167531274 0020542 0 ustar 00root root 0000000 0000000 ## v1.9.5 - 2022-01-12
### New Features
* Add UseSingleQuote option ( #265 )
### Fix bugs
* Preserve defaults while decoding nested structs ( #260 )
* Fix minor typo in decodeInit error ( #264 )
* Handle empty sequence entries ( #275 )
* Fix encoding of sequence with multiline string ( #276 )
* Fix encoding of BytesMarshaler type ( #277 )
* Fix indentState logic for multi-line value ( #278 )
## v1.9.4 - 2021-10-12
### Fix bugs
* Keep prev/next reference between tokens containing comments when filtering comment tokens ( #257 )
* Supports escaping reserved keywords in PathBuilder ( #258 )
## v1.9.3 - 2021-09-07
### New Features
* Support encoding and decoding `time.Duration` fields ( #246 )
* Allow reserved characters for key name in YAMLPath ( #251 )
* Support getting YAMLPath from ast.Node ( #252 )
* Support CommentToMap option ( #253 )
### Fix bugs
* Fix encoding nested sequences with `yaml.IndentSequence` ( #241 )
* Fix error reporting on inline structs in strict mode ( #244, #245 )
* Fix encoding of large floats ( #247 )
### Improve workflow
* Migrate CI from CircleCI to GitHub Action ( #249 )
* Add workflow for ycat ( #250 )
## v1.9.2 - 2021-07-26
### Support WithComment option ( #238 )
`yaml.WithComment` is a option for encoding with comment.
The position where you want to add a comment is represented by YAMLPath, and it is the key of `yaml.CommentMap`.
Also, you can select `Head` comment or `Line` comment as the comment type.
## v1.9.1 - 2021-07-20
### Fix DecodeFromNode ( #237 )
- Fix YAML handling where anchor exists
## v1.9.0 - 2021-07-19
### New features
- Support encoding of comment node ( #233 )
- Support `yaml.NodeToValue(ast.Node, interface{}, ...DecodeOption) error` ( #236 )
- Can convert a AST node to a value directly
### Fix decoder for comment
- Fix parsing of literal with comment ( #234 )
### Rename API ( #235 )
- Rename `MarshalWithContext` to `MarshalContext`
- Rename `UnmarshalWithContext` to `UnmarshalContext`
## v1.8.10 - 2021-07-02
### Fixed bugs
- Fix searching anchor by alias name ( #212 )
- Fixing Issue 186, scanner should account for newline characters when processing multi-line text. Without this source annotations line/column number (for this and all subsequent tokens) is inconsistent with plain text editors. e.g. https://github.com/goccy/go-yaml/issues/186. This addresses the issue specifically for single and double quote text only. ( #210 )
- Add error for unterminated flow mapping node ( #213 )
- Handle missing required field validation ( #221 )
- Nicely format unexpected node type errors ( #229 )
- Support to encode map which has defined type key ( #231 )
### New features
- Support sequence indentation by EncodeOption ( #232 )
## v1.8.9 - 2021-03-01
### Fixed bugs
- Fix origin buffer for DocumentHeader and DocumentEnd and Directive
- Fix origin buffer for anchor value
- Fix syntax error about map value
- Fix parsing MergeKey ('<<') characters
- Fix encoding of float value
- Fix incorrect column annotation when single or double quotes are used
### New features
- Support to encode/decode of ast.Node directly
golang-github-goccy-go-yaml-1.9.5/LICENSE 0000664 0000000 0000000 00000002060 14167531274 0017737 0 ustar 00root root 0000000 0000000 MIT License
Copyright (c) 2019 Masaaki Goshima
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
golang-github-goccy-go-yaml-1.9.5/Makefile 0000664 0000000 0000000 00000000352 14167531274 0020374 0 ustar 00root root 0000000 0000000 .PHONY: test
test:
go test -v -race ./...
.PHONY: cover
cover:
go test -coverprofile=cover.out ./...
.PHONY: cover-html
cover-html: cover
go tool cover -html=cover.out
.PHONY: ycat/build
ycat/build:
go build -o ycat ./cmd/ycat
golang-github-goccy-go-yaml-1.9.5/README.md 0000664 0000000 0000000 00000021345 14167531274 0020220 0 ustar 00root root 0000000 0000000 # YAML support for the Go language
[](https://pkg.go.dev/github.com/goccy/go-yaml)

[](https://codecov.io/gh/goccy/go-yaml)
[](https://goreportcard.com/report/github.com/goccy/go-yaml)
# Why a new library?
As of this writing, there already exists a de facto standard library for YAML processing for Go: [https://github.com/go-yaml/yaml](https://github.com/go-yaml/yaml). However we feel that some features are lacking, namely:
- Pretty format for error notifications
- Direct manipulation of YAML abstract syntax tree
- Support for `Anchor` and `Alias` when marshaling
- Allow referencing elements declared in another file via anchors
# Features
- Pretty format for error notifications
- Supports `Scanner` or `Lexer` or `Parser` as public API
- Supports `Anchor` and `Alias` to Marshaler
- Allow referencing elements declared in another file via anchors
- Extract value or AST by YAMLPath ( YAMLPath is like a JSONPath )
# Installation
```sh
go get -u github.com/goccy/go-yaml
```
# Synopsis
## 1. Simple Encode/Decode
Has an interface like `go-yaml/yaml` using `reflect`
```go
var v struct {
A int
B string
}
v.A = 1
v.B = "hello"
bytes, err := yaml.Marshal(v)
if err != nil {
//...
}
fmt.Println(string(bytes)) // "a: 1\nb: hello\n"
```
```go
yml := `
%YAML 1.2
---
a: 1
b: c
`
var v struct {
A int
B string
}
if err := yaml.Unmarshal([]byte(yml), &v); err != nil {
//...
}
```
To control marshal/unmarshal behavior, you can use the `yaml` tag.
```go
yml := `---
foo: 1
bar: c
`
var v struct {
A int `yaml:"foo"`
B string `yaml:"bar"`
}
if err := yaml.Unmarshal([]byte(yml), &v); err != nil {
//...
}
```
For convenience, we also accept the `json` tag. Note that not all options from
the `json` tag will have significance when parsing YAML documents. If both
tags exist, `yaml` tag will take precedence.
```go
yml := `---
foo: 1
bar: c
`
var v struct {
A int `json:"foo"`
B string `json:"bar"`
}
if err := yaml.Unmarshal([]byte(yml), &v); err != nil {
//...
}
```
For custom marshal/unmarshaling, implement either `Bytes` or `Interface` variant of marshaler/unmarshaler. The difference is that while `BytesMarshaler`/`BytesUnmarshaler` behaves like [`encoding/json`](https://pkg.go.dev/encoding/json) and `InterfaceMarshaler`/`InterfaceUnmarshaler` behaves like [`gopkg.in/yaml.v2`](https://pkg.go.dev/gopkg.in/yaml.v2).
Semantically both are the same, but they differ in performance. Because indentation matters in YAML, you cannot simply accept a valid YAML fragment from a Marshaler, and expect it to work when it is attached to the parent container's serialized form. Therefore when we receive use the `BytesMarshaler`, which returns `[]byte`, we must decode it once to figure out how to make it work in the given context. If you use the `InterfaceMarshaler`, we can skip the decoding.
If you are repeatedly marshaling complex objects, the latter is always better
performance wise. But if you are, for example, just providing a choice between
a config file format that is read only once, the former is probably easier to
code.
## 2. Reference elements declared in another file
`testdata` directory contains `anchor.yml` file:
```shell
├── testdata
└── anchor.yml
```
And `anchor.yml` is defined as follows:
```yaml
a: &a
b: 1
c: hello
```
Then, if `yaml.ReferenceDirs("testdata")` option is passed to `yaml.Decoder`,
`Decoder` tries to find the anchor definition from YAML files the under `testdata` directory.
```go
buf := bytes.NewBufferString("a: *a\n")
dec := yaml.NewDecoder(buf, yaml.ReferenceDirs("testdata"))
var v struct {
A struct {
B int
C string
}
}
if err := dec.Decode(&v); err != nil {
//...
}
fmt.Printf("%+v\n", v) // {A:{B:1 C:hello}}
```
## 3. Encode with `Anchor` and `Alias`
### 3.1. Explicitly declared `Anchor` name and `Alias` name
If you want to use `anchor` or `alias`, you can define it as a struct tag.
```go
type T struct {
A int
B string
}
var v struct {
C *T `yaml:"c,anchor=x"`
D *T `yaml:"d,alias=x"`
}
v.C = &T{A: 1, B: "hello"}
v.D = v.C
bytes, err := yaml.Marshal(v)
if err != nil {
panic(err)
}
fmt.Println(string(bytes))
/*
c: &x
a: 1
b: hello
d: *x
*/
```
### 3.2. Implicitly declared `Anchor` and `Alias` names
If you do not explicitly declare the anchor name, the default behavior is to
use the equivalent of `strings.ToLower($FieldName)` as the name of the anchor.
If you do not explicitly declare the alias name AND the value is a pointer
to another element, we look up the anchor name by finding out which anchor
field the value is assigned to by looking up its pointer address.
```go
type T struct {
I int
S string
}
var v struct {
A *T `yaml:"a,anchor"`
B *T `yaml:"b,anchor"`
C *T `yaml:"c,alias"`
D *T `yaml:"d,alias"`
}
v.A = &T{I: 1, S: "hello"}
v.B = &T{I: 2, S: "world"}
v.C = v.A // C has same pointer address to A
v.D = v.B // D has same pointer address to B
bytes, err := yaml.Marshal(v)
if err != nil {
//...
}
fmt.Println(string(bytes))
/*
a: &a
i: 1
s: hello
b: &b
i: 2
s: world
c: *a
d: *b
*/
```
### 3.3 MergeKey and Alias
Merge key and alias ( `<<: *alias` ) can be used by embedding a structure with the `inline,alias` tag.
```go
type Person struct {
*Person `yaml:",omitempty,inline,alias"` // embed Person type for default value
Name string `yaml:",omitempty"`
Age int `yaml:",omitempty"`
}
defaultPerson := &Person{
Name: "John Smith",
Age: 20,
}
people := []*Person{
{
Person: defaultPerson, // assign default value
Name: "Ken", // override Name property
Age: 10, // override Age property
},
{
Person: defaultPerson, // assign default value only
},
}
var doc struct {
Default *Person `yaml:"default,anchor"`
People []*Person `yaml:"people"`
}
doc.Default = defaultPerson
doc.People = people
bytes, err := yaml.Marshal(doc)
if err != nil {
//...
}
fmt.Println(string(bytes))
/*
default: &default
name: John Smith
age: 20
people:
- <<: *default
name: Ken
age: 10
- <<: *default
*/
```
## 4. Pretty Formatted Errors
Error values produced during parsing have two extra features over regular
error values.
First, by default, they contain extra information on the location of the error
from the source YAML document, to make it easier to find the error location.
Second, the error messages can optionally be colorized.
If you would like to control exactly how the output looks like, consider
using `yaml.FormatError`, which accepts two boolean values to
control turning these features on or off.
## 5. Use YAMLPath
```go
yml := `
store:
book:
- author: john
price: 10
- author: ken
price: 12
bicycle:
color: red
price: 19.95
`
path, err := yaml.PathString("$.store.book[*].author")
if err != nil {
//...
}
var authors []string
if err := path.Read(strings.NewReader(yml), &authors); err != nil {
//...
}
fmt.Println(authors)
// [john ken]
```
### 5.1 Print customized error with YAML source code
```go
package main
import (
"fmt"
"github.com/goccy/go-yaml"
)
func main() {
yml := `
a: 1
b: "hello"
`
var v struct {
A int
B string
}
if err := yaml.Unmarshal([]byte(yml), &v); err != nil {
panic(err)
}
if v.A != 2 {
// output error with YAML source
path, err := yaml.PathString("$.a")
if err != nil {
panic(err)
}
source, err := path.AnnotateSource([]byte(yml), true)
if err != nil {
panic(err)
}
fmt.Printf("a value expected 2 but actual %d:\n%s\n", v.A, string(source))
}
}
```
output result is the following:
# Tools
## ycat
print yaml file with color
### Installation
```sh
go install github.com/goccy/go-yaml/cmd/ycat@latest
```
# Looking for Sponsors
I'm looking for sponsors this library. This library is being developed as a personal project in my spare time. If you want a quick response or problem resolution when using this library in your project, please register as a [sponsor](https://github.com/sponsors/goccy). I will cooperate as much as possible. Of course, this library is developed as an MIT license, so you can use it freely for free.
# License
MIT
golang-github-goccy-go-yaml-1.9.5/ast/ 0000775 0000000 0000000 00000000000 14167531274 0017523 5 ustar 00root root 0000000 0000000 golang-github-goccy-go-yaml-1.9.5/ast/ast.go 0000664 0000000 0000000 00000143653 14167531274 0020655 0 ustar 00root root 0000000 0000000 package ast
import (
"fmt"
"io"
"math"
"strconv"
"strings"
"github.com/goccy/go-yaml/token"
"golang.org/x/xerrors"
)
var (
ErrInvalidTokenType = xerrors.New("invalid token type")
ErrInvalidAnchorName = xerrors.New("invalid anchor name")
ErrInvalidAliasName = xerrors.New("invalid alias name")
)
// NodeType type identifier of node
type NodeType int
const (
// UnknownNodeType type identifier for default
UnknownNodeType NodeType = iota
// DocumentType type identifier for document node
DocumentType
// NullType type identifier for null node
NullType
// BoolType type identifier for boolean node
BoolType
// IntegerType type identifier for integer node
IntegerType
// FloatType type identifier for float node
FloatType
// InfinityType type identifier for infinity node
InfinityType
// NanType type identifier for nan node
NanType
// StringType type identifier for string node
StringType
// MergeKeyType type identifier for merge key node
MergeKeyType
// LiteralType type identifier for literal node
LiteralType
// MappingType type identifier for mapping node
MappingType
// MappingKeyType type identifier for mapping key node
MappingKeyType
// MappingValueType type identifier for mapping value node
MappingValueType
// SequenceType type identifier for sequence node
SequenceType
// AnchorType type identifier for anchor node
AnchorType
// AliasType type identifier for alias node
AliasType
// DirectiveType type identifier for directive node
DirectiveType
// TagType type identifier for tag node
TagType
// CommentType type identifier for comment node
CommentType
// CommentGroupType type identifier for comment group node
CommentGroupType
)
// String node type identifier to text
func (t NodeType) String() string {
switch t {
case UnknownNodeType:
return "UnknownNode"
case DocumentType:
return "Document"
case NullType:
return "Null"
case BoolType:
return "Bool"
case IntegerType:
return "Integer"
case FloatType:
return "Float"
case InfinityType:
return "Infinity"
case NanType:
return "Nan"
case StringType:
return "String"
case MergeKeyType:
return "MergeKey"
case LiteralType:
return "Literal"
case MappingType:
return "Mapping"
case MappingKeyType:
return "MappingKey"
case MappingValueType:
return "MappingValue"
case SequenceType:
return "Sequence"
case AnchorType:
return "Anchor"
case AliasType:
return "Alias"
case DirectiveType:
return "Directive"
case TagType:
return "Tag"
case CommentType:
return "Comment"
case CommentGroupType:
return "CommentGroup"
}
return ""
}
// String node type identifier to YAML Structure name
// based on https://yaml.org/spec/1.2/spec.html
func (t NodeType) YAMLName() string {
switch t {
case UnknownNodeType:
return "unknown"
case DocumentType:
return "document"
case NullType:
return "null"
case BoolType:
return "boolean"
case IntegerType:
return "int"
case FloatType:
return "float"
case InfinityType:
return "inf"
case NanType:
return "nan"
case StringType:
return "string"
case MergeKeyType:
return "merge key"
case LiteralType:
return "scalar"
case MappingType:
return "mapping"
case MappingKeyType:
return "key"
case MappingValueType:
return "value"
case SequenceType:
return "sequence"
case AnchorType:
return "anchor"
case AliasType:
return "alias"
case DirectiveType:
return "directive"
case TagType:
return "tag"
case CommentType:
return "comment"
case CommentGroupType:
return "comment"
}
return ""
}
// Node type of node
type Node interface {
io.Reader
// String node to text
String() string
// GetToken returns token instance
GetToken() *token.Token
// Type returns type of node
Type() NodeType
// AddColumn add column number to child nodes recursively
AddColumn(int)
// SetComment set comment token to node
SetComment(*CommentGroupNode) error
// Comment returns comment token instance
GetComment() *CommentGroupNode
// GetPath returns YAMLPath for the current node
GetPath() string
// SetPath set YAMLPath for the current node
SetPath(string)
// MarshalYAML
MarshalYAML() ([]byte, error)
// already read length
readLen() int
// append read length
addReadLen(int)
// clean read length
clearLen()
// String node to text without comment
stringWithoutComment() string
}
// ScalarNode type for scalar node
type ScalarNode interface {
Node
GetValue() interface{}
}
type BaseNode struct {
Path string
Comment *CommentGroupNode
read int
}
func addCommentString(base string, node *CommentGroupNode) string {
return fmt.Sprintf("%s %s", base, node.String())
}
func (n *BaseNode) readLen() int {
return n.read
}
func (n *BaseNode) clearLen() {
n.read = 0
}
func (n *BaseNode) addReadLen(len int) {
n.read += len
}
// GetPath returns YAMLPath for the current node.
func (n *BaseNode) GetPath() string {
if n == nil {
return ""
}
return n.Path
}
// SetPath set YAMLPath for the current node.
func (n *BaseNode) SetPath(path string) {
if n == nil {
return
}
n.Path = path
}
// GetComment returns comment token instance
func (n *BaseNode) GetComment() *CommentGroupNode {
return n.Comment
}
// SetComment set comment token
func (n *BaseNode) SetComment(node *CommentGroupNode) error {
n.Comment = node
return nil
}
func min(a, b int) int {
if a < b {
return a
}
return b
}
func readNode(p []byte, node Node) (int, error) {
s := node.String()
readLen := node.readLen()
remain := len(s) - readLen
if remain == 0 {
node.clearLen()
return 0, io.EOF
}
size := min(remain, len(p))
for idx, b := range s[readLen : readLen+size] {
p[idx] = byte(b)
}
node.addReadLen(size)
return size, nil
}
// Null create node for null value
func Null(tk *token.Token) Node {
return &NullNode{
BaseNode: &BaseNode{},
Token: tk,
}
}
// Bool create node for boolean value
func Bool(tk *token.Token) Node {
b, _ := strconv.ParseBool(tk.Value)
return &BoolNode{
BaseNode: &BaseNode{},
Token: tk,
Value: b,
}
}
// Integer create node for integer value
func Integer(tk *token.Token) Node {
value := removeUnderScoreFromNumber(tk.Value)
switch tk.Type {
case token.BinaryIntegerType:
// skip two characters because binary token starts with '0b'
skipCharacterNum := 2
negativePrefix := ""
if value[0] == '-' {
skipCharacterNum++
negativePrefix = "-"
}
if len(negativePrefix) > 0 {
i, _ := strconv.ParseInt(negativePrefix+value[skipCharacterNum:], 2, 64)
return &IntegerNode{
BaseNode: &BaseNode{},
Token: tk,
Value: i,
}
}
i, _ := strconv.ParseUint(negativePrefix+value[skipCharacterNum:], 2, 64)
return &IntegerNode{
BaseNode: &BaseNode{},
Token: tk,
Value: i,
}
case token.OctetIntegerType:
// octet token starts with '0o' or '-0o' or '0' or '-0'
skipCharacterNum := 1
negativePrefix := ""
if value[0] == '-' {
skipCharacterNum++
if len(value) > 2 && value[2] == 'o' {
skipCharacterNum++
}
negativePrefix = "-"
} else {
if value[1] == 'o' {
skipCharacterNum++
}
}
if len(negativePrefix) > 0 {
i, _ := strconv.ParseInt(negativePrefix+value[skipCharacterNum:], 8, 64)
return &IntegerNode{
BaseNode: &BaseNode{},
Token: tk,
Value: i,
}
}
i, _ := strconv.ParseUint(value[skipCharacterNum:], 8, 64)
return &IntegerNode{
BaseNode: &BaseNode{},
Token: tk,
Value: i,
}
case token.HexIntegerType:
// hex token starts with '0x' or '-0x'
skipCharacterNum := 2
negativePrefix := ""
if value[0] == '-' {
skipCharacterNum++
negativePrefix = "-"
}
if len(negativePrefix) > 0 {
i, _ := strconv.ParseInt(negativePrefix+value[skipCharacterNum:], 16, 64)
return &IntegerNode{
BaseNode: &BaseNode{},
Token: tk,
Value: i,
}
}
i, _ := strconv.ParseUint(value[skipCharacterNum:], 16, 64)
return &IntegerNode{
BaseNode: &BaseNode{},
Token: tk,
Value: i,
}
}
if value[0] == '-' || value[0] == '+' {
i, _ := strconv.ParseInt(value, 10, 64)
return &IntegerNode{
BaseNode: &BaseNode{},
Token: tk,
Value: i,
}
}
i, _ := strconv.ParseUint(value, 10, 64)
return &IntegerNode{
BaseNode: &BaseNode{},
Token: tk,
Value: i,
}
}
// Float create node for float value
func Float(tk *token.Token) Node {
f, _ := strconv.ParseFloat(removeUnderScoreFromNumber(tk.Value), 64)
return &FloatNode{
BaseNode: &BaseNode{},
Token: tk,
Value: f,
}
}
// Infinity create node for .inf or -.inf value
func Infinity(tk *token.Token) *InfinityNode {
node := &InfinityNode{
BaseNode: &BaseNode{},
Token: tk,
}
switch tk.Value {
case ".inf", ".Inf", ".INF":
node.Value = math.Inf(0)
case "-.inf", "-.Inf", "-.INF":
node.Value = math.Inf(-1)
}
return node
}
// Nan create node for .nan value
func Nan(tk *token.Token) *NanNode {
return &NanNode{
BaseNode: &BaseNode{},
Token: tk,
}
}
// String create node for string value
func String(tk *token.Token) *StringNode {
return &StringNode{
BaseNode: &BaseNode{},
Token: tk,
Value: tk.Value,
}
}
// Comment create node for comment
func Comment(tk *token.Token) *CommentNode {
return &CommentNode{
BaseNode: &BaseNode{},
Token: tk,
}
}
func CommentGroup(comments []*token.Token) *CommentGroupNode {
nodes := []*CommentNode{}
for _, comment := range comments {
nodes = append(nodes, Comment(comment))
}
return &CommentGroupNode{
BaseNode: &BaseNode{},
Comments: nodes,
}
}
// MergeKey create node for merge key ( << )
func MergeKey(tk *token.Token) *MergeKeyNode {
return &MergeKeyNode{
BaseNode: &BaseNode{},
Token: tk,
}
}
// Mapping create node for map
func Mapping(tk *token.Token, isFlowStyle bool, values ...*MappingValueNode) *MappingNode {
node := &MappingNode{
BaseNode: &BaseNode{},
Start: tk,
IsFlowStyle: isFlowStyle,
Values: []*MappingValueNode{},
}
node.Values = append(node.Values, values...)
return node
}
// MappingValue create node for mapping value
func MappingValue(tk *token.Token, key Node, value Node) *MappingValueNode {
return &MappingValueNode{
BaseNode: &BaseNode{},
Start: tk,
Key: key,
Value: value,
}
}
// MappingKey create node for map key ( '?' ).
func MappingKey(tk *token.Token) *MappingKeyNode {
return &MappingKeyNode{
BaseNode: &BaseNode{},
Start: tk,
}
}
// Sequence create node for sequence
func Sequence(tk *token.Token, isFlowStyle bool) *SequenceNode {
return &SequenceNode{
BaseNode: &BaseNode{},
Start: tk,
IsFlowStyle: isFlowStyle,
Values: []Node{},
}
}
func Anchor(tk *token.Token) *AnchorNode {
return &AnchorNode{
BaseNode: &BaseNode{},
Start: tk,
}
}
func Alias(tk *token.Token) *AliasNode {
return &AliasNode{
BaseNode: &BaseNode{},
Start: tk,
}
}
func Document(tk *token.Token, body Node) *DocumentNode {
return &DocumentNode{
BaseNode: &BaseNode{},
Start: tk,
Body: body,
}
}
func Directive(tk *token.Token) *DirectiveNode {
return &DirectiveNode{
BaseNode: &BaseNode{},
Start: tk,
}
}
func Literal(tk *token.Token) *LiteralNode {
return &LiteralNode{
BaseNode: &BaseNode{},
Start: tk,
}
}
func Tag(tk *token.Token) *TagNode {
return &TagNode{
BaseNode: &BaseNode{},
Start: tk,
}
}
// File contains all documents in YAML file
type File struct {
Name string
Docs []*DocumentNode
}
// Read implements (io.Reader).Read
func (f *File) Read(p []byte) (int, error) {
for _, doc := range f.Docs {
n, err := doc.Read(p)
if err == io.EOF {
continue
}
return n, nil
}
return 0, io.EOF
}
// String all documents to text
func (f *File) String() string {
docs := []string{}
for _, doc := range f.Docs {
docs = append(docs, doc.String())
}
return strings.Join(docs, "\n")
}
func (f *File) stringWithoutComment() string {
return f.String()
}
// DocumentNode type of Document
type DocumentNode struct {
*BaseNode
Start *token.Token // position of DocumentHeader ( `---` )
End *token.Token // position of DocumentEnd ( `...` )
Body Node
}
// Read implements (io.Reader).Read
func (d *DocumentNode) Read(p []byte) (int, error) {
return readNode(p, d)
}
// Type returns DocumentNodeType
func (d *DocumentNode) Type() NodeType { return DocumentType }
// GetToken returns token instance
func (d *DocumentNode) GetToken() *token.Token {
return d.Body.GetToken()
}
// AddColumn add column number to child nodes recursively
func (d *DocumentNode) AddColumn(col int) {
if d.Body != nil {
d.Body.AddColumn(col)
}
}
// String document to text
func (d *DocumentNode) String() string {
doc := []string{}
if d.Start != nil {
doc = append(doc, d.Start.Value)
}
doc = append(doc, d.Body.String())
if d.End != nil {
doc = append(doc, d.End.Value)
}
return strings.Join(doc, "\n")
}
func (d *DocumentNode) stringWithoutComment() string {
return d.String()
}
// MarshalYAML encodes to a YAML text
func (d *DocumentNode) MarshalYAML() ([]byte, error) {
return []byte(d.String()), nil
}
func removeUnderScoreFromNumber(num string) string {
return strings.ReplaceAll(num, "_", "")
}
// NullNode type of null node
type NullNode struct {
*BaseNode
Token *token.Token
}
// Read implements (io.Reader).Read
func (n *NullNode) Read(p []byte) (int, error) {
return readNode(p, n)
}
// Type returns NullType
func (n *NullNode) Type() NodeType { return NullType }
// GetToken returns token instance
func (n *NullNode) GetToken() *token.Token {
return n.Token
}
// AddColumn add column number to child nodes recursively
func (n *NullNode) AddColumn(col int) {
n.Token.AddColumn(col)
}
// GetValue returns nil value
func (n *NullNode) GetValue() interface{} {
return nil
}
// String returns `null` text
func (n *NullNode) String() string {
if n.Comment != nil {
return fmt.Sprintf("null %s", n.Comment.String())
}
return n.stringWithoutComment()
}
func (n *NullNode) stringWithoutComment() string {
return "null"
}
// MarshalYAML encodes to a YAML text
func (n *NullNode) MarshalYAML() ([]byte, error) {
return []byte(n.String()), nil
}
// IntegerNode type of integer node
type IntegerNode struct {
*BaseNode
Token *token.Token
Value interface{} // int64 or uint64 value
}
// Read implements (io.Reader).Read
func (n *IntegerNode) Read(p []byte) (int, error) {
return readNode(p, n)
}
// Type returns IntegerType
func (n *IntegerNode) Type() NodeType { return IntegerType }
// GetToken returns token instance
func (n *IntegerNode) GetToken() *token.Token {
return n.Token
}
// AddColumn add column number to child nodes recursively
func (n *IntegerNode) AddColumn(col int) {
n.Token.AddColumn(col)
}
// GetValue returns int64 value
func (n *IntegerNode) GetValue() interface{} {
return n.Value
}
// String int64 to text
func (n *IntegerNode) String() string {
if n.Comment != nil {
return addCommentString(n.Token.Value, n.Comment)
}
return n.stringWithoutComment()
}
func (n *IntegerNode) stringWithoutComment() string {
return n.Token.Value
}
// MarshalYAML encodes to a YAML text
func (n *IntegerNode) MarshalYAML() ([]byte, error) {
return []byte(n.String()), nil
}
// FloatNode type of float node
type FloatNode struct {
*BaseNode
Token *token.Token
Precision int
Value float64
}
// Read implements (io.Reader).Read
func (n *FloatNode) Read(p []byte) (int, error) {
return readNode(p, n)
}
// Type returns FloatType
func (n *FloatNode) Type() NodeType { return FloatType }
// GetToken returns token instance
func (n *FloatNode) GetToken() *token.Token {
return n.Token
}
// AddColumn add column number to child nodes recursively
func (n *FloatNode) AddColumn(col int) {
n.Token.AddColumn(col)
}
// GetValue returns float64 value
func (n *FloatNode) GetValue() interface{} {
return n.Value
}
// String float64 to text
func (n *FloatNode) String() string {
if n.Comment != nil {
return addCommentString(n.Token.Value, n.Comment)
}
return n.stringWithoutComment()
}
func (n *FloatNode) stringWithoutComment() string {
return n.Token.Value
}
// MarshalYAML encodes to a YAML text
func (n *FloatNode) MarshalYAML() ([]byte, error) {
return []byte(n.String()), nil
}
// StringNode type of string node
type StringNode struct {
*BaseNode
Token *token.Token
Value string
}
// Read implements (io.Reader).Read
func (n *StringNode) Read(p []byte) (int, error) {
return readNode(p, n)
}
// Type returns StringType
func (n *StringNode) Type() NodeType { return StringType }
// GetToken returns token instance
func (n *StringNode) GetToken() *token.Token {
return n.Token
}
// AddColumn add column number to child nodes recursively
func (n *StringNode) AddColumn(col int) {
n.Token.AddColumn(col)
}
// GetValue returns string value
func (n *StringNode) GetValue() interface{} {
return n.Value
}
// escapeSingleQuote escapes s to a single quoted scalar.
// https://yaml.org/spec/1.2.2/#732-single-quoted-style
func escapeSingleQuote(s string) string {
var sb strings.Builder
growLen := len(s) + // s includes also one ' from the doubled pair
2 + // opening and closing '
strings.Count(s, "'") // ' added by ReplaceAll
sb.Grow(growLen)
sb.WriteString("'")
sb.WriteString(strings.ReplaceAll(s, "'", "''"))
sb.WriteString("'")
return sb.String()
}
// String string value to text with quote or literal header if required
func (n *StringNode) String() string {
switch n.Token.Type {
case token.SingleQuoteType:
quoted := escapeSingleQuote(n.Value)
if n.Comment != nil {
return addCommentString(quoted, n.Comment)
}
return quoted
case token.DoubleQuoteType:
quoted := strconv.Quote(n.Value)
if n.Comment != nil {
return addCommentString(quoted, n.Comment)
}
return quoted
}
lbc := token.DetectLineBreakCharacter(n.Value)
if strings.Contains(n.Value, lbc) {
// This block assumes that the line breaks in this inside scalar content and the Outside scalar content are the same.
// It works mostly, but inconsistencies occur if line break characters are mixed.
header := token.LiteralBlockHeader(n.Value)
space := strings.Repeat(" ", n.Token.Position.Column-1)
values := []string{}
for _, v := range strings.Split(n.Value, lbc) {
values = append(values, fmt.Sprintf("%s %s", space, v))
}
block := strings.TrimSuffix(strings.TrimSuffix(strings.Join(values, lbc), fmt.Sprintf("%s %s", lbc, space)), fmt.Sprintf(" %s", space))
return fmt.Sprintf("%s%s%s", header, lbc, block)
} else if len(n.Value) > 0 && (n.Value[0] == '{' || n.Value[0] == '[') {
return fmt.Sprintf(`'%s'`, n.Value)
}
if n.Comment != nil {
return addCommentString(n.Value, n.Comment)
}
return n.Value
}
func (n *StringNode) stringWithoutComment() string {
switch n.Token.Type {
case token.SingleQuoteType:
quoted := fmt.Sprintf(`'%s'`, n.Value)
return quoted
case token.DoubleQuoteType:
quoted := strconv.Quote(n.Value)
return quoted
}
lbc := token.DetectLineBreakCharacter(n.Value)
if strings.Contains(n.Value, lbc) {
// This block assumes that the line breaks in this inside scalar content and the Outside scalar content are the same.
// It works mostly, but inconsistencies occur if line break characters are mixed.
header := token.LiteralBlockHeader(n.Value)
space := strings.Repeat(" ", n.Token.Position.Column-1)
values := []string{}
for _, v := range strings.Split(n.Value, lbc) {
values = append(values, fmt.Sprintf("%s %s", space, v))
}
block := strings.TrimSuffix(strings.TrimSuffix(strings.Join(values, lbc), fmt.Sprintf("%s %s", lbc, space)), fmt.Sprintf(" %s", space))
return fmt.Sprintf("%s%s%s", header, lbc, block)
} else if len(n.Value) > 0 && (n.Value[0] == '{' || n.Value[0] == '[') {
return fmt.Sprintf(`'%s'`, n.Value)
}
return n.Value
}
// MarshalYAML encodes to a YAML text
func (n *StringNode) MarshalYAML() ([]byte, error) {
return []byte(n.String()), nil
}
// LiteralNode type of literal node
type LiteralNode struct {
*BaseNode
Start *token.Token
Value *StringNode
}
// Read implements (io.Reader).Read
func (n *LiteralNode) Read(p []byte) (int, error) {
return readNode(p, n)
}
// Type returns LiteralType
func (n *LiteralNode) Type() NodeType { return LiteralType }
// GetToken returns token instance
func (n *LiteralNode) GetToken() *token.Token {
return n.Start
}
// AddColumn add column number to child nodes recursively
func (n *LiteralNode) AddColumn(col int) {
n.Start.AddColumn(col)
if n.Value != nil {
n.Value.AddColumn(col)
}
}
// GetValue returns string value
func (n *LiteralNode) GetValue() interface{} {
return n.String()
}
// String literal to text
func (n *LiteralNode) String() string {
origin := n.Value.GetToken().Origin
lit := strings.TrimRight(strings.TrimRight(origin, " "), "\n")
if n.Comment != nil {
return fmt.Sprintf("%s %s\n%s", n.Start.Value, n.Comment.String(), lit)
}
return fmt.Sprintf("%s\n%s", n.Start.Value, lit)
}
func (n *LiteralNode) stringWithoutComment() string {
return n.String()
}
// MarshalYAML encodes to a YAML text
func (n *LiteralNode) MarshalYAML() ([]byte, error) {
return []byte(n.String()), nil
}
// MergeKeyNode type of merge key node
type MergeKeyNode struct {
*BaseNode
Token *token.Token
}
// Read implements (io.Reader).Read
func (n *MergeKeyNode) Read(p []byte) (int, error) {
return readNode(p, n)
}
// Type returns MergeKeyType
func (n *MergeKeyNode) Type() NodeType { return MergeKeyType }
// GetToken returns token instance
func (n *MergeKeyNode) GetToken() *token.Token {
return n.Token
}
// GetValue returns '<<' value
func (n *MergeKeyNode) GetValue() interface{} {
return n.Token.Value
}
// String returns '<<' value
func (n *MergeKeyNode) String() string {
return n.Token.Value
}
func (n *MergeKeyNode) stringWithoutComment() string {
return n.Token.Value
}
// AddColumn add column number to child nodes recursively
func (n *MergeKeyNode) AddColumn(col int) {
n.Token.AddColumn(col)
}
// MarshalYAML encodes to a YAML text
func (n *MergeKeyNode) MarshalYAML() ([]byte, error) {
return []byte(n.String()), nil
}
// BoolNode type of boolean node
type BoolNode struct {
*BaseNode
Token *token.Token
Value bool
}
// Read implements (io.Reader).Read
func (n *BoolNode) Read(p []byte) (int, error) {
return readNode(p, n)
}
// Type returns BoolType
func (n *BoolNode) Type() NodeType { return BoolType }
// GetToken returns token instance
func (n *BoolNode) GetToken() *token.Token {
return n.Token
}
// AddColumn add column number to child nodes recursively
func (n *BoolNode) AddColumn(col int) {
n.Token.AddColumn(col)
}
// GetValue returns boolean value
func (n *BoolNode) GetValue() interface{} {
return n.Value
}
// String boolean to text
func (n *BoolNode) String() string {
if n.Comment != nil {
return addCommentString(n.Token.Value, n.Comment)
}
return n.stringWithoutComment()
}
func (n *BoolNode) stringWithoutComment() string {
return n.Token.Value
}
// MarshalYAML encodes to a YAML text
func (n *BoolNode) MarshalYAML() ([]byte, error) {
return []byte(n.String()), nil
}
// InfinityNode type of infinity node
type InfinityNode struct {
*BaseNode
Token *token.Token
Value float64
}
// Read implements (io.Reader).Read
func (n *InfinityNode) Read(p []byte) (int, error) {
return readNode(p, n)
}
// Type returns InfinityType
func (n *InfinityNode) Type() NodeType { return InfinityType }
// GetToken returns token instance
func (n *InfinityNode) GetToken() *token.Token {
return n.Token
}
// AddColumn add column number to child nodes recursively
func (n *InfinityNode) AddColumn(col int) {
n.Token.AddColumn(col)
}
// GetValue returns math.Inf(0) or math.Inf(-1)
func (n *InfinityNode) GetValue() interface{} {
return n.Value
}
// String infinity to text
func (n *InfinityNode) String() string {
if n.Comment != nil {
return addCommentString(n.Token.Value, n.Comment)
}
return n.stringWithoutComment()
}
func (n *InfinityNode) stringWithoutComment() string {
return n.Token.Value
}
// MarshalYAML encodes to a YAML text
func (n *InfinityNode) MarshalYAML() ([]byte, error) {
return []byte(n.String()), nil
}
// NanNode type of nan node
type NanNode struct {
*BaseNode
Token *token.Token
}
// Read implements (io.Reader).Read
func (n *NanNode) Read(p []byte) (int, error) {
return readNode(p, n)
}
// Type returns NanType
func (n *NanNode) Type() NodeType { return NanType }
// GetToken returns token instance
func (n *NanNode) GetToken() *token.Token {
return n.Token
}
// AddColumn add column number to child nodes recursively
func (n *NanNode) AddColumn(col int) {
n.Token.AddColumn(col)
}
// GetValue returns math.NaN()
func (n *NanNode) GetValue() interface{} {
return math.NaN()
}
// String returns .nan
func (n *NanNode) String() string {
if n.Comment != nil {
return addCommentString(n.Token.Value, n.Comment)
}
return n.stringWithoutComment()
}
func (n *NanNode) stringWithoutComment() string {
return n.Token.Value
}
// MarshalYAML encodes to a YAML text
func (n *NanNode) MarshalYAML() ([]byte, error) {
return []byte(n.String()), nil
}
// MapNode interface of MappingValueNode / MappingNode
type MapNode interface {
MapRange() *MapNodeIter
}
// MapNodeIter is an iterator for ranging over a MapNode
type MapNodeIter struct {
values []*MappingValueNode
idx int
}
const (
startRangeIndex = -1
)
// Next advances the map iterator and reports whether there is another entry.
// It returns false when the iterator is exhausted.
func (m *MapNodeIter) Next() bool {
m.idx++
next := m.idx < len(m.values)
return next
}
// Key returns the key of the iterator's current map node entry.
func (m *MapNodeIter) Key() Node {
return m.values[m.idx].Key
}
// Value returns the value of the iterator's current map node entry.
func (m *MapNodeIter) Value() Node {
return m.values[m.idx].Value
}
// MappingNode type of mapping node
type MappingNode struct {
*BaseNode
Start *token.Token
End *token.Token
IsFlowStyle bool
Values []*MappingValueNode
}
func (n *MappingNode) startPos() *token.Position {
if len(n.Values) == 0 {
return n.Start.Position
}
return n.Values[0].Key.GetToken().Position
}
// Merge merge key/value of map.
func (n *MappingNode) Merge(target *MappingNode) {
keyToMapValueMap := map[string]*MappingValueNode{}
for _, value := range n.Values {
key := value.Key.String()
keyToMapValueMap[key] = value
}
column := n.startPos().Column - target.startPos().Column
target.AddColumn(column)
for _, value := range target.Values {
mapValue, exists := keyToMapValueMap[value.Key.String()]
if exists {
mapValue.Value = value.Value
} else {
n.Values = append(n.Values, value)
}
}
}
// SetIsFlowStyle set value to IsFlowStyle field recursively.
func (n *MappingNode) SetIsFlowStyle(isFlow bool) {
n.IsFlowStyle = isFlow
for _, value := range n.Values {
value.SetIsFlowStyle(isFlow)
}
}
// Read implements (io.Reader).Read
func (n *MappingNode) Read(p []byte) (int, error) {
return readNode(p, n)
}
// Type returns MappingType
func (n *MappingNode) Type() NodeType { return MappingType }
// GetToken returns token instance
func (n *MappingNode) GetToken() *token.Token {
return n.Start
}
// AddColumn add column number to child nodes recursively
func (n *MappingNode) AddColumn(col int) {
n.Start.AddColumn(col)
n.End.AddColumn(col)
for _, value := range n.Values {
value.AddColumn(col)
}
}
func (n *MappingNode) flowStyleString(commentMode bool) string {
values := []string{}
for _, value := range n.Values {
values = append(values, strings.TrimLeft(value.String(), " "))
}
mapText := fmt.Sprintf("{%s}", strings.Join(values, ", "))
if commentMode && n.Comment != nil {
return addCommentString(mapText, n.Comment)
}
return mapText
}
func (n *MappingNode) blockStyleString(commentMode bool) string {
values := []string{}
for _, value := range n.Values {
values = append(values, value.String())
}
mapText := strings.Join(values, "\n")
if commentMode && n.Comment != nil {
value := values[0]
var spaceNum int
for i := 0; i < len(value); i++ {
if value[i] != ' ' {
break
}
spaceNum++
}
comment := n.Comment.StringWithSpace(spaceNum)
return fmt.Sprintf("%s\n%s", comment, mapText)
}
return mapText
}
// String mapping values to text
func (n *MappingNode) String() string {
if len(n.Values) == 0 {
if n.Comment != nil {
return addCommentString("{}", n.Comment)
}
return "{}"
}
commentMode := true
if n.IsFlowStyle || len(n.Values) == 0 {
return n.flowStyleString(commentMode)
}
return n.blockStyleString(commentMode)
}
func (n *MappingNode) stringWithoutComment() string {
commentMode := false
if n.IsFlowStyle || len(n.Values) == 0 {
return n.flowStyleString(commentMode)
}
return n.blockStyleString(commentMode)
}
// MapRange implements MapNode protocol
func (n *MappingNode) MapRange() *MapNodeIter {
return &MapNodeIter{
idx: startRangeIndex,
values: n.Values,
}
}
// MarshalYAML encodes to a YAML text
func (n *MappingNode) MarshalYAML() ([]byte, error) {
return []byte(n.String()), nil
}
// MappingKeyNode type of tag node
type MappingKeyNode struct {
*BaseNode
Start *token.Token
Value Node
}
// Read implements (io.Reader).Read
func (n *MappingKeyNode) Read(p []byte) (int, error) {
return readNode(p, n)
}
// Type returns MappingKeyType
func (n *MappingKeyNode) Type() NodeType { return MappingKeyType }
// GetToken returns token instance
func (n *MappingKeyNode) GetToken() *token.Token {
return n.Start
}
// AddColumn add column number to child nodes recursively
func (n *MappingKeyNode) AddColumn(col int) {
n.Start.AddColumn(col)
if n.Value != nil {
n.Value.AddColumn(col)
}
}
// String tag to text
func (n *MappingKeyNode) String() string {
return fmt.Sprintf("%s %s", n.Start.Value, n.Value.String())
}
func (n *MappingKeyNode) stringWithoutComment() string {
return fmt.Sprintf("%s %s", n.Start.Value, n.Value.String())
}
// MarshalYAML encodes to a YAML text
func (n *MappingKeyNode) MarshalYAML() ([]byte, error) {
return []byte(n.String()), nil
}
// MappingValueNode type of mapping value
type MappingValueNode struct {
*BaseNode
Start *token.Token
Key Node
Value Node
}
// Replace replace value node.
func (n *MappingValueNode) Replace(value Node) error {
column := n.Value.GetToken().Position.Column - value.GetToken().Position.Column
value.AddColumn(column)
n.Value = value
return nil
}
// Read implements (io.Reader).Read
func (n *MappingValueNode) Read(p []byte) (int, error) {
return readNode(p, n)
}
// Type returns MappingValueType
func (n *MappingValueNode) Type() NodeType { return MappingValueType }
// GetToken returns token instance
func (n *MappingValueNode) GetToken() *token.Token {
return n.Start
}
// AddColumn add column number to child nodes recursively
func (n *MappingValueNode) AddColumn(col int) {
n.Start.AddColumn(col)
if n.Key != nil {
n.Key.AddColumn(col)
}
if n.Value != nil {
n.Value.AddColumn(col)
}
}
// SetIsFlowStyle set value to IsFlowStyle field recursively.
func (n *MappingValueNode) SetIsFlowStyle(isFlow bool) {
switch value := n.Value.(type) {
case *MappingNode:
value.SetIsFlowStyle(isFlow)
case *MappingValueNode:
value.SetIsFlowStyle(isFlow)
case *SequenceNode:
value.SetIsFlowStyle(isFlow)
}
}
// String mapping value to text
func (n *MappingValueNode) String() string {
if n.Comment != nil {
return fmt.Sprintf(
"%s\n%s",
n.Comment.StringWithSpace(n.Key.GetToken().Position.Column-1),
n.toString(),
)
}
return n.toString()
}
func (n *MappingValueNode) toString() string {
space := strings.Repeat(" ", n.Key.GetToken().Position.Column-1)
keyIndentLevel := n.Key.GetToken().Position.IndentLevel
valueIndentLevel := n.Value.GetToken().Position.IndentLevel
keyComment := n.Key.GetComment()
if _, ok := n.Value.(ScalarNode); ok {
return fmt.Sprintf("%s%s: %s", space, n.Key.String(), n.Value.String())
} else if keyIndentLevel < valueIndentLevel {
if keyComment != nil {
return fmt.Sprintf(
"%s%s: %s\n%s",
space,
n.Key.stringWithoutComment(),
keyComment.String(),
n.Value.String(),
)
}
return fmt.Sprintf("%s%s:\n%s", space, n.Key.String(), n.Value.String())
} else if m, ok := n.Value.(*MappingNode); ok && (m.IsFlowStyle || len(m.Values) == 0) {
return fmt.Sprintf("%s%s: %s", space, n.Key.String(), n.Value.String())
} else if s, ok := n.Value.(*SequenceNode); ok && (s.IsFlowStyle || len(s.Values) == 0) {
return fmt.Sprintf("%s%s: %s", space, n.Key.String(), n.Value.String())
} else if _, ok := n.Value.(*AnchorNode); ok {
return fmt.Sprintf("%s%s: %s", space, n.Key.String(), n.Value.String())
} else if _, ok := n.Value.(*AliasNode); ok {
return fmt.Sprintf("%s%s: %s", space, n.Key.String(), n.Value.String())
}
if keyComment != nil {
return fmt.Sprintf(
"%s%s: %s\n%s",
space,
n.Key.stringWithoutComment(),
keyComment.String(),
n.Value.String(),
)
}
if m, ok := n.Value.(*MappingNode); ok && m.Comment != nil {
return fmt.Sprintf(
"%s%s: %s",
space,
n.Key.String(),
strings.TrimLeft(n.Value.String(), " "),
)
}
return fmt.Sprintf("%s%s:\n%s", space, n.Key.String(), n.Value.String())
}
func (n *MappingValueNode) stringWithoutComment() string {
space := strings.Repeat(" ", n.Key.GetToken().Position.Column-1)
keyIndentLevel := n.Key.GetToken().Position.IndentLevel
valueIndentLevel := n.Value.GetToken().Position.IndentLevel
if _, ok := n.Value.(ScalarNode); ok {
return fmt.Sprintf("%s%s: %s", space, n.Key.String(), n.Value.String())
} else if keyIndentLevel < valueIndentLevel {
return fmt.Sprintf("%s%s:\n%s", space, n.Key.String(), n.Value.String())
} else if m, ok := n.Value.(*MappingNode); ok && (m.IsFlowStyle || len(m.Values) == 0) {
return fmt.Sprintf("%s%s: %s", space, n.Key.String(), n.Value.String())
} else if s, ok := n.Value.(*SequenceNode); ok && (s.IsFlowStyle || len(s.Values) == 0) {
return fmt.Sprintf("%s%s: %s", space, n.Key.String(), n.Value.String())
} else if _, ok := n.Value.(*AnchorNode); ok {
return fmt.Sprintf("%s%s: %s", space, n.Key.String(), n.Value.String())
} else if _, ok := n.Value.(*AliasNode); ok {
return fmt.Sprintf("%s%s: %s", space, n.Key.String(), n.Value.String())
}
return fmt.Sprintf("%s%s:\n%s", space, n.Key.String(), n.Value.String())
}
// MapRange implements MapNode protocol
func (n *MappingValueNode) MapRange() *MapNodeIter {
return &MapNodeIter{
idx: startRangeIndex,
values: []*MappingValueNode{n},
}
}
// MarshalYAML encodes to a YAML text
func (n *MappingValueNode) MarshalYAML() ([]byte, error) {
return []byte(n.String()), nil
}
// ArrayNode interface of SequenceNode
type ArrayNode interface {
ArrayRange() *ArrayNodeIter
}
// ArrayNodeIter is an iterator for ranging over a ArrayNode
type ArrayNodeIter struct {
values []Node
idx int
}
// Next advances the array iterator and reports whether there is another entry.
// It returns false when the iterator is exhausted.
func (m *ArrayNodeIter) Next() bool {
m.idx++
next := m.idx < len(m.values)
return next
}
// Value returns the value of the iterator's current array entry.
func (m *ArrayNodeIter) Value() Node {
return m.values[m.idx]
}
// Len returns length of array
func (m *ArrayNodeIter) Len() int {
return len(m.values)
}
// SequenceNode type of sequence node
type SequenceNode struct {
*BaseNode
Start *token.Token
End *token.Token
IsFlowStyle bool
Values []Node
ValueComments []*CommentGroupNode
}
// Replace replace value node.
func (n *SequenceNode) Replace(idx int, value Node) error {
if len(n.Values) <= idx {
return xerrors.Errorf(
"invalid index for sequence: sequence length is %d, but specified %d index",
len(n.Values), idx,
)
}
column := n.Values[idx].GetToken().Position.Column - value.GetToken().Position.Column
value.AddColumn(column)
n.Values[idx] = value
return nil
}
// Merge merge sequence value.
func (n *SequenceNode) Merge(target *SequenceNode) {
column := n.Start.Position.Column - target.Start.Position.Column
target.AddColumn(column)
for _, value := range target.Values {
n.Values = append(n.Values, value)
}
}
// SetIsFlowStyle set value to IsFlowStyle field recursively.
func (n *SequenceNode) SetIsFlowStyle(isFlow bool) {
n.IsFlowStyle = isFlow
for _, value := range n.Values {
switch value := value.(type) {
case *MappingNode:
value.SetIsFlowStyle(isFlow)
case *MappingValueNode:
value.SetIsFlowStyle(isFlow)
case *SequenceNode:
value.SetIsFlowStyle(isFlow)
}
}
}
// Read implements (io.Reader).Read
func (n *SequenceNode) Read(p []byte) (int, error) {
return readNode(p, n)
}
// Type returns SequenceType
func (n *SequenceNode) Type() NodeType { return SequenceType }
// GetToken returns token instance
func (n *SequenceNode) GetToken() *token.Token {
return n.Start
}
// AddColumn add column number to child nodes recursively
func (n *SequenceNode) AddColumn(col int) {
n.Start.AddColumn(col)
n.End.AddColumn(col)
for _, value := range n.Values {
value.AddColumn(col)
}
}
func (n *SequenceNode) flowStyleString() string {
values := []string{}
for _, value := range n.Values {
values = append(values, value.String())
}
return fmt.Sprintf("[%s]", strings.Join(values, ", "))
}
func (n *SequenceNode) blockStyleString() string {
space := strings.Repeat(" ", n.Start.Position.Column-1)
values := []string{}
if n.Comment != nil {
values = append(values, n.Comment.StringWithSpace(n.Start.Position.Column-1))
}
for idx, value := range n.Values {
valueStr := value.String()
splittedValues := strings.Split(valueStr, "\n")
trimmedFirstValue := strings.TrimLeft(splittedValues[0], " ")
diffLength := len(splittedValues[0]) - len(trimmedFirstValue)
if len(splittedValues) > 1 && value.Type() == StringType || value.Type() == LiteralType {
// If multi-line string, the space characters for indent have already been added, so delete them.
for i := 1; i < len(splittedValues); i++ {
splittedValues[i] = strings.TrimLeft(splittedValues[i], " ")
}
}
newValues := []string{trimmedFirstValue}
for i := 1; i < len(splittedValues); i++ {
if len(splittedValues[i]) <= diffLength {
// this line is \n or white space only
newValues = append(newValues, "")
continue
}
trimmed := splittedValues[i][diffLength:]
newValues = append(newValues, fmt.Sprintf("%s %s", space, trimmed))
}
newValue := strings.Join(newValues, "\n")
if len(n.ValueComments) == len(n.Values) && n.ValueComments[idx] != nil {
values = append(values, n.ValueComments[idx].StringWithSpace(n.Start.Position.Column-1))
}
values = append(values, fmt.Sprintf("%s- %s", space, newValue))
}
return strings.Join(values, "\n")
}
// String sequence to text
func (n *SequenceNode) String() string {
if n.IsFlowStyle || len(n.Values) == 0 {
return n.flowStyleString()
}
return n.blockStyleString()
}
func (n *SequenceNode) stringWithoutComment() string {
if n.IsFlowStyle || len(n.Values) == 0 {
return n.flowStyleString()
}
return n.blockStyleString()
}
// ArrayRange implements ArrayNode protocol
func (n *SequenceNode) ArrayRange() *ArrayNodeIter {
return &ArrayNodeIter{
idx: startRangeIndex,
values: n.Values,
}
}
// MarshalYAML encodes to a YAML text
func (n *SequenceNode) MarshalYAML() ([]byte, error) {
return []byte(n.String()), nil
}
// AnchorNode type of anchor node
type AnchorNode struct {
*BaseNode
Start *token.Token
Name Node
Value Node
}
func (n *AnchorNode) SetName(name string) error {
if n.Name == nil {
return ErrInvalidAnchorName
}
s, ok := n.Name.(*StringNode)
if !ok {
return ErrInvalidAnchorName
}
s.Value = name
return nil
}
// Read implements (io.Reader).Read
func (n *AnchorNode) Read(p []byte) (int, error) {
return readNode(p, n)
}
// Type returns AnchorType
func (n *AnchorNode) Type() NodeType { return AnchorType }
// GetToken returns token instance
func (n *AnchorNode) GetToken() *token.Token {
return n.Start
}
// AddColumn add column number to child nodes recursively
func (n *AnchorNode) AddColumn(col int) {
n.Start.AddColumn(col)
if n.Name != nil {
n.Name.AddColumn(col)
}
if n.Value != nil {
n.Value.AddColumn(col)
}
}
// String anchor to text
func (n *AnchorNode) String() string {
value := n.Value.String()
if len(strings.Split(value, "\n")) > 1 {
return fmt.Sprintf("&%s\n%s", n.Name.String(), value)
} else if s, ok := n.Value.(*SequenceNode); ok && !s.IsFlowStyle {
return fmt.Sprintf("&%s\n%s", n.Name.String(), value)
} else if m, ok := n.Value.(*MappingNode); ok && !m.IsFlowStyle {
return fmt.Sprintf("&%s\n%s", n.Name.String(), value)
}
return fmt.Sprintf("&%s %s", n.Name.String(), value)
}
func (n *AnchorNode) stringWithoutComment() string {
return n.String()
}
// MarshalYAML encodes to a YAML text
func (n *AnchorNode) MarshalYAML() ([]byte, error) {
return []byte(n.String()), nil
}
// AliasNode type of alias node
type AliasNode struct {
*BaseNode
Start *token.Token
Value Node
}
func (n *AliasNode) SetName(name string) error {
if n.Value == nil {
return ErrInvalidAliasName
}
s, ok := n.Value.(*StringNode)
if !ok {
return ErrInvalidAliasName
}
s.Value = name
return nil
}
// Read implements (io.Reader).Read
func (n *AliasNode) Read(p []byte) (int, error) {
return readNode(p, n)
}
// Type returns AliasType
func (n *AliasNode) Type() NodeType { return AliasType }
// GetToken returns token instance
func (n *AliasNode) GetToken() *token.Token {
return n.Start
}
// AddColumn add column number to child nodes recursively
func (n *AliasNode) AddColumn(col int) {
n.Start.AddColumn(col)
if n.Value != nil {
n.Value.AddColumn(col)
}
}
// String alias to text
func (n *AliasNode) String() string {
return fmt.Sprintf("*%s", n.Value.String())
}
func (n *AliasNode) stringWithoutComment() string {
return fmt.Sprintf("*%s", n.Value.String())
}
// MarshalYAML encodes to a YAML text
func (n *AliasNode) MarshalYAML() ([]byte, error) {
return []byte(n.String()), nil
}
// DirectiveNode type of directive node
type DirectiveNode struct {
*BaseNode
Start *token.Token
Value Node
}
// Read implements (io.Reader).Read
func (n *DirectiveNode) Read(p []byte) (int, error) {
return readNode(p, n)
}
// Type returns DirectiveType
func (n *DirectiveNode) Type() NodeType { return DirectiveType }
// GetToken returns token instance
func (n *DirectiveNode) GetToken() *token.Token {
return n.Start
}
// AddColumn add column number to child nodes recursively
func (n *DirectiveNode) AddColumn(col int) {
if n.Value != nil {
n.Value.AddColumn(col)
}
}
// String directive to text
func (n *DirectiveNode) String() string {
return fmt.Sprintf("%s%s", n.Start.Value, n.Value.String())
}
func (n *DirectiveNode) stringWithoutComment() string {
return fmt.Sprintf("%s%s", n.Start.Value, n.Value.String())
}
// MarshalYAML encodes to a YAML text
func (n *DirectiveNode) MarshalYAML() ([]byte, error) {
return []byte(n.String()), nil
}
// TagNode type of tag node
type TagNode struct {
*BaseNode
Start *token.Token
Value Node
}
// Read implements (io.Reader).Read
func (n *TagNode) Read(p []byte) (int, error) {
return readNode(p, n)
}
// Type returns TagType
func (n *TagNode) Type() NodeType { return TagType }
// GetToken returns token instance
func (n *TagNode) GetToken() *token.Token {
return n.Start
}
// AddColumn add column number to child nodes recursively
func (n *TagNode) AddColumn(col int) {
n.Start.AddColumn(col)
if n.Value != nil {
n.Value.AddColumn(col)
}
}
// String tag to text
func (n *TagNode) String() string {
return fmt.Sprintf("%s %s", n.Start.Value, n.Value.String())
}
func (n *TagNode) stringWithoutComment() string {
return fmt.Sprintf("%s %s", n.Start.Value, n.Value.String())
}
// MarshalYAML encodes to a YAML text
func (n *TagNode) MarshalYAML() ([]byte, error) {
return []byte(n.String()), nil
}
// CommentNode type of comment node
type CommentNode struct {
*BaseNode
Token *token.Token
}
// Read implements (io.Reader).Read
func (n *CommentNode) Read(p []byte) (int, error) {
return readNode(p, n)
}
// Type returns TagType
func (n *CommentNode) Type() NodeType { return CommentType }
// GetToken returns token instance
func (n *CommentNode) GetToken() *token.Token { return n.Token }
// AddColumn add column number to child nodes recursively
func (n *CommentNode) AddColumn(col int) {
if n.Token == nil {
return
}
n.Token.AddColumn(col)
}
// String comment to text
func (n *CommentNode) String() string {
return fmt.Sprintf("#%s", n.Token.Value)
}
func (n *CommentNode) stringWithoutComment() string {
return ""
}
// MarshalYAML encodes to a YAML text
func (n *CommentNode) MarshalYAML() ([]byte, error) {
return []byte(n.String()), nil
}
// CommentGroupNode type of comment node
type CommentGroupNode struct {
*BaseNode
Comments []*CommentNode
}
// Read implements (io.Reader).Read
func (n *CommentGroupNode) Read(p []byte) (int, error) {
return readNode(p, n)
}
// Type returns TagType
func (n *CommentGroupNode) Type() NodeType { return CommentType }
// GetToken returns token instance
func (n *CommentGroupNode) GetToken() *token.Token {
if len(n.Comments) > 0 {
return n.Comments[0].Token
}
return nil
}
// AddColumn add column number to child nodes recursively
func (n *CommentGroupNode) AddColumn(col int) {
for _, comment := range n.Comments {
comment.AddColumn(col)
}
}
// String comment to text
func (n *CommentGroupNode) String() string {
values := []string{}
for _, comment := range n.Comments {
values = append(values, comment.String())
}
return strings.Join(values, "\n")
}
func (n *CommentGroupNode) StringWithSpace(col int) string {
space := strings.Repeat(" ", col)
values := []string{}
for _, comment := range n.Comments {
values = append(values, space+comment.String())
}
return strings.Join(values, "\n")
}
func (n *CommentGroupNode) stringWithoutComment() string {
return ""
}
// MarshalYAML encodes to a YAML text
func (n *CommentGroupNode) MarshalYAML() ([]byte, error) {
return []byte(n.String()), nil
}
// Visitor has Visit method that is invokded for each node encountered by Walk.
// If the result visitor w is not nil, Walk visits each of the children of node with the visitor w,
// followed by a call of w.Visit(nil).
type Visitor interface {
Visit(Node) Visitor
}
// Walk traverses an AST in depth-first order: It starts by calling v.Visit(node); node must not be nil.
// If the visitor w returned by v.Visit(node) is not nil,
// Walk is invoked recursively with visitor w for each of the non-nil children of node,
// followed by a call of w.Visit(nil).
func Walk(v Visitor, node Node) {
if v = v.Visit(node); v == nil {
return
}
switch n := node.(type) {
case *CommentNode:
case *NullNode:
walkComment(v, n.BaseNode)
case *IntegerNode:
walkComment(v, n.BaseNode)
case *FloatNode:
walkComment(v, n.BaseNode)
case *StringNode:
walkComment(v, n.BaseNode)
case *MergeKeyNode:
walkComment(v, n.BaseNode)
case *BoolNode:
walkComment(v, n.BaseNode)
case *InfinityNode:
walkComment(v, n.BaseNode)
case *NanNode:
walkComment(v, n.BaseNode)
case *LiteralNode:
walkComment(v, n.BaseNode)
Walk(v, n.Value)
case *DirectiveNode:
walkComment(v, n.BaseNode)
Walk(v, n.Value)
case *TagNode:
walkComment(v, n.BaseNode)
Walk(v, n.Value)
case *DocumentNode:
walkComment(v, n.BaseNode)
Walk(v, n.Body)
case *MappingNode:
walkComment(v, n.BaseNode)
for _, value := range n.Values {
Walk(v, value)
}
case *MappingKeyNode:
walkComment(v, n.BaseNode)
Walk(v, n.Value)
case *MappingValueNode:
walkComment(v, n.BaseNode)
Walk(v, n.Key)
Walk(v, n.Value)
case *SequenceNode:
walkComment(v, n.BaseNode)
for _, value := range n.Values {
Walk(v, value)
}
case *AnchorNode:
walkComment(v, n.BaseNode)
Walk(v, n.Name)
Walk(v, n.Value)
case *AliasNode:
walkComment(v, n.BaseNode)
Walk(v, n.Value)
}
}
func walkComment(v Visitor, base *BaseNode) {
if base == nil {
return
}
if base.Comment == nil {
return
}
Walk(v, base.Comment)
}
type filterWalker struct {
typ NodeType
results []Node
}
func (v *filterWalker) Visit(n Node) Visitor {
if v.typ == n.Type() {
v.results = append(v.results, n)
}
return v
}
type parentFinder struct {
target Node
}
func (f *parentFinder) walk(parent, node Node) Node {
if f.target == node {
return parent
}
switch n := node.(type) {
case *CommentNode:
return nil
case *NullNode:
return nil
case *IntegerNode:
return nil
case *FloatNode:
return nil
case *StringNode:
return nil
case *MergeKeyNode:
return nil
case *BoolNode:
return nil
case *InfinityNode:
return nil
case *NanNode:
return nil
case *LiteralNode:
return f.walk(node, n.Value)
case *DirectiveNode:
return f.walk(node, n.Value)
case *TagNode:
return f.walk(node, n.Value)
case *DocumentNode:
return f.walk(node, n.Body)
case *MappingNode:
for _, value := range n.Values {
if found := f.walk(node, value); found != nil {
return found
}
}
case *MappingKeyNode:
return f.walk(node, n.Value)
case *MappingValueNode:
if found := f.walk(node, n.Key); found != nil {
return found
}
return f.walk(node, n.Value)
case *SequenceNode:
for _, value := range n.Values {
if found := f.walk(node, value); found != nil {
return found
}
}
case *AnchorNode:
if found := f.walk(node, n.Name); found != nil {
return found
}
return f.walk(node, n.Value)
case *AliasNode:
return f.walk(node, n.Value)
}
return nil
}
// Parent get parent node from child node.
func Parent(root, child Node) Node {
finder := &parentFinder{target: child}
return finder.walk(root, root)
}
// Filter returns a list of nodes that match the given type.
func Filter(typ NodeType, node Node) []Node {
walker := &filterWalker{typ: typ}
Walk(walker, node)
return walker.results
}
// FilterFile returns a list of nodes that match the given type.
func FilterFile(typ NodeType, file *File) []Node {
results := []Node{}
for _, doc := range file.Docs {
walker := &filterWalker{typ: typ}
Walk(walker, doc)
results = append(results, walker.results...)
}
return results
}
type ErrInvalidMergeType struct {
dst Node
src Node
}
func (e *ErrInvalidMergeType) Error() string {
return fmt.Sprintf("cannot merge %s into %s", e.src.Type(), e.dst.Type())
}
// Merge merge document, map, sequence node.
func Merge(dst Node, src Node) error {
if doc, ok := src.(*DocumentNode); ok {
src = doc.Body
}
err := &ErrInvalidMergeType{dst: dst, src: src}
switch dst.Type() {
case DocumentType:
node := dst.(*DocumentNode)
return Merge(node.Body, src)
case MappingType:
node := dst.(*MappingNode)
target, ok := src.(*MappingNode)
if !ok {
return err
}
node.Merge(target)
return nil
case SequenceType:
node := dst.(*SequenceNode)
target, ok := src.(*SequenceNode)
if !ok {
return err
}
node.Merge(target)
return nil
}
return err
}
golang-github-goccy-go-yaml-1.9.5/ast/ast_test.go 0000664 0000000 0000000 00000000347 14167531274 0021704 0 ustar 00root root 0000000 0000000 package ast
import "testing"
func TestEscapeSingleQuote(t *testing.T) {
expected := `'Victor''s victory'`
got := escapeSingleQuote("Victor's victory")
if got != expected {
t.Fatalf("expected:%s\ngot:%s", expected, got)
}
}
golang-github-goccy-go-yaml-1.9.5/benchmarks/ 0000775 0000000 0000000 00000000000 14167531274 0021051 5 ustar 00root root 0000000 0000000 golang-github-goccy-go-yaml-1.9.5/benchmarks/benchmark_test.go 0000664 0000000 0000000 00000001657 14167531274 0024402 0 ustar 00root root 0000000 0000000 package benchmarks
import (
"testing"
"github.com/goccy/go-yaml"
goyaml2 "gopkg.in/yaml.v2"
goyaml3 "gopkg.in/yaml.v3"
)
func Benchmark(b *testing.B) {
const src = `---
id: 1
message: Hello, World
verified: true
elements:
- one
- 0.02
- null
- -inf
`
type T struct {
ID int `yaml:"id"`
Message string `yaml:"message"`
Verified bool `yaml:"verified,omitempty"`
}
b.Run("gopkg.in/yaml.v2", func(b *testing.B) {
var t T
for i := 0; i < b.N; i++ {
if err := goyaml2.Unmarshal([]byte(src), &t); err != nil {
b.Fatal(err)
}
}
})
b.Run("gopkg.in/yaml.v3", func(b *testing.B) {
var t T
for i := 0; i < b.N; i++ {
if err := goyaml3.Unmarshal([]byte(src), &t); err != nil {
b.Fatal(err)
}
}
})
b.Run("github.com/goccy/go-yaml", func(b *testing.B) {
var t T
for i := 0; i < b.N; i++ {
if err := yaml.Unmarshal([]byte(src), &t); err != nil {
b.Fatal(err)
}
}
})
}
golang-github-goccy-go-yaml-1.9.5/benchmarks/go.mod 0000664 0000000 0000000 00000000334 14167531274 0022157 0 ustar 00root root 0000000 0000000 module benchmarks
go 1.12
replace github.com/goccy/go-yaml => ../
require (
github.com/goccy/go-yaml v0.0.0-00010101000000-000000000000
gopkg.in/yaml.v2 v2.3.0
gopkg.in/yaml.v3 v3.0.0-20200506231410-2ff61e1afc86
)
golang-github-goccy-go-yaml-1.9.5/benchmarks/go.sum 0000664 0000000 0000000 00000007044 14167531274 0022211 0 ustar 00root root 0000000 0000000 github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW10=
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191010194322-b09406accb47 h1:/XfQ9z7ib8eEJX2hdgFTZJ/ntt0swNk5oYBziWeTCvY=
golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898 h1:/atklqdjdhuosWIl6AIbOeHJjicWYPqR9bpxqxYG2pA=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM=
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
gopkg.in/go-playground/validator.v9 v9.30.0 h1:Wk0Z37oBmKj9/n+tPyBHZmeL19LaCoK3Qq48VwYENss=
gopkg.in/go-playground/validator.v9 v9.30.0/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200506231410-2ff61e1afc86 h1:OfFoIUYv/me30yv7XlMy4F9RJw8DEm8WQ6QG1Ph4bH0=
gopkg.in/yaml.v3 v3.0.0-20200506231410-2ff61e1afc86/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
golang-github-goccy-go-yaml-1.9.5/cmd/ 0000775 0000000 0000000 00000000000 14167531274 0017477 5 ustar 00root root 0000000 0000000 golang-github-goccy-go-yaml-1.9.5/cmd/ycat/ 0000775 0000000 0000000 00000000000 14167531274 0020437 5 ustar 00root root 0000000 0000000 golang-github-goccy-go-yaml-1.9.5/cmd/ycat/ycat.go 0000664 0000000 0000000 00000003466 14167531274 0021737 0 ustar 00root root 0000000 0000000 package main
import (
"errors"
"fmt"
"io/ioutil"
"os"
"github.com/fatih/color"
"github.com/goccy/go-yaml"
"github.com/goccy/go-yaml/lexer"
"github.com/goccy/go-yaml/printer"
"github.com/mattn/go-colorable"
)
const escape = "\x1b"
func format(attr color.Attribute) string {
return fmt.Sprintf("%s[%dm", escape, attr)
}
func _main(args []string) error {
if len(args) < 2 {
return errors.New("ycat: usage: ycat file.yml")
}
filename := args[1]
bytes, err := ioutil.ReadFile(filename)
if err != nil {
return err
}
tokens := lexer.Tokenize(string(bytes))
var p printer.Printer
p.LineNumber = true
p.LineNumberFormat = func(num int) string {
fn := color.New(color.Bold, color.FgHiWhite).SprintFunc()
return fn(fmt.Sprintf("%2d | ", num))
}
p.Bool = func() *printer.Property {
return &printer.Property{
Prefix: format(color.FgHiMagenta),
Suffix: format(color.Reset),
}
}
p.Number = func() *printer.Property {
return &printer.Property{
Prefix: format(color.FgHiMagenta),
Suffix: format(color.Reset),
}
}
p.MapKey = func() *printer.Property {
return &printer.Property{
Prefix: format(color.FgHiCyan),
Suffix: format(color.Reset),
}
}
p.Anchor = func() *printer.Property {
return &printer.Property{
Prefix: format(color.FgHiYellow),
Suffix: format(color.Reset),
}
}
p.Alias = func() *printer.Property {
return &printer.Property{
Prefix: format(color.FgHiYellow),
Suffix: format(color.Reset),
}
}
p.String = func() *printer.Property {
return &printer.Property{
Prefix: format(color.FgHiGreen),
Suffix: format(color.Reset),
}
}
writer := colorable.NewColorableStdout()
writer.Write([]byte(p.PrintTokens(tokens) + "\n"))
return nil
}
func main() {
if err := _main(os.Args); err != nil {
fmt.Printf("%v\n", yaml.FormatError(err, true, true))
}
}
golang-github-goccy-go-yaml-1.9.5/decode.go 0000664 0000000 0000000 00000123650 14167531274 0020515 0 ustar 00root root 0000000 0000000 package yaml
import (
"bytes"
"context"
"encoding"
"encoding/base64"
"fmt"
"io"
"io/ioutil"
"math"
"os"
"path/filepath"
"reflect"
"strconv"
"time"
"github.com/goccy/go-yaml/ast"
"github.com/goccy/go-yaml/internal/errors"
"github.com/goccy/go-yaml/parser"
"github.com/goccy/go-yaml/token"
"golang.org/x/xerrors"
)
// Decoder reads and decodes YAML values from an input stream.
type Decoder struct {
reader io.Reader
referenceReaders []io.Reader
anchorNodeMap map[string]ast.Node
anchorValueMap map[string]reflect.Value
toCommentMap CommentMap
opts []DecodeOption
referenceFiles []string
referenceDirs []string
isRecursiveDir bool
isResolvedReference bool
validator StructValidator
disallowUnknownField bool
disallowDuplicateKey bool
useOrderedMap bool
useJSONUnmarshaler bool
parsedFile *ast.File
streamIndex int
}
// NewDecoder returns a new decoder that reads from r.
func NewDecoder(r io.Reader, opts ...DecodeOption) *Decoder {
return &Decoder{
reader: r,
anchorNodeMap: map[string]ast.Node{},
anchorValueMap: map[string]reflect.Value{},
opts: opts,
referenceReaders: []io.Reader{},
referenceFiles: []string{},
referenceDirs: []string{},
isRecursiveDir: false,
isResolvedReference: false,
disallowUnknownField: false,
disallowDuplicateKey: false,
useOrderedMap: false,
}
}
func (d *Decoder) castToFloat(v interface{}) interface{} {
switch vv := v.(type) {
case int:
return float64(vv)
case int8:
return float64(vv)
case int16:
return float64(vv)
case int32:
return float64(vv)
case int64:
return float64(vv)
case uint:
return float64(vv)
case uint8:
return float64(vv)
case uint16:
return float64(vv)
case uint32:
return float64(vv)
case uint64:
return float64(vv)
case float32:
return float64(vv)
case float64:
return vv
case string:
// if error occurred, return zero value
f, _ := strconv.ParseFloat(vv, 64)
return f
}
return 0
}
func (d *Decoder) mergeValueNode(value ast.Node) ast.Node {
if value.Type() == ast.AliasType {
aliasNode := value.(*ast.AliasNode)
aliasName := aliasNode.Value.GetToken().Value
return d.anchorNodeMap[aliasName]
}
return value
}
func (d *Decoder) mapKeyNodeToString(node ast.Node) string {
key := d.nodeToValue(node)
if key == nil {
return "null"
}
if k, ok := key.(string); ok {
return k
}
return fmt.Sprint(key)
}
func (d *Decoder) setToMapValue(node ast.Node, m map[string]interface{}) {
d.setPathToCommentMap(node)
switch n := node.(type) {
case *ast.MappingValueNode:
if n.Key.Type() == ast.MergeKeyType {
d.setToMapValue(d.mergeValueNode(n.Value), m)
} else {
key := d.mapKeyNodeToString(n.Key)
m[key] = d.nodeToValue(n.Value)
}
case *ast.MappingNode:
for _, value := range n.Values {
d.setToMapValue(value, m)
}
case *ast.AnchorNode:
anchorName := n.Name.GetToken().Value
d.anchorNodeMap[anchorName] = n.Value
}
}
func (d *Decoder) setToOrderedMapValue(node ast.Node, m *MapSlice) {
switch n := node.(type) {
case *ast.MappingValueNode:
if n.Key.Type() == ast.MergeKeyType {
d.setToOrderedMapValue(d.mergeValueNode(n.Value), m)
} else {
key := d.mapKeyNodeToString(n.Key)
*m = append(*m, MapItem{Key: key, Value: d.nodeToValue(n.Value)})
}
case *ast.MappingNode:
for _, value := range n.Values {
d.setToOrderedMapValue(value, m)
}
}
}
func (d *Decoder) setPathToCommentMap(node ast.Node) {
if d.toCommentMap == nil {
return
}
commentGroup := node.GetComment()
if commentGroup == nil {
return
}
texts := []string{}
for _, comment := range commentGroup.Comments {
texts = append(texts, comment.Token.Value)
}
if len(texts) == 0 {
return
}
if len(texts) == 1 {
d.toCommentMap[node.GetPath()] = LineComment(texts[0])
} else {
d.toCommentMap[node.GetPath()] = HeadComment(texts...)
}
}
func (d *Decoder) nodeToValue(node ast.Node) interface{} {
d.setPathToCommentMap(node)
switch n := node.(type) {
case *ast.NullNode:
return nil
case *ast.StringNode:
return n.GetValue()
case *ast.IntegerNode:
return n.GetValue()
case *ast.FloatNode:
return n.GetValue()
case *ast.BoolNode:
return n.GetValue()
case *ast.InfinityNode:
return n.GetValue()
case *ast.NanNode:
return n.GetValue()
case *ast.TagNode:
switch token.ReservedTagKeyword(n.Start.Value) {
case token.TimestampTag:
t, _ := d.castToTime(n.Value)
return t
case token.IntegerTag:
i, _ := strconv.Atoi(fmt.Sprint(d.nodeToValue(n.Value)))
return i
case token.FloatTag:
return d.castToFloat(d.nodeToValue(n.Value))
case token.NullTag:
return nil
case token.BinaryTag:
b, _ := base64.StdEncoding.DecodeString(d.nodeToValue(n.Value).(string))
return b
case token.StringTag:
return d.nodeToValue(n.Value)
case token.MappingTag:
return d.nodeToValue(n.Value)
}
case *ast.AnchorNode:
anchorName := n.Name.GetToken().Value
anchorValue := d.nodeToValue(n.Value)
d.anchorNodeMap[anchorName] = n.Value
return anchorValue
case *ast.AliasNode:
aliasName := n.Value.GetToken().Value
node := d.anchorNodeMap[aliasName]
return d.nodeToValue(node)
case *ast.LiteralNode:
return n.Value.GetValue()
case *ast.MappingKeyNode:
return d.nodeToValue(n.Value)
case *ast.MappingValueNode:
if n.Key.Type() == ast.MergeKeyType {
value := d.mergeValueNode(n.Value)
if d.useOrderedMap {
m := MapSlice{}
d.setToOrderedMapValue(value, &m)
return m
}
m := map[string]interface{}{}
d.setToMapValue(value, m)
return m
}
key := d.mapKeyNodeToString(n.Key)
if d.useOrderedMap {
return MapSlice{{Key: key, Value: d.nodeToValue(n.Value)}}
}
return map[string]interface{}{
key: d.nodeToValue(n.Value),
}
case *ast.MappingNode:
if d.useOrderedMap {
m := make(MapSlice, 0, len(n.Values))
for _, value := range n.Values {
d.setToOrderedMapValue(value, &m)
}
return m
}
m := make(map[string]interface{}, len(n.Values))
for _, value := range n.Values {
d.setToMapValue(value, m)
}
return m
case *ast.SequenceNode:
v := make([]interface{}, 0, len(n.Values))
for _, value := range n.Values {
v = append(v, d.nodeToValue(value))
}
return v
}
return nil
}
func (d *Decoder) resolveAlias(node ast.Node) ast.Node {
switch n := node.(type) {
case *ast.MappingNode:
for idx, value := range n.Values {
n.Values[idx] = d.resolveAlias(value).(*ast.MappingValueNode)
}
case *ast.TagNode:
n.Value = d.resolveAlias(n.Value)
case *ast.MappingKeyNode:
n.Value = d.resolveAlias(n.Value)
case *ast.MappingValueNode:
if n.Key.Type() == ast.MergeKeyType && n.Value.Type() == ast.AliasType {
value := d.resolveAlias(n.Value)
keyColumn := n.Key.GetToken().Position.Column
requiredColumn := keyColumn + 2
value.AddColumn(requiredColumn)
n.Value = value
} else {
n.Key = d.resolveAlias(n.Key)
n.Value = d.resolveAlias(n.Value)
}
case *ast.SequenceNode:
for idx, value := range n.Values {
n.Values[idx] = d.resolveAlias(value)
}
case *ast.AliasNode:
aliasName := n.Value.GetToken().Value
return d.resolveAlias(d.anchorNodeMap[aliasName])
}
return node
}
func (d *Decoder) getMapNode(node ast.Node) (ast.MapNode, error) {
if _, ok := node.(*ast.NullNode); ok {
return nil, nil
}
if anchor, ok := node.(*ast.AnchorNode); ok {
mapNode, ok := anchor.Value.(ast.MapNode)
if ok {
return mapNode, nil
}
return nil, errUnexpectedNodeType(anchor.Value.Type(), ast.MappingType, node.GetToken())
}
if alias, ok := node.(*ast.AliasNode); ok {
aliasName := alias.Value.GetToken().Value
node := d.anchorNodeMap[aliasName]
if node == nil {
return nil, xerrors.Errorf("cannot find anchor by alias name %s", aliasName)
}
mapNode, ok := node.(ast.MapNode)
if ok {
return mapNode, nil
}
return nil, errUnexpectedNodeType(node.Type(), ast.MappingType, node.GetToken())
}
mapNode, ok := node.(ast.MapNode)
if !ok {
return nil, errUnexpectedNodeType(node.Type(), ast.MappingType, node.GetToken())
}
return mapNode, nil
}
func (d *Decoder) getArrayNode(node ast.Node) (ast.ArrayNode, error) {
if _, ok := node.(*ast.NullNode); ok {
return nil, nil
}
if anchor, ok := node.(*ast.AnchorNode); ok {
arrayNode, ok := anchor.Value.(ast.ArrayNode)
if ok {
return arrayNode, nil
}
return nil, errUnexpectedNodeType(anchor.Value.Type(), ast.SequenceType, node.GetToken())
}
if alias, ok := node.(*ast.AliasNode); ok {
aliasName := alias.Value.GetToken().Value
node := d.anchorNodeMap[aliasName]
if node == nil {
return nil, xerrors.Errorf("cannot find anchor by alias name %s", aliasName)
}
arrayNode, ok := node.(ast.ArrayNode)
if ok {
return arrayNode, nil
}
return nil, errUnexpectedNodeType(node.Type(), ast.SequenceType, node.GetToken())
}
arrayNode, ok := node.(ast.ArrayNode)
if !ok {
return nil, errUnexpectedNodeType(node.Type(), ast.SequenceType, node.GetToken())
}
return arrayNode, nil
}
func (d *Decoder) fileToNode(f *ast.File) ast.Node {
for _, doc := range f.Docs {
if v := d.nodeToValue(doc.Body); v != nil {
return doc.Body
}
}
return nil
}
func (d *Decoder) convertValue(v reflect.Value, typ reflect.Type) (reflect.Value, error) {
if typ.Kind() != reflect.String {
if !v.Type().ConvertibleTo(typ) {
return reflect.Zero(typ), errTypeMismatch(typ, v.Type())
}
return v.Convert(typ), nil
}
// cast value to string
switch v.Type().Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return reflect.ValueOf(fmt.Sprint(v.Int())), nil
case reflect.Float32, reflect.Float64:
return reflect.ValueOf(fmt.Sprint(v.Float())), nil
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return reflect.ValueOf(fmt.Sprint(v.Uint())), nil
case reflect.Bool:
return reflect.ValueOf(fmt.Sprint(v.Bool())), nil
}
if !v.Type().ConvertibleTo(typ) {
return reflect.Zero(typ), errTypeMismatch(typ, v.Type())
}
return v.Convert(typ), nil
}
type overflowError struct {
dstType reflect.Type
srcNum string
}
func (e *overflowError) Error() string {
return fmt.Sprintf("cannot unmarshal %s into Go value of type %s ( overflow )", e.srcNum, e.dstType)
}
func errOverflow(dstType reflect.Type, num string) *overflowError {
return &overflowError{dstType: dstType, srcNum: num}
}
type typeError struct {
dstType reflect.Type
srcType reflect.Type
structFieldName *string
}
func (e *typeError) Error() string {
if e.structFieldName != nil {
return fmt.Sprintf("cannot unmarshal %s into Go struct field %s of type %s", e.srcType, *e.structFieldName, e.dstType)
}
return fmt.Sprintf("cannot unmarshal %s into Go value of type %s", e.srcType, e.dstType)
}
func errTypeMismatch(dstType, srcType reflect.Type) *typeError {
return &typeError{dstType: dstType, srcType: srcType}
}
type unknownFieldError struct {
err error
}
func (e *unknownFieldError) Error() string {
return e.err.Error()
}
func errUnknownField(msg string, tk *token.Token) *unknownFieldError {
return &unknownFieldError{err: errors.ErrSyntax(msg, tk)}
}
func errUnexpectedNodeType(actual, expected ast.NodeType, tk *token.Token) error {
return errors.ErrSyntax(fmt.Sprintf("%s was used where %s is expected", actual.YAMLName(), expected.YAMLName()), tk)
}
type duplicateKeyError struct {
err error
}
func (e *duplicateKeyError) Error() string {
return e.err.Error()
}
func errDuplicateKey(msg string, tk *token.Token) *duplicateKeyError {
return &duplicateKeyError{err: errors.ErrSyntax(msg, tk)}
}
func (d *Decoder) deleteStructKeys(structType reflect.Type, unknownFields map[string]ast.Node) error {
if structType.Kind() == reflect.Ptr {
structType = structType.Elem()
}
structFieldMap, err := structFieldMap(structType)
if err != nil {
return errors.Wrapf(err, "failed to create struct field map")
}
for j := 0; j < structType.NumField(); j++ {
field := structType.Field(j)
if isIgnoredStructField(field) {
continue
}
structField, exists := structFieldMap[field.Name]
if !exists {
continue
}
if structField.IsInline {
d.deleteStructKeys(field.Type, unknownFields)
} else {
delete(unknownFields, structField.RenderName)
}
}
return nil
}
func (d *Decoder) lastNode(node ast.Node) ast.Node {
switch n := node.(type) {
case *ast.MappingNode:
if len(n.Values) > 0 {
return d.lastNode(n.Values[len(n.Values)-1])
}
case *ast.MappingValueNode:
return d.lastNode(n.Value)
case *ast.SequenceNode:
if len(n.Values) > 0 {
return d.lastNode(n.Values[len(n.Values)-1])
}
}
return node
}
func (d *Decoder) unmarshalableDocument(node ast.Node) []byte {
node = d.resolveAlias(node)
doc := node.String()
last := d.lastNode(node)
if last != nil && last.Type() == ast.LiteralType {
doc += "\n"
}
return []byte(doc)
}
func (d *Decoder) unmarshalableText(node ast.Node) ([]byte, bool) {
node = d.resolveAlias(node)
if node.Type() == ast.AnchorType {
node = node.(*ast.AnchorNode).Value
}
switch n := node.(type) {
case *ast.StringNode:
return []byte(n.Value), true
case *ast.LiteralNode:
return []byte(n.Value.GetToken().Value), true
default:
scalar, ok := n.(ast.ScalarNode)
if ok {
return []byte(fmt.Sprint(scalar.GetValue())), true
}
}
return nil, false
}
type jsonUnmarshaler interface {
UnmarshalJSON([]byte) error
}
func (d *Decoder) canDecodeByUnmarshaler(dst reflect.Value) bool {
iface := dst.Addr().Interface()
switch iface.(type) {
case BytesUnmarshalerContext:
return true
case BytesUnmarshaler:
return true
case InterfaceUnmarshalerContext:
return true
case InterfaceUnmarshaler:
return true
case *time.Time:
return true
case *time.Duration:
return true
case encoding.TextUnmarshaler:
return true
case jsonUnmarshaler:
return d.useJSONUnmarshaler
}
return false
}
func (d *Decoder) decodeByUnmarshaler(ctx context.Context, dst reflect.Value, src ast.Node) error {
iface := dst.Addr().Interface()
if unmarshaler, ok := iface.(BytesUnmarshalerContext); ok {
if err := unmarshaler.UnmarshalYAML(ctx, d.unmarshalableDocument(src)); err != nil {
return errors.Wrapf(err, "failed to UnmarshalYAML")
}
return nil
}
if unmarshaler, ok := iface.(BytesUnmarshaler); ok {
if err := unmarshaler.UnmarshalYAML(d.unmarshalableDocument(src)); err != nil {
return errors.Wrapf(err, "failed to UnmarshalYAML")
}
return nil
}
if unmarshaler, ok := iface.(InterfaceUnmarshalerContext); ok {
if err := unmarshaler.UnmarshalYAML(ctx, func(v interface{}) error {
rv := reflect.ValueOf(v)
if rv.Type().Kind() != reflect.Ptr {
return errors.ErrDecodeRequiredPointerType
}
if err := d.decodeValue(ctx, rv.Elem(), src); err != nil {
return errors.Wrapf(err, "failed to decode value")
}
return nil
}); err != nil {
return errors.Wrapf(err, "failed to UnmarshalYAML")
}
return nil
}
if unmarshaler, ok := iface.(InterfaceUnmarshaler); ok {
if err := unmarshaler.UnmarshalYAML(func(v interface{}) error {
rv := reflect.ValueOf(v)
if rv.Type().Kind() != reflect.Ptr {
return errors.ErrDecodeRequiredPointerType
}
if err := d.decodeValue(ctx, rv.Elem(), src); err != nil {
return errors.Wrapf(err, "failed to decode value")
}
return nil
}); err != nil {
return errors.Wrapf(err, "failed to UnmarshalYAML")
}
return nil
}
if _, ok := iface.(*time.Time); ok {
return d.decodeTime(ctx, dst, src)
}
if _, ok := iface.(*time.Duration); ok {
return d.decodeDuration(ctx, dst, src)
}
if unmarshaler, isText := iface.(encoding.TextUnmarshaler); isText {
b, ok := d.unmarshalableText(src)
if ok {
if err := unmarshaler.UnmarshalText(b); err != nil {
return errors.Wrapf(err, "failed to UnmarshalText")
}
return nil
}
}
if d.useJSONUnmarshaler {
if unmarshaler, ok := iface.(jsonUnmarshaler); ok {
jsonBytes, err := YAMLToJSON(d.unmarshalableDocument(src))
if err != nil {
return errors.Wrapf(err, "failed to convert yaml to json")
}
jsonBytes = bytes.TrimRight(jsonBytes, "\n")
if err := unmarshaler.UnmarshalJSON(jsonBytes); err != nil {
return errors.Wrapf(err, "failed to UnmarshalJSON")
}
return nil
}
}
return xerrors.Errorf("does not implemented Unmarshaler")
}
var (
astNodeType = reflect.TypeOf((*ast.Node)(nil)).Elem()
)
func (d *Decoder) decodeValue(ctx context.Context, dst reflect.Value, src ast.Node) error {
if src.Type() == ast.AnchorType {
anchorName := src.(*ast.AnchorNode).Name.GetToken().Value
if _, exists := d.anchorValueMap[anchorName]; !exists {
d.anchorValueMap[anchorName] = dst
}
}
if d.canDecodeByUnmarshaler(dst) {
if err := d.decodeByUnmarshaler(ctx, dst, src); err != nil {
return errors.Wrapf(err, "failed to decode by unmarshaler")
}
return nil
}
valueType := dst.Type()
switch valueType.Kind() {
case reflect.Ptr:
if dst.IsNil() {
return nil
}
if src.Type() == ast.NullType {
// set nil value to pointer
dst.Set(reflect.Zero(valueType))
return nil
}
v := d.createDecodableValue(dst.Type())
if err := d.decodeValue(ctx, v, src); err != nil {
return errors.Wrapf(err, "failed to decode ptr value")
}
dst.Set(d.castToAssignableValue(v, dst.Type()))
case reflect.Interface:
if dst.Type() == astNodeType {
dst.Set(reflect.ValueOf(src))
return nil
}
v := reflect.ValueOf(d.nodeToValue(src))
if v.IsValid() {
dst.Set(v)
}
case reflect.Map:
return d.decodeMap(ctx, dst, src)
case reflect.Array:
return d.decodeArray(ctx, dst, src)
case reflect.Slice:
if mapSlice, ok := dst.Addr().Interface().(*MapSlice); ok {
return d.decodeMapSlice(ctx, mapSlice, src)
}
return d.decodeSlice(ctx, dst, src)
case reflect.Struct:
if mapItem, ok := dst.Addr().Interface().(*MapItem); ok {
return d.decodeMapItem(ctx, mapItem, src)
}
return d.decodeStruct(ctx, dst, src)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
v := d.nodeToValue(src)
switch vv := v.(type) {
case int64:
if !dst.OverflowInt(vv) {
dst.SetInt(vv)
return nil
}
case uint64:
if vv <= math.MaxInt64 && !dst.OverflowInt(int64(vv)) {
dst.SetInt(int64(vv))
return nil
}
case float64:
if vv <= math.MaxInt64 && !dst.OverflowInt(int64(vv)) {
dst.SetInt(int64(vv))
return nil
}
default:
return errTypeMismatch(valueType, reflect.TypeOf(v))
}
return errOverflow(valueType, fmt.Sprint(v))
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
v := d.nodeToValue(src)
switch vv := v.(type) {
case int64:
if 0 <= vv && !dst.OverflowUint(uint64(vv)) {
dst.SetUint(uint64(vv))
return nil
}
case uint64:
if !dst.OverflowUint(vv) {
dst.SetUint(vv)
return nil
}
case float64:
if 0 <= vv && vv <= math.MaxUint64 && !dst.OverflowUint(uint64(vv)) {
dst.SetUint(uint64(vv))
return nil
}
default:
return errTypeMismatch(valueType, reflect.TypeOf(v))
}
return errOverflow(valueType, fmt.Sprint(v))
}
v := reflect.ValueOf(d.nodeToValue(src))
if v.IsValid() {
convertedValue, err := d.convertValue(v, dst.Type())
if err != nil {
return errors.Wrapf(err, "failed to convert value")
}
dst.Set(convertedValue)
}
return nil
}
func (d *Decoder) createDecodableValue(typ reflect.Type) reflect.Value {
for {
if typ.Kind() == reflect.Ptr {
typ = typ.Elem()
continue
}
break
}
return reflect.New(typ).Elem()
}
func (d *Decoder) castToAssignableValue(value reflect.Value, target reflect.Type) reflect.Value {
if target.Kind() != reflect.Ptr {
return value
}
maxTryCount := 5
tryCount := 0
for {
if tryCount > maxTryCount {
return value
}
if value.Type().AssignableTo(target) {
break
}
value = value.Addr()
tryCount++
}
return value
}
func (d *Decoder) createDecodedNewValue(
ctx context.Context, typ reflect.Type, defaultVal reflect.Value, node ast.Node,
) (reflect.Value, error) {
if node.Type() == ast.AliasType {
aliasName := node.(*ast.AliasNode).Value.GetToken().Value
newValue := d.anchorValueMap[aliasName]
if newValue.IsValid() {
return newValue, nil
}
}
if node.Type() == ast.NullType {
return reflect.Zero(typ), nil
}
newValue := d.createDecodableValue(typ)
for defaultVal.Kind() == reflect.Ptr {
defaultVal = defaultVal.Elem()
}
if defaultVal.IsValid() && defaultVal.Type().AssignableTo(newValue.Type()) {
newValue.Set(defaultVal)
}
if err := d.decodeValue(ctx, newValue, node); err != nil {
return newValue, errors.Wrapf(err, "failed to decode value")
}
return newValue, nil
}
func (d *Decoder) keyToNodeMap(node ast.Node, ignoreMergeKey bool, getKeyOrValueNode func(*ast.MapNodeIter) ast.Node) (map[string]ast.Node, error) {
mapNode, err := d.getMapNode(node)
if err != nil {
return nil, errors.Wrapf(err, "failed to get map node")
}
keyMap := map[string]struct{}{}
keyToNodeMap := map[string]ast.Node{}
if mapNode == nil {
return keyToNodeMap, nil
}
mapIter := mapNode.MapRange()
for mapIter.Next() {
keyNode := mapIter.Key()
if keyNode.Type() == ast.MergeKeyType {
if ignoreMergeKey {
continue
}
mergeMap, err := d.keyToNodeMap(mapIter.Value(), ignoreMergeKey, getKeyOrValueNode)
if err != nil {
return nil, errors.Wrapf(err, "failed to get keyToNodeMap by MergeKey node")
}
for k, v := range mergeMap {
if err := d.validateDuplicateKey(keyMap, k, v); err != nil {
return nil, errors.Wrapf(err, "invalid struct key")
}
keyToNodeMap[k] = v
}
} else {
key, ok := d.nodeToValue(keyNode).(string)
if !ok {
return nil, errors.Wrapf(err, "failed to decode map key")
}
if err := d.validateDuplicateKey(keyMap, key, keyNode); err != nil {
return nil, errors.Wrapf(err, "invalid struct key")
}
keyToNodeMap[key] = getKeyOrValueNode(mapIter)
}
}
return keyToNodeMap, nil
}
func (d *Decoder) keyToKeyNodeMap(node ast.Node, ignoreMergeKey bool) (map[string]ast.Node, error) {
m, err := d.keyToNodeMap(node, ignoreMergeKey, func(nodeMap *ast.MapNodeIter) ast.Node { return nodeMap.Key() })
if err != nil {
return nil, errors.Wrapf(err, "failed to get keyToNodeMap")
}
return m, nil
}
func (d *Decoder) keyToValueNodeMap(node ast.Node, ignoreMergeKey bool) (map[string]ast.Node, error) {
m, err := d.keyToNodeMap(node, ignoreMergeKey, func(nodeMap *ast.MapNodeIter) ast.Node { return nodeMap.Value() })
if err != nil {
return nil, errors.Wrapf(err, "failed to get keyToNodeMap")
}
return m, nil
}
func (d *Decoder) setDefaultValueIfConflicted(v reflect.Value, fieldMap StructFieldMap) error {
typ := v.Type()
if typ.Kind() != reflect.Struct {
return nil
}
embeddedStructFieldMap, err := structFieldMap(typ)
if err != nil {
return errors.Wrapf(err, "failed to get struct field map by embedded type")
}
for i := 0; i < typ.NumField(); i++ {
field := typ.Field(i)
if isIgnoredStructField(field) {
continue
}
structField := embeddedStructFieldMap[field.Name]
if !fieldMap.isIncludedRenderName(structField.RenderName) {
continue
}
// if declared same key name, set default value
fieldValue := v.Field(i)
if fieldValue.CanSet() {
fieldValue.Set(reflect.Zero(fieldValue.Type()))
}
}
return nil
}
// This is a subset of the formats allowed by the regular expression
// defined at http://yaml.org/type/timestamp.html.
var allowedTimestampFormats = []string{
"2006-1-2T15:4:5.999999999Z07:00", // RCF3339Nano with short date fields.
"2006-1-2t15:4:5.999999999Z07:00", // RFC3339Nano with short date fields and lower-case "t".
"2006-1-2 15:4:5.999999999", // space separated with no time zone
"2006-1-2", // date only
}
func (d *Decoder) castToTime(src ast.Node) (time.Time, error) {
if src == nil {
return time.Time{}, nil
}
v := d.nodeToValue(src)
if t, ok := v.(time.Time); ok {
return t, nil
}
s, ok := v.(string)
if !ok {
return time.Time{}, errTypeMismatch(reflect.TypeOf(time.Time{}), reflect.TypeOf(v))
}
for _, format := range allowedTimestampFormats {
t, err := time.Parse(format, s)
if err != nil {
// invalid format
continue
}
return t, nil
}
return time.Time{}, nil
}
func (d *Decoder) decodeTime(ctx context.Context, dst reflect.Value, src ast.Node) error {
t, err := d.castToTime(src)
if err != nil {
return errors.Wrapf(err, "failed to convert to time")
}
dst.Set(reflect.ValueOf(t))
return nil
}
func (d *Decoder) castToDuration(src ast.Node) (time.Duration, error) {
if src == nil {
return 0, nil
}
v := d.nodeToValue(src)
if t, ok := v.(time.Duration); ok {
return t, nil
}
s, ok := v.(string)
if !ok {
return 0, errTypeMismatch(reflect.TypeOf(time.Duration(0)), reflect.TypeOf(v))
}
t, err := time.ParseDuration(s)
if err != nil {
return 0, errors.Wrapf(err, "failed to parse duration")
}
return t, nil
}
func (d *Decoder) decodeDuration(ctx context.Context, dst reflect.Value, src ast.Node) error {
t, err := d.castToDuration(src)
if err != nil {
return errors.Wrapf(err, "failed to convert to duration")
}
dst.Set(reflect.ValueOf(t))
return nil
}
// getMergeAliasName support single alias only
func (d *Decoder) getMergeAliasName(src ast.Node) string {
mapNode, err := d.getMapNode(src)
if err != nil {
return ""
}
if mapNode == nil {
return ""
}
mapIter := mapNode.MapRange()
for mapIter.Next() {
key := mapIter.Key()
value := mapIter.Value()
if key.Type() == ast.MergeKeyType && value.Type() == ast.AliasType {
return value.(*ast.AliasNode).Value.GetToken().Value
}
}
return ""
}
func (d *Decoder) decodeStruct(ctx context.Context, dst reflect.Value, src ast.Node) error {
if src == nil {
return nil
}
structType := dst.Type()
srcValue := reflect.ValueOf(src)
srcType := srcValue.Type()
if srcType.Kind() == reflect.Ptr {
srcType = srcType.Elem()
srcValue = srcValue.Elem()
}
if structType == srcType {
// dst value implements ast.Node
dst.Set(srcValue)
return nil
}
structFieldMap, err := structFieldMap(structType)
if err != nil {
return errors.Wrapf(err, "failed to create struct field map")
}
ignoreMergeKey := structFieldMap.hasMergeProperty()
keyToNodeMap, err := d.keyToValueNodeMap(src, ignoreMergeKey)
if err != nil {
return errors.Wrapf(err, "failed to get keyToValueNodeMap")
}
var unknownFields map[string]ast.Node
if d.disallowUnknownField {
unknownFields, err = d.keyToKeyNodeMap(src, ignoreMergeKey)
if err != nil {
return errors.Wrapf(err, "failed to get keyToKeyNodeMap")
}
}
aliasName := d.getMergeAliasName(src)
var foundErr error
for i := 0; i < structType.NumField(); i++ {
field := structType.Field(i)
if isIgnoredStructField(field) {
continue
}
structField := structFieldMap[field.Name]
if structField.IsInline {
fieldValue := dst.FieldByName(field.Name)
if structField.IsAutoAlias {
if aliasName != "" {
newFieldValue := d.anchorValueMap[aliasName]
if newFieldValue.IsValid() {
fieldValue.Set(d.castToAssignableValue(newFieldValue, fieldValue.Type()))
}
}
continue
}
if !fieldValue.CanSet() {
return xerrors.Errorf("cannot set embedded type as unexported field %s.%s", field.PkgPath, field.Name)
}
if fieldValue.Type().Kind() == reflect.Ptr && src.Type() == ast.NullType {
// set nil value to pointer
fieldValue.Set(reflect.Zero(fieldValue.Type()))
continue
}
mapNode := ast.Mapping(nil, false)
for k, v := range keyToNodeMap {
key := &ast.StringNode{BaseNode: &ast.BaseNode{}, Value: k}
mapNode.Values = append(mapNode.Values, ast.MappingValue(nil, key, v))
}
newFieldValue, err := d.createDecodedNewValue(ctx, fieldValue.Type(), fieldValue, mapNode)
if d.disallowUnknownField {
if err := d.deleteStructKeys(fieldValue.Type(), unknownFields); err != nil {
return errors.Wrapf(err, "cannot delete struct keys")
}
}
if err != nil {
if foundErr != nil {
continue
}
var te *typeError
if xerrors.As(err, &te) {
if te.structFieldName != nil {
fieldName := fmt.Sprintf("%s.%s", structType.Name(), *te.structFieldName)
te.structFieldName = &fieldName
} else {
fieldName := fmt.Sprintf("%s.%s", structType.Name(), field.Name)
te.structFieldName = &fieldName
}
foundErr = te
continue
} else {
foundErr = err
}
continue
}
d.setDefaultValueIfConflicted(newFieldValue, structFieldMap)
fieldValue.Set(d.castToAssignableValue(newFieldValue, fieldValue.Type()))
continue
}
v, exists := keyToNodeMap[structField.RenderName]
if !exists {
continue
}
delete(unknownFields, structField.RenderName)
fieldValue := dst.FieldByName(field.Name)
if fieldValue.Type().Kind() == reflect.Ptr && src.Type() == ast.NullType {
// set nil value to pointer
fieldValue.Set(reflect.Zero(fieldValue.Type()))
continue
}
newFieldValue, err := d.createDecodedNewValue(ctx, fieldValue.Type(), fieldValue, v)
if err != nil {
if foundErr != nil {
continue
}
var te *typeError
if xerrors.As(err, &te) {
fieldName := fmt.Sprintf("%s.%s", structType.Name(), field.Name)
te.structFieldName = &fieldName
foundErr = te
} else {
foundErr = err
}
continue
}
fieldValue.Set(d.castToAssignableValue(newFieldValue, fieldValue.Type()))
}
if foundErr != nil {
return errors.Wrapf(foundErr, "failed to decode value")
}
// Ignore unknown fields when parsing an inline struct (recognized by a nil token).
// Unknown fields are expected (they could be fields from the parent struct).
if len(unknownFields) != 0 && d.disallowUnknownField && src.GetToken() != nil {
for key, node := range unknownFields {
return errUnknownField(fmt.Sprintf(`unknown field "%s"`, key), node.GetToken())
}
}
if d.validator != nil {
if err := d.validator.Struct(dst.Interface()); err != nil {
ev := reflect.ValueOf(err)
if ev.Type().Kind() == reflect.Slice {
for i := 0; i < ev.Len(); i++ {
fieldErr, ok := ev.Index(i).Interface().(FieldError)
if !ok {
continue
}
fieldName := fieldErr.StructField()
structField, exists := structFieldMap[fieldName]
if !exists {
continue
}
node, exists := keyToNodeMap[structField.RenderName]
if exists {
// TODO: to make FieldError message cutomizable
return errors.ErrSyntax(fmt.Sprintf("%s", err), node.GetToken())
} else if t := src.GetToken(); t != nil && t.Prev != nil && t.Prev.Prev != nil {
// A missing required field will not be in the keyToNodeMap
// the error needs to be associated with the parent of the source node
return errors.ErrSyntax(fmt.Sprintf("%s", err), t.Prev.Prev)
}
}
}
return err
}
}
return nil
}
func (d *Decoder) decodeArray(ctx context.Context, dst reflect.Value, src ast.Node) error {
arrayNode, err := d.getArrayNode(src)
if err != nil {
return errors.Wrapf(err, "failed to get array node")
}
if arrayNode == nil {
return nil
}
iter := arrayNode.ArrayRange()
arrayValue := reflect.New(dst.Type()).Elem()
arrayType := dst.Type()
elemType := arrayType.Elem()
idx := 0
var foundErr error
for iter.Next() {
v := iter.Value()
if elemType.Kind() == reflect.Ptr && v.Type() == ast.NullType {
// set nil value to pointer
arrayValue.Index(idx).Set(reflect.Zero(elemType))
} else {
dstValue, err := d.createDecodedNewValue(ctx, elemType, reflect.Value{}, v)
if err != nil {
if foundErr == nil {
foundErr = err
}
continue
} else {
arrayValue.Index(idx).Set(d.castToAssignableValue(dstValue, elemType))
}
}
idx++
}
dst.Set(arrayValue)
if foundErr != nil {
return errors.Wrapf(foundErr, "failed to decode value")
}
return nil
}
func (d *Decoder) decodeSlice(ctx context.Context, dst reflect.Value, src ast.Node) error {
arrayNode, err := d.getArrayNode(src)
if err != nil {
return errors.Wrapf(err, "failed to get array node")
}
if arrayNode == nil {
return nil
}
iter := arrayNode.ArrayRange()
sliceType := dst.Type()
sliceValue := reflect.MakeSlice(sliceType, 0, iter.Len())
elemType := sliceType.Elem()
var foundErr error
for iter.Next() {
v := iter.Value()
if elemType.Kind() == reflect.Ptr && v.Type() == ast.NullType {
// set nil value to pointer
sliceValue = reflect.Append(sliceValue, reflect.Zero(elemType))
continue
}
dstValue, err := d.createDecodedNewValue(ctx, elemType, reflect.Value{}, v)
if err != nil {
if foundErr == nil {
foundErr = err
}
continue
}
sliceValue = reflect.Append(sliceValue, d.castToAssignableValue(dstValue, elemType))
}
dst.Set(sliceValue)
if foundErr != nil {
return errors.Wrapf(foundErr, "failed to decode value")
}
return nil
}
func (d *Decoder) decodeMapItem(ctx context.Context, dst *MapItem, src ast.Node) error {
mapNode, err := d.getMapNode(src)
if err != nil {
return errors.Wrapf(err, "failed to get map node")
}
if mapNode == nil {
return nil
}
mapIter := mapNode.MapRange()
if !mapIter.Next() {
return nil
}
key := mapIter.Key()
value := mapIter.Value()
if key.Type() == ast.MergeKeyType {
if err := d.decodeMapItem(ctx, dst, value); err != nil {
return errors.Wrapf(err, "failed to decode map with merge key")
}
return nil
}
*dst = MapItem{
Key: d.nodeToValue(key),
Value: d.nodeToValue(value),
}
return nil
}
func (d *Decoder) validateDuplicateKey(keyMap map[string]struct{}, key interface{}, keyNode ast.Node) error {
k, ok := key.(string)
if !ok {
return nil
}
if d.disallowDuplicateKey {
if _, exists := keyMap[k]; exists {
return errDuplicateKey(fmt.Sprintf(`duplicate key "%s"`, k), keyNode.GetToken())
}
}
keyMap[k] = struct{}{}
return nil
}
func (d *Decoder) decodeMapSlice(ctx context.Context, dst *MapSlice, src ast.Node) error {
mapNode, err := d.getMapNode(src)
if err != nil {
return errors.Wrapf(err, "failed to get map node")
}
if mapNode == nil {
return nil
}
mapSlice := MapSlice{}
mapIter := mapNode.MapRange()
keyMap := map[string]struct{}{}
for mapIter.Next() {
key := mapIter.Key()
value := mapIter.Value()
if key.Type() == ast.MergeKeyType {
var m MapSlice
if err := d.decodeMapSlice(ctx, &m, value); err != nil {
return errors.Wrapf(err, "failed to decode map with merge key")
}
for _, v := range m {
if err := d.validateDuplicateKey(keyMap, v.Key, value); err != nil {
return errors.Wrapf(err, "invalid map key")
}
mapSlice = append(mapSlice, v)
}
continue
}
k := d.nodeToValue(key)
if err := d.validateDuplicateKey(keyMap, k, key); err != nil {
return errors.Wrapf(err, "invalid map key")
}
mapSlice = append(mapSlice, MapItem{
Key: k,
Value: d.nodeToValue(value),
})
}
*dst = mapSlice
return nil
}
func (d *Decoder) decodeMap(ctx context.Context, dst reflect.Value, src ast.Node) error {
mapNode, err := d.getMapNode(src)
if err != nil {
return errors.Wrapf(err, "failed to get map node")
}
if mapNode == nil {
return nil
}
mapType := dst.Type()
mapValue := reflect.MakeMap(mapType)
keyType := mapValue.Type().Key()
valueType := mapValue.Type().Elem()
mapIter := mapNode.MapRange()
keyMap := map[string]struct{}{}
var foundErr error
for mapIter.Next() {
key := mapIter.Key()
value := mapIter.Value()
if key.Type() == ast.MergeKeyType {
if err := d.decodeMap(ctx, dst, value); err != nil {
return errors.Wrapf(err, "failed to decode map with merge key")
}
iter := dst.MapRange()
for iter.Next() {
if err := d.validateDuplicateKey(keyMap, iter.Key(), value); err != nil {
return errors.Wrapf(err, "invalid map key")
}
mapValue.SetMapIndex(iter.Key(), iter.Value())
}
continue
}
k := reflect.ValueOf(d.nodeToValue(key))
if k.IsValid() && k.Type().ConvertibleTo(keyType) {
k = k.Convert(keyType)
}
if k.IsValid() {
if err := d.validateDuplicateKey(keyMap, k.Interface(), key); err != nil {
return errors.Wrapf(err, "invalid map key")
}
}
if valueType.Kind() == reflect.Ptr && value.Type() == ast.NullType {
// set nil value to pointer
mapValue.SetMapIndex(k, reflect.Zero(valueType))
continue
}
dstValue, err := d.createDecodedNewValue(ctx, valueType, reflect.Value{}, value)
if err != nil {
if foundErr == nil {
foundErr = err
}
}
if !k.IsValid() {
// expect nil key
mapValue.SetMapIndex(d.createDecodableValue(keyType), d.castToAssignableValue(dstValue, valueType))
continue
}
mapValue.SetMapIndex(k, d.castToAssignableValue(dstValue, valueType))
}
dst.Set(mapValue)
if foundErr != nil {
return errors.Wrapf(foundErr, "failed to decode value")
}
return nil
}
func (d *Decoder) fileToReader(file string) (io.Reader, error) {
reader, err := os.Open(file)
if err != nil {
return nil, errors.Wrapf(err, "failed to open file")
}
return reader, nil
}
func (d *Decoder) isYAMLFile(file string) bool {
ext := filepath.Ext(file)
if ext == ".yml" {
return true
}
if ext == ".yaml" {
return true
}
return false
}
func (d *Decoder) readersUnderDir(dir string) ([]io.Reader, error) {
pattern := fmt.Sprintf("%s/*", dir)
matches, err := filepath.Glob(pattern)
if err != nil {
return nil, errors.Wrapf(err, "failed to get files by %s", pattern)
}
readers := []io.Reader{}
for _, match := range matches {
if !d.isYAMLFile(match) {
continue
}
reader, err := d.fileToReader(match)
if err != nil {
return nil, errors.Wrapf(err, "failed to get reader")
}
readers = append(readers, reader)
}
return readers, nil
}
func (d *Decoder) readersUnderDirRecursive(dir string) ([]io.Reader, error) {
readers := []io.Reader{}
if err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if !d.isYAMLFile(path) {
return nil
}
reader, err := d.fileToReader(path)
if err != nil {
return errors.Wrapf(err, "failed to get reader")
}
readers = append(readers, reader)
return nil
}); err != nil {
return nil, errors.Wrapf(err, "interrupt walk in %s", dir)
}
return readers, nil
}
func (d *Decoder) resolveReference() error {
for _, opt := range d.opts {
if err := opt(d); err != nil {
return errors.Wrapf(err, "failed to exec option")
}
}
for _, file := range d.referenceFiles {
reader, err := d.fileToReader(file)
if err != nil {
return errors.Wrapf(err, "failed to get reader")
}
d.referenceReaders = append(d.referenceReaders, reader)
}
for _, dir := range d.referenceDirs {
if !d.isRecursiveDir {
readers, err := d.readersUnderDir(dir)
if err != nil {
return errors.Wrapf(err, "failed to get readers from under the %s", dir)
}
d.referenceReaders = append(d.referenceReaders, readers...)
} else {
readers, err := d.readersUnderDirRecursive(dir)
if err != nil {
return errors.Wrapf(err, "failed to get readers from under the %s", dir)
}
d.referenceReaders = append(d.referenceReaders, readers...)
}
}
for _, reader := range d.referenceReaders {
bytes, err := ioutil.ReadAll(reader)
if err != nil {
return errors.Wrapf(err, "failed to read buffer")
}
// assign new anchor definition to anchorMap
if _, err := d.parse(bytes); err != nil {
return errors.Wrapf(err, "failed to decode")
}
}
d.isResolvedReference = true
return nil
}
func (d *Decoder) parse(bytes []byte) (*ast.File, error) {
var parseMode parser.Mode
if d.toCommentMap != nil {
parseMode = parser.ParseComments
}
f, err := parser.ParseBytes(bytes, parseMode)
if err != nil {
return nil, errors.Wrapf(err, "failed to parse yaml")
}
normalizedFile := &ast.File{}
for _, doc := range f.Docs {
// try to decode ast.Node to value and map anchor value to anchorMap
if v := d.nodeToValue(doc.Body); v != nil {
normalizedFile.Docs = append(normalizedFile.Docs, doc)
}
}
return normalizedFile, nil
}
func (d *Decoder) isInitialized() bool {
return d.parsedFile != nil
}
func (d *Decoder) decodeInit() error {
if !d.isResolvedReference {
if err := d.resolveReference(); err != nil {
return errors.Wrapf(err, "failed to resolve reference")
}
}
var buf bytes.Buffer
if _, err := io.Copy(&buf, d.reader); err != nil {
return errors.Wrapf(err, "failed to copy from reader")
}
file, err := d.parse(buf.Bytes())
if err != nil {
return errors.Wrapf(err, "failed to decode")
}
d.parsedFile = file
return nil
}
func (d *Decoder) decode(ctx context.Context, v reflect.Value) error {
if len(d.parsedFile.Docs) <= d.streamIndex {
return io.EOF
}
body := d.parsedFile.Docs[d.streamIndex].Body
if body == nil {
return nil
}
if err := d.decodeValue(ctx, v.Elem(), body); err != nil {
return errors.Wrapf(err, "failed to decode value")
}
d.streamIndex++
return nil
}
// Decode reads the next YAML-encoded value from its input
// and stores it in the value pointed to by v.
//
// See the documentation for Unmarshal for details about the
// conversion of YAML into a Go value.
func (d *Decoder) Decode(v interface{}) error {
return d.DecodeContext(context.Background(), v)
}
// DecodeContext reads the next YAML-encoded value from its input
// and stores it in the value pointed to by v with context.Context.
func (d *Decoder) DecodeContext(ctx context.Context, v interface{}) error {
rv := reflect.ValueOf(v)
if rv.Type().Kind() != reflect.Ptr {
return errors.ErrDecodeRequiredPointerType
}
if d.isInitialized() {
if err := d.decode(ctx, rv); err != nil {
if err == io.EOF {
return err
}
return errors.Wrapf(err, "failed to decode")
}
return nil
}
if err := d.decodeInit(); err != nil {
return errors.Wrapf(err, "failed to decodeInit")
}
if err := d.decode(ctx, rv); err != nil {
if err == io.EOF {
return err
}
return errors.Wrapf(err, "failed to decode")
}
return nil
}
// DecodeFromNode decodes node into the value pointed to by v.
func (d *Decoder) DecodeFromNode(node ast.Node, v interface{}) error {
return d.DecodeFromNodeContext(context.Background(), node, v)
}
// DecodeFromNodeContext decodes node into the value pointed to by v with context.Context.
func (d *Decoder) DecodeFromNodeContext(ctx context.Context, node ast.Node, v interface{}) error {
rv := reflect.ValueOf(v)
if rv.Type().Kind() != reflect.Ptr {
return errors.ErrDecodeRequiredPointerType
}
if !d.isInitialized() {
if err := d.decodeInit(); err != nil {
return errors.Wrapf(err, "failed to decodInit")
}
}
// resolve references to the anchor on the same file
d.nodeToValue(node)
if err := d.decodeValue(ctx, rv.Elem(), node); err != nil {
return errors.Wrapf(err, "failed to decode value")
}
return nil
}
golang-github-goccy-go-yaml-1.9.5/decode_test.go 0000664 0000000 0000000 00000152007 14167531274 0021552 0 ustar 00root root 0000000 0000000 package yaml_test
import (
"bytes"
"context"
"fmt"
"io"
"log"
"math"
"net"
"reflect"
"strconv"
"strings"
"testing"
"time"
"github.com/goccy/go-yaml"
"github.com/goccy/go-yaml/ast"
"github.com/goccy/go-yaml/internal/errors"
"github.com/goccy/go-yaml/parser"
"golang.org/x/xerrors"
)
type Child struct {
B int
C int `yaml:"-"`
}
func TestDecoder(t *testing.T) {
tests := []struct {
source string
value interface{}
}{
{
"null\n",
(*struct{})(nil),
},
{
"v: hi\n",
map[string]string{"v": "hi"},
},
{
"v: \"true\"\n",
map[string]string{"v": "true"},
},
{
"v: \"false\"\n",
map[string]string{"v": "false"},
},
{
"v: true\n",
map[string]interface{}{"v": true},
},
{
"v: true\n",
map[string]string{"v": "true"},
},
{
"v: 10\n",
map[string]string{"v": "10"},
},
{
"v: -10\n",
map[string]string{"v": "-10"},
},
{
"v: 1.234\n",
map[string]string{"v": "1.234"},
},
{
"v: false\n",
map[string]bool{"v": false},
},
{
"v: 10\n",
map[string]int{"v": 10},
},
{
"v: 10",
map[string]interface{}{"v": 10},
},
{
"v: 0b10",
map[string]interface{}{"v": 2},
},
{
"v: -0b101010",
map[string]interface{}{"v": -42},
},
{
"v: -0b1000000000000000000000000000000000000000000000000000000000000000",
map[string]interface{}{"v": -9223372036854775808},
},
{
"v: 0xA",
map[string]interface{}{"v": 10},
},
{
"v: .1",
map[string]interface{}{"v": 0.1},
},
{
"v: -.1",
map[string]interface{}{"v": -0.1},
},
{
"v: -10\n",
map[string]int{"v": -10},
},
{
"v: 4294967296\n",
map[string]int{"v": 4294967296},
},
{
"v: 0.1\n",
map[string]interface{}{"v": 0.1},
},
{
"v: 0.99\n",
map[string]float32{"v": 0.99},
},
{
"v: -0.1\n",
map[string]float64{"v": -0.1},
},
{
"v: 6.8523e+5",
map[string]interface{}{"v": 6.8523e+5},
},
{
"v: 685.230_15e+03",
map[string]interface{}{"v": 685.23015e+03},
},
{
"v: 685_230.15",
map[string]interface{}{"v": 685230.15},
},
{
"v: 685_230.15",
map[string]float64{"v": 685230.15},
},
{
"v: 685230",
map[string]interface{}{"v": 685230},
},
{
"v: +685_230",
map[string]interface{}{"v": 685230},
},
{
"v: 02472256",
map[string]interface{}{"v": 685230},
},
{
"v: 0x_0A_74_AE",
map[string]interface{}{"v": 685230},
},
{
"v: 0b1010_0111_0100_1010_1110",
map[string]interface{}{"v": 685230},
},
{
"v: +685_230",
map[string]int{"v": 685230},
},
// Bools from spec
{
"v: True",
map[string]interface{}{"v": true},
},
{
"v: TRUE",
map[string]interface{}{"v": true},
},
{
"v: False",
map[string]interface{}{"v": false},
},
{
"v: FALSE",
map[string]interface{}{"v": false},
},
{
"v: y",
map[string]interface{}{"v": "y"}, // y or yes or Yes is string
},
{
"v: NO",
map[string]interface{}{"v": "NO"}, // no or No or NO is string
},
{
"v: on",
map[string]interface{}{"v": "on"}, // on is string
},
// Some cross type conversions
{
"v: 42",
map[string]uint{"v": 42},
}, {
"v: 4294967296",
map[string]uint64{"v": 4294967296},
},
// int
{
"v: 2147483647",
map[string]int{"v": math.MaxInt32},
},
{
"v: -2147483648",
map[string]int{"v": math.MinInt32},
},
// int64
{
"v: 9223372036854775807",
map[string]int64{"v": math.MaxInt64},
},
{
"v: 0b111111111111111111111111111111111111111111111111111111111111111",
map[string]int64{"v": math.MaxInt64},
},
{
"v: -9223372036854775808",
map[string]int64{"v": math.MinInt64},
},
{
"v: -0b111111111111111111111111111111111111111111111111111111111111111",
map[string]int64{"v": -math.MaxInt64},
},
// uint
{
"v: 0",
map[string]uint{"v": 0},
},
{
"v: 4294967295",
map[string]uint{"v": math.MaxUint32},
},
// uint64
{
"v: 0",
map[string]uint{"v": 0},
},
{
"v: 18446744073709551615",
map[string]uint64{"v": math.MaxUint64},
},
{
"v: 0b1111111111111111111111111111111111111111111111111111111111111111",
map[string]uint64{"v": math.MaxUint64},
},
{
"v: 9223372036854775807",
map[string]uint64{"v": math.MaxInt64},
},
// float32
{
"v: 3.40282346638528859811704183484516925440e+38",
map[string]float32{"v": math.MaxFloat32},
},
{
"v: 1.401298464324817070923729583289916131280e-45",
map[string]float32{"v": math.SmallestNonzeroFloat32},
},
{
"v: 18446744073709551615",
map[string]float32{"v": float32(math.MaxUint64)},
},
{
"v: 18446744073709551616",
map[string]float32{"v": float32(math.MaxUint64 + 1)},
},
// float64
{
"v: 1.797693134862315708145274237317043567981e+308",
map[string]float64{"v": math.MaxFloat64},
},
{
"v: 4.940656458412465441765687928682213723651e-324",
map[string]float64{"v": math.SmallestNonzeroFloat64},
},
{
"v: 18446744073709551615",
map[string]float64{"v": float64(math.MaxUint64)},
},
{
"v: 18446744073709551616",
map[string]float64{"v": float64(math.MaxUint64 + 1)},
},
// Timestamps
{
// Date only.
"v: 2015-01-01\n",
map[string]time.Time{"v": time.Date(2015, 1, 1, 0, 0, 0, 0, time.UTC)},
},
{
// RFC3339
"v: 2015-02-24T18:19:39.12Z\n",
map[string]time.Time{"v": time.Date(2015, 2, 24, 18, 19, 39, .12e9, time.UTC)},
},
{
// RFC3339 with short dates.
"v: 2015-2-3T3:4:5Z",
map[string]time.Time{"v": time.Date(2015, 2, 3, 3, 4, 5, 0, time.UTC)},
},
{
// ISO8601 lower case t
"v: 2015-02-24t18:19:39Z\n",
map[string]time.Time{"v": time.Date(2015, 2, 24, 18, 19, 39, 0, time.UTC)},
},
{
// space separate, no time zone
"v: 2015-02-24 18:19:39\n",
map[string]time.Time{"v": time.Date(2015, 2, 24, 18, 19, 39, 0, time.UTC)},
},
{
"v: 60s\n",
map[string]time.Duration{"v": time.Minute},
},
{
"v: -0.5h\n",
map[string]time.Duration{"v": -30 * time.Minute},
},
// Single Quoted values.
{
`'1': '2'`,
map[interface{}]interface{}{"1": `2`},
},
{
`'1': '"2"'`,
map[interface{}]interface{}{"1": `"2"`},
},
{
`'1': ''''`,
map[interface{}]interface{}{"1": `'`},
},
{
`'1': '''2'''`,
map[interface{}]interface{}{"1": `'2'`},
},
{
`'1': 'B''z'`,
map[interface{}]interface{}{"1": `B'z`},
},
{
`'1': '\'`,
map[interface{}]interface{}{"1": `\`},
},
{
`'1': '\\'`,
map[interface{}]interface{}{"1": `\\`},
},
{
`'1': '\"2\"'`,
map[interface{}]interface{}{"1": `\"2\"`},
},
{
`'1': '\\"2\\"'`,
map[interface{}]interface{}{"1": `\\"2\\"`},
},
{
"'1': ' 1\n 2\n 3'",
map[interface{}]interface{}{"1": " 1 2 3"},
},
{
"'1': '\n 2\n 3'",
map[interface{}]interface{}{"1": " 2 3"},
},
// Double Quoted values.
{
`"1": "2"`,
map[interface{}]interface{}{"1": `2`},
},
{
`"1": "\"2\""`,
map[interface{}]interface{}{"1": `"2"`},
},
{
`"1": "\""`,
map[interface{}]interface{}{"1": `"`},
},
{
`"1": "X\"z"`,
map[interface{}]interface{}{"1": `X"z`},
},
{
`"1": "\\"`,
map[interface{}]interface{}{"1": `\`},
},
{
`"1": "\\\\"`,
map[interface{}]interface{}{"1": `\\`},
},
{
`"1": "\\\"2\\\""`,
map[interface{}]interface{}{"1": `\"2\"`},
},
{
"'1': \" 1\n 2\n 3\"",
map[interface{}]interface{}{"1": " 1 2 3"},
},
{
"'1': \"\n 2\n 3\"",
map[interface{}]interface{}{"1": " 2 3"},
},
{
`"1": "a\x2Fb"`,
map[interface{}]interface{}{"1": `a/b`},
},
{
`"1": "a\u002Fb"`,
map[interface{}]interface{}{"1": `a/b`},
},
{
`"1": "a\x2Fb\u002Fc\U0000002Fd"`,
map[interface{}]interface{}{"1": `a/b/c/d`},
},
{
"a: -b_c",
map[string]interface{}{"a": "-b_c"},
},
{
"a: +b_c",
map[string]interface{}{"a": "+b_c"},
},
{
"a: 50cent_of_dollar",
map[string]interface{}{"a": "50cent_of_dollar"},
},
// Nulls
{
"v:",
map[string]interface{}{"v": nil},
},
{
"v: ~",
map[string]interface{}{"v": nil},
},
{
"~: null key",
map[interface{}]string{nil: "null key"},
},
{
"v:",
map[string]*bool{"v": nil},
},
{
"v: null",
map[string]*string{"v": nil},
},
{
"v: null",
map[string]string{"v": ""},
},
{
"v: null",
map[string]interface{}{"v": nil},
},
{
"v: Null",
map[string]interface{}{"v": nil},
},
{
"v: NULL",
map[string]interface{}{"v": nil},
},
{
"v: ~",
map[string]*string{"v": nil},
},
{
"v: ~",
map[string]string{"v": ""},
},
{
"v: .inf\n",
map[string]interface{}{"v": math.Inf(0)},
},
{
"v: .Inf\n",
map[string]interface{}{"v": math.Inf(0)},
},
{
"v: .INF\n",
map[string]interface{}{"v": math.Inf(0)},
},
{
"v: -.inf\n",
map[string]interface{}{"v": math.Inf(-1)},
},
{
"v: -.Inf\n",
map[string]interface{}{"v": math.Inf(-1)},
},
{
"v: -.INF\n",
map[string]interface{}{"v": math.Inf(-1)},
},
{
"v: .nan\n",
map[string]interface{}{"v": math.NaN()},
},
{
"v: .NaN\n",
map[string]interface{}{"v": math.NaN()},
},
{
"v: .NAN\n",
map[string]interface{}{"v": math.NaN()},
},
// Explicit tags.
{
"v: !!float '1.1'",
map[string]interface{}{"v": 1.1},
},
{
"v: !!float 0",
map[string]interface{}{"v": float64(0)},
},
{
"v: !!float -1",
map[string]interface{}{"v": float64(-1)},
},
{
"v: !!null ''",
map[string]interface{}{"v": nil},
},
{
"v: !!timestamp \"2015-01-01\"",
map[string]time.Time{"v": time.Date(2015, 1, 1, 0, 0, 0, 0, time.UTC)},
},
{
"v: !!timestamp 2015-01-01",
map[string]time.Time{"v": time.Date(2015, 1, 1, 0, 0, 0, 0, time.UTC)},
},
// Flow sequence
{
"v: [A,B]",
map[string]interface{}{"v": []interface{}{"A", "B"}},
},
{
"v: [A,B,C,]",
map[string][]string{"v": []string{"A", "B", "C"}},
},
{
"v: [A,1,C]",
map[string][]string{"v": []string{"A", "1", "C"}},
},
{
"v: [A,1,C]",
map[string]interface{}{"v": []interface{}{"A", 1, "C"}},
},
// Block sequence
{
"v:\n - A\n - B",
map[string]interface{}{"v": []interface{}{"A", "B"}},
},
{
"v:\n - A\n - B\n - C",
map[string][]string{"v": []string{"A", "B", "C"}},
},
{
"v:\n - A\n - 1\n - C",
map[string][]string{"v": []string{"A", "1", "C"}},
},
{
"v:\n - A\n - 1\n - C",
map[string]interface{}{"v": []interface{}{"A", 1, "C"}},
},
// Map inside interface with no type hints.
{
"a: {b: c}",
map[interface{}]interface{}{"a": map[interface{}]interface{}{"b": "c"}},
},
{
"v: \"\"\n",
map[string]string{"v": ""},
},
{
"v:\n- A\n- B\n",
map[string][]string{"v": {"A", "B"}},
},
{
"a: '-'\n",
map[string]string{"a": "-"},
},
{
"123\n",
123,
},
{
"hello: world\n",
map[string]string{"hello": "world"},
},
{
"hello: world\r\n",
map[string]string{"hello": "world"},
},
{
"hello: world\rGo: Gopher",
map[string]string{"hello": "world", "Go": "Gopher"},
},
// Structs and type conversions.
{
"hello: world",
struct{ Hello string }{"world"},
},
{
"a: {b: c}",
struct{ A struct{ B string } }{struct{ B string }{"c"}},
},
{
"a: {b: c}",
struct{ A map[string]string }{map[string]string{"b": "c"}},
},
{
"a:",
struct{ A map[string]string }{},
},
{
"a: 1",
struct{ A int }{1},
},
{
"a: 1",
struct{ A float64 }{1},
},
{
"a: 1.0",
struct{ A int }{1},
},
{
"a: 1.0",
struct{ A uint }{1},
},
{
"a: [1, 2]",
struct{ A []int }{[]int{1, 2}},
},
{
"a: [1, 2]",
struct{ A [2]int }{[2]int{1, 2}},
},
{
"a: 1",
struct{ B int }{0},
},
{
"a: 1",
struct {
B int `yaml:"a"`
}{1},
},
{
"a: 1\n",
yaml.MapItem{Key: "a", Value: 1},
},
{
"a: 1\nb: 2\nc: 3\n",
yaml.MapSlice{
{Key: "a", Value: 1},
{Key: "b", Value: 2},
{Key: "c", Value: 3},
},
},
{
"v:\n- A\n- 1\n- B:\n - 2\n - 3\n",
map[string]interface{}{
"v": []interface{}{
"A",
1,
map[string][]int{
"B": {2, 3},
},
},
},
},
{
"a:\n b: c\n",
map[string]interface{}{
"a": map[string]string{
"b": "c",
},
},
},
{
"a: {x: 1}\n",
map[string]map[string]int{
"a": {
"x": 1,
},
},
},
{
"t2: 2018-01-09T10:40:47Z\nt4: 2098-01-09T10:40:47Z\n",
map[string]string{
"t2": "2018-01-09T10:40:47Z",
"t4": "2098-01-09T10:40:47Z",
},
},
{
"a: [1, 2]\n",
map[string][]int{
"a": {1, 2},
},
},
{
"a: {b: c, d: e}\n",
map[string]interface{}{
"a": map[string]string{
"b": "c",
"d": "e",
},
},
},
{
"a: 3s\n",
map[string]string{
"a": "3s",
},
},
{
"a: \n",
map[string]string{"a": ""},
},
{
"a: \"1:1\"\n",
map[string]string{"a": "1:1"},
},
{
"a: 1.2.3.4\n",
map[string]string{"a": "1.2.3.4"},
},
{
"a: 'b: c'\n",
map[string]string{"a": "b: c"},
},
{
"a: 'Hello #comment'\n",
map[string]string{"a": "Hello #comment"},
},
{
"a: 100.5\n",
map[string]interface{}{
"a": 100.5,
},
},
{
"a: \"\\0\"\n",
map[string]string{"a": "\\0"},
},
{
"b: 2\na: 1\nd: 4\nc: 3\nsub:\n e: 5\n",
map[string]interface{}{
"b": 2,
"a": 1,
"d": 4,
"c": 3,
"sub": map[string]int{
"e": 5,
},
},
},
{
" a : b \n",
map[string]string{"a": "b"},
},
{
"a: b # comment\nb: c\n",
map[string]string{
"a": "b",
"b": "c",
},
},
{
"---\na: b\n",
map[string]string{"a": "b"},
},
{
"a: b\n...\n",
map[string]string{"a": "b"},
},
{
"%YAML 1.2\n---\n",
(*struct{})(nil),
},
{
"---\n",
(*struct{})(nil),
},
{
"...",
(*struct{})(nil),
},
{
"v: go test ./...",
map[string]string{"v": "go test ./..."},
},
{
"v: echo ---",
map[string]string{"v": "echo ---"},
},
{
"v: |\n hello\n ...\n world\n",
map[string]string{"v": "hello\n...\nworld\n"},
},
{
"a: !!binary gIGC\n",
map[string]string{"a": "\x80\x81\x82"},
},
{
"a: !!binary |\n " + strings.Repeat("kJCQ", 17) + "kJ\n CQ\n",
map[string]string{"a": strings.Repeat("\x90", 54)},
},
{
"v:\n- A\n- |-\n B\n C\n",
map[string][]string{
"v": {
"A", "B\nC",
},
},
},
{
"v:\n- A\n- >-\n B\n C\n",
map[string][]string{
"v": {
"A", "B C",
},
},
},
{
"a: b\nc: d\n",
struct {
A string
C string `yaml:"c"`
}{
"b", "d",
},
},
{
"a: 1\nb: 2\n",
struct {
A int
B int `yaml:"-"`
}{
1, 0,
},
},
{
"a: 1\nb: 2\n",
struct {
A int
Child `yaml:",inline"`
}{
1,
Child{
B: 2,
C: 0,
},
},
},
// Anchors and aliases.
{
"a: &x 1\nb: &y 2\nc: *x\nd: *y\n",
struct{ A, B, C, D int }{1, 2, 1, 2},
},
{
"a: &a {c: 1}\nb: *a\n",
struct {
A, B struct {
C int
}
}{struct{ C int }{1}, struct{ C int }{1}},
},
{
"a: &a [1, 2]\nb: *a\n",
struct{ B []int }{[]int{1, 2}},
},
{
"tags:\n- hello-world\na: foo",
struct {
Tags []string
A string
}{Tags: []string{"hello-world"}, A: "foo"},
},
{
"",
(*struct{})(nil),
},
{
"{}", struct{}{},
},
{
"v: /a/{b}",
map[string]string{"v": "/a/{b}"},
},
{
"v: 1[]{},!%?&*",
map[string]string{"v": "1[]{},!%?&*"},
},
{
"v: user's item",
map[string]string{"v": "user's item"},
},
{
"v: [1,[2,[3,[4,5],6],7],8]",
map[string]interface{}{
"v": []interface{}{
1,
[]interface{}{
2,
[]interface{}{
3,
[]int{4, 5},
6,
},
7,
},
8,
},
},
},
{
"v: {a: {b: {c: {d: e},f: g},h: i},j: k}",
map[string]interface{}{
"v": map[string]interface{}{
"a": map[string]interface{}{
"b": map[string]interface{}{
"c": map[string]string{
"d": "e",
},
"f": "g",
},
"h": "i",
},
"j": "k",
},
},
},
{
`---
- a:
b:
- c: d
`,
[]map[string]interface{}{
{
"a": map[string]interface{}{
"b": nil,
},
},
{
"c": "d",
},
},
},
{
`---
a:
b:
c: d
`,
map[string]interface{}{
"a": map[string]interface{}{
"b": nil,
},
"c": "d",
},
},
{
`---
a:
b:
c:
`,
map[string]interface{}{
"a": nil,
"b": nil,
"c": nil,
},
},
{
`---
a: go test ./...
b:
c:
`,
map[string]interface{}{
"a": "go test ./...",
"b": nil,
"c": nil,
},
},
{
`---
a: |
hello
...
world
b:
c:
`,
map[string]interface{}{
"a": "hello\n...\nworld\n",
"b": nil,
"c": nil,
},
},
// Multi bytes
{
"v: あいうえお\nv2: かきくけこ",
map[string]string{"v": "あいうえお", "v2": "かきくけこ"},
},
}
for _, test := range tests {
t.Run(test.source, func(t *testing.T) {
buf := bytes.NewBufferString(test.source)
dec := yaml.NewDecoder(buf)
typ := reflect.ValueOf(test.value).Type()
value := reflect.New(typ)
if err := dec.Decode(value.Interface()); err != nil {
if err == io.EOF {
return
}
t.Fatalf("%s: %+v", test.source, err)
}
actual := fmt.Sprintf("%+v", value.Elem().Interface())
expect := fmt.Sprintf("%+v", test.value)
if actual != expect {
t.Fatalf("failed to test [%s], actual=[%s], expect=[%s]", test.source, actual, expect)
}
})
}
}
func TestDecoder_TypeConversionError(t *testing.T) {
t.Run("type conversion for struct", func(t *testing.T) {
type T struct {
A int
B uint
C float32
D bool
}
type U struct {
*T `yaml:",inline"`
}
t.Run("string to int", func(t *testing.T) {
var v T
err := yaml.Unmarshal([]byte(`a: str`), &v)
if err == nil {
t.Fatal("expected to error")
}
msg := "cannot unmarshal string into Go struct field T.A of type int"
if err.Error() != msg {
t.Fatalf("unexpected error message: %s. expect: %s", err.Error(), msg)
}
})
t.Run("string to bool", func(t *testing.T) {
var v T
err := yaml.Unmarshal([]byte(`d: str`), &v)
if err == nil {
t.Fatal("expected to error")
}
msg := "cannot unmarshal string into Go struct field T.D of type bool"
if err.Error() != msg {
t.Fatalf("unexpected error message: %s. expect: %s", err.Error(), msg)
}
})
t.Run("string to int at inline", func(t *testing.T) {
var v U
err := yaml.Unmarshal([]byte(`a: str`), &v)
if err == nil {
t.Fatal("expected to error")
}
msg := "cannot unmarshal string into Go struct field U.T.A of type int"
if err.Error() != msg {
t.Fatalf("unexpected error message: %s. expect: %s", err.Error(), msg)
}
})
})
t.Run("type conversion for array", func(t *testing.T) {
t.Run("string to int", func(t *testing.T) {
var v map[string][]int
err := yaml.Unmarshal([]byte(`v: [A,1,C]`), &v)
if err == nil {
t.Fatal("expected to error")
}
msg := "cannot unmarshal string into Go value of type int"
if err.Error() != msg {
t.Fatalf("unexpected error message: %s. expect: %s", err.Error(), msg)
}
if len(v) == 0 || len(v["v"]) == 0 {
t.Fatal("failed to decode value")
}
if v["v"][0] != 1 {
t.Fatal("failed to decode value")
}
})
t.Run("string to int", func(t *testing.T) {
var v map[string][]int
err := yaml.Unmarshal([]byte("v:\n - A\n - 1\n - C"), &v)
if err == nil {
t.Fatal("expected to error")
}
msg := "cannot unmarshal string into Go value of type int"
if err.Error() != msg {
t.Fatalf("unexpected error message: %s. expect: %s", err.Error(), msg)
}
if len(v) == 0 || len(v["v"]) == 0 {
t.Fatal("failed to decode value")
}
if v["v"][0] != 1 {
t.Fatal("failed to decode value")
}
})
})
t.Run("overflow error", func(t *testing.T) {
t.Run("negative number to uint", func(t *testing.T) {
var v map[string]uint
err := yaml.Unmarshal([]byte("v: -42"), &v)
if err == nil {
t.Fatal("expected to error")
}
msg := "cannot unmarshal -42 into Go value of type uint ( overflow )"
if err.Error() != msg {
t.Fatalf("unexpected error message: %s. expect: %s", err.Error(), msg)
}
if v["v"] != 0 {
t.Fatal("failed to decode value")
}
})
t.Run("negative number to uint64", func(t *testing.T) {
var v map[string]uint64
err := yaml.Unmarshal([]byte("v: -4294967296"), &v)
if err == nil {
t.Fatal("expected to error")
}
msg := "cannot unmarshal -4294967296 into Go value of type uint64 ( overflow )"
if err.Error() != msg {
t.Fatalf("unexpected error message: %s. expect: %s", err.Error(), msg)
}
if v["v"] != 0 {
t.Fatal("failed to decode value")
}
})
t.Run("larger number for int32", func(t *testing.T) {
var v map[string]int32
err := yaml.Unmarshal([]byte("v: 4294967297"), &v)
if err == nil {
t.Fatal("expected to error")
}
msg := "cannot unmarshal 4294967297 into Go value of type int32 ( overflow )"
if err.Error() != msg {
t.Fatalf("unexpected error message: %s. expect: %s", err.Error(), msg)
}
if v["v"] != 0 {
t.Fatal("failed to decode value")
}
})
t.Run("larger number for int8", func(t *testing.T) {
var v map[string]int8
err := yaml.Unmarshal([]byte("v: 128"), &v)
if err == nil {
t.Fatal("expected to error")
}
msg := "cannot unmarshal 128 into Go value of type int8 ( overflow )"
if err.Error() != msg {
t.Fatalf("unexpected error message: %s. expect: %s", err.Error(), msg)
}
if v["v"] != 0 {
t.Fatal("failed to decode value")
}
})
})
t.Run("type conversion for time", func(t *testing.T) {
type T struct {
A time.Time
B time.Duration
}
t.Run("int to time", func(t *testing.T) {
var v T
err := yaml.Unmarshal([]byte(`a: 123`), &v)
if err == nil {
t.Fatal("expected to error")
}
msg := "cannot unmarshal uint64 into Go struct field T.A of type time.Time"
if err.Error() != msg {
t.Fatalf("unexpected error message: %s. expect: %s", err.Error(), msg)
}
})
t.Run("string to duration", func(t *testing.T) {
var v T
err := yaml.Unmarshal([]byte(`b: str`), &v)
if err == nil {
t.Fatal("expected to error")
}
msg := `time: invalid duration "str"`
if err.Error() != msg {
t.Fatalf("unexpected error message: %s. expect: %s", err.Error(), msg)
}
})
t.Run("int to duration", func(t *testing.T) {
var v T
err := yaml.Unmarshal([]byte(`b: 10`), &v)
if err == nil {
t.Fatal("expected to error")
}
msg := "cannot unmarshal uint64 into Go struct field T.B of type time.Duration"
if err.Error() != msg {
t.Fatalf("unexpected error message: %s. expect: %s", err.Error(), msg)
}
})
})
}
func TestDecoder_AnchorReferenceDirs(t *testing.T) {
buf := bytes.NewBufferString("a: *a\n")
dec := yaml.NewDecoder(buf, yaml.ReferenceDirs("testdata"))
var v struct {
A struct {
B int
C string
}
}
if err := dec.Decode(&v); err != nil {
t.Fatalf("%+v", err)
}
if v.A.B != 1 {
t.Fatal("failed to decode by reference dirs")
}
if v.A.C != "hello" {
t.Fatal("failed to decode by reference dirs")
}
}
func TestDecoder_AnchorReferenceDirsRecursive(t *testing.T) {
buf := bytes.NewBufferString("a: *a\n")
dec := yaml.NewDecoder(
buf,
yaml.RecursiveDir(true),
yaml.ReferenceDirs("testdata"),
)
var v struct {
A struct {
B int
C string
}
}
if err := dec.Decode(&v); err != nil {
t.Fatalf("%+v", err)
}
if v.A.B != 1 {
t.Fatal("failed to decode by reference dirs")
}
if v.A.C != "hello" {
t.Fatal("failed to decode by reference dirs")
}
}
func TestDecoder_AnchorFiles(t *testing.T) {
buf := bytes.NewBufferString("a: *a\n")
dec := yaml.NewDecoder(buf, yaml.ReferenceFiles("testdata/anchor.yml"))
var v struct {
A struct {
B int
C string
}
}
if err := dec.Decode(&v); err != nil {
t.Fatalf("%+v", err)
}
if v.A.B != 1 {
t.Fatal("failed to decode by reference dirs")
}
if v.A.C != "hello" {
t.Fatal("failed to decode by reference dirs")
}
}
func TestDecodeWithMergeKey(t *testing.T) {
yml := `
a: &a
b: 1
c: hello
items:
- <<: *a
- <<: *a
c: world
`
type Item struct {
B int
C string
}
type T struct {
Items []*Item
}
buf := bytes.NewBufferString(yml)
dec := yaml.NewDecoder(buf)
var v T
if err := dec.Decode(&v); err != nil {
t.Fatalf("%+v", err)
}
if len(v.Items) != 2 {
t.Fatal("failed to decode with merge key")
}
if v.Items[0].B != 1 || v.Items[0].C != "hello" {
t.Fatal("failed to decode with merge key")
}
if v.Items[1].B != 1 || v.Items[1].C != "world" {
t.Fatal("failed to decode with merge key")
}
t.Run("decode with interface{}", func(t *testing.T) {
buf := bytes.NewBufferString(yml)
dec := yaml.NewDecoder(buf)
var v interface{}
if err := dec.Decode(&v); err != nil {
t.Fatalf("%+v", err)
}
items := v.(map[string]interface{})["items"].([]interface{})
if len(items) != 2 {
t.Fatal("failed to decode with merge key")
}
b0 := items[0].(map[string]interface{})["b"]
if _, ok := b0.(uint64); !ok {
t.Fatal("failed to decode with merge key")
}
if b0.(uint64) != 1 {
t.Fatal("failed to decode with merge key")
}
c0 := items[0].(map[string]interface{})["c"]
if _, ok := c0.(string); !ok {
t.Fatal("failed to decode with merge key")
}
if c0.(string) != "hello" {
t.Fatal("failed to decode with merge key")
}
b1 := items[1].(map[string]interface{})["b"]
if _, ok := b1.(uint64); !ok {
t.Fatal("failed to decode with merge key")
}
if b1.(uint64) != 1 {
t.Fatal("failed to decode with merge key")
}
c1 := items[1].(map[string]interface{})["c"]
if _, ok := c1.(string); !ok {
t.Fatal("failed to decode with merge key")
}
if c1.(string) != "world" {
t.Fatal("failed to decode with merge key")
}
})
t.Run("decode with map", func(t *testing.T) {
var v struct {
Items []map[string]interface{}
}
buf := bytes.NewBufferString(yml)
dec := yaml.NewDecoder(buf)
if err := dec.Decode(&v); err != nil {
t.Fatalf("%+v", err)
}
if len(v.Items) != 2 {
t.Fatal("failed to decode with merge key")
}
b0 := v.Items[0]["b"]
if _, ok := b0.(uint64); !ok {
t.Fatal("failed to decode with merge key")
}
if b0.(uint64) != 1 {
t.Fatal("failed to decode with merge key")
}
c0 := v.Items[0]["c"]
if _, ok := c0.(string); !ok {
t.Fatal("failed to decode with merge key")
}
if c0.(string) != "hello" {
t.Fatal("failed to decode with merge key")
}
b1 := v.Items[1]["b"]
if _, ok := b1.(uint64); !ok {
t.Fatal("failed to decode with merge key")
}
if b1.(uint64) != 1 {
t.Fatal("failed to decode with merge key")
}
c1 := v.Items[1]["c"]
if _, ok := c1.(string); !ok {
t.Fatal("failed to decode with merge key")
}
if c1.(string) != "world" {
t.Fatal("failed to decode with merge key")
}
})
}
func TestDecoder_Inline(t *testing.T) {
type Base struct {
A int
B string
}
yml := `---
a: 1
b: hello
c: true
`
var v struct {
*Base `yaml:",inline"`
C bool
}
if err := yaml.NewDecoder(strings.NewReader(yml)).Decode(&v); err != nil {
t.Fatalf("%+v", err)
}
if v.A != 1 {
t.Fatal("failed to decode with inline key")
}
if v.B != "hello" {
t.Fatal("failed to decode with inline key")
}
if !v.C {
t.Fatal("failed to decode with inline key")
}
t.Run("multiple inline with strict", func(t *testing.T) {
type Base struct {
A int
B string
}
type Base2 struct {
Base *Base `yaml:",inline"`
}
yml := `---
a: 1
b: hello
`
var v struct {
Base2 *Base2 `yaml:",inline"`
}
if err := yaml.NewDecoder(strings.NewReader(yml), yaml.Strict()).Decode(&v); err != nil {
t.Fatalf("%+v", err)
}
if v.Base2.Base.A != 1 {
t.Fatal("failed to decode with inline key")
}
if v.Base2.Base.B != "hello" {
t.Fatal("failed to decode with inline key")
}
})
}
func TestDecoder_InlineAndConflictKey(t *testing.T) {
type Base struct {
A int
B string
}
yml := `---
a: 1
b: hello
c: true
`
var v struct {
*Base `yaml:",inline"`
A int
C bool
}
if err := yaml.NewDecoder(strings.NewReader(yml)).Decode(&v); err != nil {
t.Fatalf("%+v", err)
}
if v.A != 1 {
t.Fatal("failed to decode with inline key")
}
if v.B != "hello" {
t.Fatal("failed to decode with inline key")
}
if !v.C {
t.Fatal("failed to decode with inline key")
}
if v.Base.A != 0 {
t.Fatal("failed to decode with inline key")
}
}
func TestDecoder_InlineAndWrongTypeStrict(t *testing.T) {
type Base struct {
A int
B string
}
yml := `---
a: notanint
b: hello
c: true
`
var v struct {
*Base `yaml:",inline"`
C bool
}
err := yaml.NewDecoder(strings.NewReader(yml), yaml.Strict()).Decode(&v)
if err == nil {
t.Fatalf("expected error")
}
//TODO: properly check if errors are colored/have source
t.Logf("%s", err)
t.Logf("%s", yaml.FormatError(err, true, false))
t.Logf("%s", yaml.FormatError(err, false, true))
t.Logf("%s", yaml.FormatError(err, true, true))
}
func TestDecoder_InvalidCases(t *testing.T) {
const src = `---
a:
- b
c: d
`
var v struct {
A []string
}
err := yaml.NewDecoder(strings.NewReader(src)).Decode(&v)
if err == nil {
t.Fatalf("expected error")
}
if err.Error() != yaml.FormatError(err, false, true) {
t.Logf("err.Error() = %s", err.Error())
t.Logf("yaml.FormatError(err, false, true) = %s", yaml.FormatError(err, false, true))
t.Fatal(`err.Error() should match yaml.FormatError(err, false, true)`)
}
//TODO: properly check if errors are colored/have source
t.Logf("%s", err)
t.Logf("%s", yaml.FormatError(err, true, false))
t.Logf("%s", yaml.FormatError(err, false, true))
t.Logf("%s", yaml.FormatError(err, true, true))
}
func TestDecoder_JSONTags(t *testing.T) {
var v struct {
A string `json:"a_json"` // no YAML tag
B string `json:"b_json" yaml:"b_yaml"` // both tags
}
const src = `---
a_json: a_json_value
b_json: b_json_value
b_yaml: b_yaml_value
`
if err := yaml.NewDecoder(strings.NewReader(src)).Decode(&v); err != nil {
t.Fatalf(`parsing should succeed: %s`, err)
}
if v.A != "a_json_value" {
t.Fatalf("v.A should be `a_json_value`, got `%s`", v.A)
}
if v.B != "b_yaml_value" {
t.Fatalf("v.B should be `b_yaml_value`, got `%s`", v.B)
}
}
func TestDecoder_DisallowUnknownField(t *testing.T) {
t.Run("different level keys with same name", func(t *testing.T) {
var v struct {
C Child `yaml:"c"`
}
yml := `---
b: 1
c:
b: 1
`
err := yaml.NewDecoder(strings.NewReader(yml), yaml.DisallowUnknownField()).Decode(&v)
if err == nil {
t.Fatalf("error expected")
}
})
t.Run("inline", func(t *testing.T) {
var v struct {
*Child `yaml:",inline"`
A string `yaml:"a"`
}
yml := `---
a: a
b: 1
`
if err := yaml.NewDecoder(strings.NewReader(yml), yaml.DisallowUnknownField()).Decode(&v); err != nil {
t.Fatalf(`parsing should succeed: %s`, err)
}
if v.A != "a" {
t.Fatalf("v.A should be `a`, got `%s`", v.A)
}
if v.B != 1 {
t.Fatalf("v.B should be 1, got %d", v.B)
}
if v.C != 0 {
t.Fatalf("v.C should be 0, got %d", v.C)
}
})
t.Run("list", func(t *testing.T) {
type C struct {
Child `yaml:",inline"`
}
var v struct {
Children []C `yaml:"children"`
}
yml := `---
children:
- b: 1
- b: 2
`
if err := yaml.NewDecoder(strings.NewReader(yml), yaml.DisallowUnknownField()).Decode(&v); err != nil {
t.Fatalf(`parsing should succeed: %s`, err)
}
if len(v.Children) != 2 {
t.Fatalf(`len(v.Children) should be 2, got %d`, len(v.Children))
}
if v.Children[0].B != 1 {
t.Fatalf(`v.Children[0].B should be 1, got %d`, v.Children[0].B)
}
if v.Children[1].B != 2 {
t.Fatalf(`v.Children[1].B should be 2, got %d`, v.Children[1].B)
}
})
}
func TestDecoder_DisallowDuplicateKey(t *testing.T) {
yml := `
a: b
a: c
`
expected := `
[3:1] duplicate key "a"
2 | a: b
> 3 | a: c
^
`
t.Run("map", func(t *testing.T) {
var v map[string]string
err := yaml.NewDecoder(strings.NewReader(yml), yaml.DisallowDuplicateKey()).Decode(&v)
if err == nil {
t.Fatal("decoding should fail")
}
actual := "\n" + err.Error()
if expected != actual {
t.Fatalf("expected:[%s] actual:[%s]", expected, actual)
}
})
t.Run("struct", func(t *testing.T) {
var v struct {
A string
}
err := yaml.NewDecoder(strings.NewReader(yml), yaml.DisallowDuplicateKey()).Decode(&v)
if err == nil {
t.Fatal("decoding should fail")
}
actual := "\n" + err.Error()
if expected != actual {
t.Fatalf("expected:[%s] actual:[%s]", expected, actual)
}
})
}
func TestDecoder_DefaultValues(t *testing.T) {
v := struct {
A string `yaml:"a"`
B string `yaml:"b"`
c string // private
D struct {
E string `yaml:"e"`
F struct {
G string `yaml:"g"`
} `yaml:"f"`
H struct {
I string `yaml:"i"`
} `yaml:",inline"`
} `yaml:"d"`
J struct {
K string `yaml:"k"`
L struct {
M string `yaml:"m"`
} `yaml:"l"`
N struct {
O string `yaml:"o"`
} `yaml:",inline"`
} `yaml:",inline"`
P struct {
Q string `yaml:"q"`
R struct {
S string `yaml:"s"`
} `yaml:"r"`
T struct {
U string `yaml:"u"`
} `yaml:",inline"`
} `yaml:"p"`
V struct {
W string `yaml:"w"`
X struct {
Y string `yaml:"y"`
} `yaml:"x"`
Z struct {
Ä string `yaml:"ä"`
} `yaml:",inline"`
} `yaml:",inline"`
}{
B: "defaultBValue",
c: "defaultCValue",
}
v.D.E = "defaultEValue"
v.D.F.G = "defaultGValue"
v.D.H.I = "defaultIValue"
v.J.K = "defaultKValue"
v.J.L.M = "defaultMValue"
v.J.N.O = "defaultOValue"
v.P.R.S = "defaultSValue"
v.P.T.U = "defaultUValue"
v.V.X.Y = "defaultYValue"
v.V.Z.Ä = "defaultÄValue"
const src = `---
a: a_value
p:
q: q_value
w: w_value
`
if err := yaml.NewDecoder(strings.NewReader(src)).Decode(&v); err != nil {
t.Fatalf(`parsing should succeed: %s`, err)
}
if v.A != "a_value" {
t.Fatalf("v.A should be `a_value`, got `%s`", v.A)
}
if v.B != "defaultBValue" {
t.Fatalf("v.B should be `defaultValue`, got `%s`", v.B)
}
if v.c != "defaultCValue" {
t.Fatalf("v.c should be `defaultCValue`, got `%s`", v.c)
}
if v.D.E != "defaultEValue" {
t.Fatalf("v.D.E should be `defaultEValue`, got `%s`", v.D.E)
}
if v.D.F.G != "defaultGValue" {
t.Fatalf("v.D.F.G should be `defaultGValue`, got `%s`", v.D.F.G)
}
if v.D.H.I != "defaultIValue" {
t.Fatalf("v.D.H.I should be `defaultIValue`, got `%s`", v.D.H.I)
}
if v.J.K != "defaultKValue" {
t.Fatalf("v.J.K should be `defaultKValue`, got `%s`", v.J.K)
}
if v.J.L.M != "defaultMValue" {
t.Fatalf("v.J.L.M should be `defaultMValue`, got `%s`", v.J.L.M)
}
if v.J.N.O != "defaultOValue" {
t.Fatalf("v.J.N.O should be `defaultOValue`, got `%s`", v.J.N.O)
}
if v.P.Q != "q_value" {
t.Fatalf("v.P.Q should be `q_value`, got `%s`", v.P.Q)
}
if v.P.R.S != "defaultSValue" {
t.Fatalf("v.P.R.S should be `defaultSValue`, got `%s`", v.P.R.S)
}
if v.P.T.U != "defaultUValue" {
t.Fatalf("v.P.T.U should be `defaultUValue`, got `%s`", v.P.T.U)
}
if v.V.W != "w_value" {
t.Fatalf("v.V.W should be `w_value`, got `%s`", v.V.W)
}
if v.V.X.Y != "defaultYValue" {
t.Fatalf("v.V.X.Y should be `defaultYValue`, got `%s`", v.V.X.Y)
}
if v.V.Z.Ä != "defaultÄValue" {
t.Fatalf("v.V.Z.Ä should be `defaultÄValue`, got `%s`", v.V.Z.Ä)
}
}
func Example_YAMLTags() {
yml := `---
foo: 1
bar: c
A: 2
B: d
`
var v struct {
A int `yaml:"foo" json:"A"`
B string `yaml:"bar" json:"B"`
}
if err := yaml.Unmarshal([]byte(yml), &v); err != nil {
log.Fatal(err)
}
fmt.Println(v.A)
fmt.Println(v.B)
// OUTPUT:
// 1
// c
}
type useJSONUnmarshalerTest struct {
s string
}
func (t *useJSONUnmarshalerTest) UnmarshalJSON(b []byte) error {
s, err := strconv.Unquote(string(b))
if err != nil {
return err
}
t.s = s
return nil
}
func TestDecoder_UseJSONUnmarshaler(t *testing.T) {
var v useJSONUnmarshalerTest
if err := yaml.UnmarshalWithOptions([]byte(`"a"`), &v, yaml.UseJSONUnmarshaler()); err != nil {
t.Fatal(err)
}
if v.s != "a" {
t.Fatalf("unexpected decoded value: %s", v.s)
}
}
type unmarshalContext struct {
v int
}
func (c *unmarshalContext) UnmarshalYAML(ctx context.Context, b []byte) error {
v, ok := ctx.Value("k").(int)
if !ok {
return fmt.Errorf("cannot get valid context")
}
if v != 1 {
return fmt.Errorf("cannot get valid context")
}
if string(b) != "1" {
return fmt.Errorf("cannot get valid bytes")
}
c.v = v
return nil
}
func Test_UnmarshalerContext(t *testing.T) {
ctx := context.WithValue(context.Background(), "k", 1)
var v unmarshalContext
if err := yaml.UnmarshalContext(ctx, []byte(`1`), &v); err != nil {
t.Fatalf("%+v", err)
}
if v.v != 1 {
t.Fatal("cannot call UnmarshalYAML")
}
}
func TestDecoder_DecodeFromNode(t *testing.T) {
t.Run("has reference", func(t *testing.T) {
str := `
anchor: &map
text: hello
map: *map`
var buf bytes.Buffer
dec := yaml.NewDecoder(&buf)
f, err := parser.ParseBytes([]byte(str), 0)
if err != nil {
t.Fatalf("failed to parse: %s", err)
}
type T struct {
Map map[string]string
}
var v T
if err := dec.DecodeFromNode(f.Docs[0].Body, &v); err != nil {
t.Fatalf("failed to decode: %s", err)
}
actual := fmt.Sprintf("%+v", v)
expect := fmt.Sprintf("%+v", T{map[string]string{"text": "hello"}})
if actual != expect {
t.Fatalf("actual=[%s], expect=[%s]", actual, expect)
}
})
t.Run("with reference option", func(t *testing.T) {
anchor := strings.NewReader(`
map: &map
text: hello`)
var buf bytes.Buffer
dec := yaml.NewDecoder(&buf, yaml.ReferenceReaders(anchor))
f, err := parser.ParseBytes([]byte("map: *map"), 0)
if err != nil {
t.Fatalf("failed to parse: %s", err)
}
type T struct {
Map map[string]string
}
var v T
if err := dec.DecodeFromNode(f.Docs[0].Body, &v); err != nil {
t.Fatalf("failed to decode: %s", err)
}
actual := fmt.Sprintf("%+v", v)
expect := fmt.Sprintf("%+v", T{map[string]string{"text": "hello"}})
if actual != expect {
t.Fatalf("actual=[%s], expect=[%s]", actual, expect)
}
})
t.Run("value is not pointer", func(t *testing.T) {
var buf bytes.Buffer
var v bool
err := yaml.NewDecoder(&buf).DecodeFromNode(nil, v)
if !xerrors.Is(err, errors.ErrDecodeRequiredPointerType) {
t.Fatalf("unexpected error: %s", err)
}
})
}
func Example_JSONTags() {
yml := `---
foo: 1
bar: c
`
var v struct {
A int `json:"foo"`
B string `json:"bar"`
}
if err := yaml.Unmarshal([]byte(yml), &v); err != nil {
log.Fatal(err)
}
fmt.Println(v.A)
fmt.Println(v.B)
// OUTPUT:
// 1
// c
}
func Example_DisallowUnknownField() {
var v struct {
A string `yaml:"simple"`
C string `yaml:"complicated"`
}
const src = `---
simple: string
complecated: string
`
err := yaml.NewDecoder(strings.NewReader(src), yaml.DisallowUnknownField()).Decode(&v)
fmt.Printf("%v\n", err)
// OUTPUT:
// [3:1] unknown field "complecated"
// 1 | ---
// 2 | simple: string
// > 3 | complecated: string
// ^
}
func Example_Unmarshal_Node() {
f, err := parser.ParseBytes([]byte("text: node example"), 0)
if err != nil {
panic(err)
}
var v struct {
Text string `yaml:"text"`
}
if err := yaml.NodeToValue(f.Docs[0].Body, &v); err != nil {
panic(err)
}
fmt.Println(v.Text)
// OUTPUT:
// node example
}
type unmarshalableYAMLStringValue string
func (v *unmarshalableYAMLStringValue) UnmarshalYAML(b []byte) error {
var s string
if err := yaml.Unmarshal(b, &s); err != nil {
return err
}
*v = unmarshalableYAMLStringValue(s)
return nil
}
type unmarshalableTextStringValue string
func (v *unmarshalableTextStringValue) UnmarshalText(b []byte) error {
*v = unmarshalableTextStringValue(string(b))
return nil
}
type unmarshalableStringContainer struct {
A unmarshalableYAMLStringValue `yaml:"a"`
B unmarshalableTextStringValue `yaml:"b"`
}
func TestUnmarshalableString(t *testing.T) {
t.Run("empty string", func(t *testing.T) {
t.Parallel()
yml := `
a: ""
b: ""
`
var container unmarshalableStringContainer
if err := yaml.Unmarshal([]byte(yml), &container); err != nil {
t.Fatalf("failed to unmarshal %v", err)
}
if container.A != "" {
t.Fatalf("expected empty string, but %q is set", container.A)
}
if container.B != "" {
t.Fatalf("expected empty string, but %q is set", container.B)
}
})
t.Run("filled string", func(t *testing.T) {
t.Parallel()
yml := `
a: "aaa"
b: "bbb"
`
var container unmarshalableStringContainer
if err := yaml.Unmarshal([]byte(yml), &container); err != nil {
t.Fatalf("failed to unmarshal %v", err)
}
if container.A != "aaa" {
t.Fatalf("expected \"aaa\", but %q is set", container.A)
}
if container.B != "bbb" {
t.Fatalf("expected \"bbb\", but %q is set", container.B)
}
})
t.Run("single-quoted string", func(t *testing.T) {
t.Parallel()
yml := `
a: 'aaa'
b: 'bbb'
`
var container unmarshalableStringContainer
if err := yaml.Unmarshal([]byte(yml), &container); err != nil {
t.Fatalf("failed to unmarshal %v", err)
}
if container.A != "aaa" {
t.Fatalf("expected \"aaa\", but %q is set", container.A)
}
if container.B != "bbb" {
t.Fatalf("expected \"aaa\", but %q is set", container.B)
}
})
t.Run("literal", func(t *testing.T) {
t.Parallel()
yml := `
a: |
a
b
c
b: |
a
b
c
`
var container unmarshalableStringContainer
if err := yaml.Unmarshal([]byte(yml), &container); err != nil {
t.Fatalf("failed to unmarshal %v", err)
}
if container.A != "a\nb\nc\n" {
t.Fatalf("expected \"a\nb\nc\n\", but %q is set", container.A)
}
if container.B != "a\nb\nc\n" {
t.Fatalf("expected \"a\nb\nc\n\", but %q is set", container.B)
}
})
t.Run("anchor/alias", func(t *testing.T) {
yml := `
a: &x 1
b: *x
c: &y hello
d: *y
`
var v struct {
A, B, C, D unmarshalableTextStringValue
}
if err := yaml.Unmarshal([]byte(yml), &v); err != nil {
t.Fatal(err)
}
if v.A != "1" {
t.Fatal("failed to unmarshal")
}
if v.B != "1" {
t.Fatal("failed to unmarshal")
}
if v.C != "hello" {
t.Fatal("failed to unmarshal")
}
if v.D != "hello" {
t.Fatal("failed to unmarshal")
}
})
t.Run("net.IP", func(t *testing.T) {
yml := `
a: &a 127.0.0.1
b: *a
`
var v struct {
A, B net.IP
}
if err := yaml.Unmarshal([]byte(yml), &v); err != nil {
t.Fatal(err)
}
if v.A.String() != net.IPv4(127, 0, 0, 1).String() {
t.Fatal("failed to unmarshal")
}
if v.B.String() != net.IPv4(127, 0, 0, 1).String() {
t.Fatal("failed to unmarshal")
}
})
}
type unmarshalablePtrStringContainer struct {
V *string `yaml:"value"`
}
func TestUnmarshalablePtrString(t *testing.T) {
t.Run("empty string", func(t *testing.T) {
t.Parallel()
var container unmarshalablePtrStringContainer
if err := yaml.Unmarshal([]byte(`value: ""`), &container); err != nil {
t.Fatalf("failed to unmarshal %v", err)
}
if container.V == nil || *container.V != "" {
t.Fatalf("expected empty string, but %q is set", *container.V)
}
})
t.Run("null", func(t *testing.T) {
t.Parallel()
var container unmarshalablePtrStringContainer
if err := yaml.Unmarshal([]byte(`value: null`), &container); err != nil {
t.Fatalf("failed to unmarshal %v", err)
}
if container.V != (*string)(nil) {
t.Fatalf("expected nil, but %q is set", *container.V)
}
})
}
type unmarshalableIntValue int
func (v *unmarshalableIntValue) UnmarshalYAML(raw []byte) error {
i, err := strconv.Atoi(string(raw))
if err != nil {
return err
}
*v = unmarshalableIntValue(i)
return nil
}
type unmarshalableIntContainer struct {
V unmarshalableIntValue `yaml:"value"`
}
func TestUnmarshalableInt(t *testing.T) {
t.Run("empty int", func(t *testing.T) {
t.Parallel()
var container unmarshalableIntContainer
if err := yaml.Unmarshal([]byte(``), &container); err != nil {
t.Fatalf("failed to unmarshal %v", err)
}
if container.V != 0 {
t.Fatalf("expected empty int, but %d is set", container.V)
}
})
t.Run("filled int", func(t *testing.T) {
t.Parallel()
var container unmarshalableIntContainer
if err := yaml.Unmarshal([]byte(`value: 9`), &container); err != nil {
t.Fatalf("failed to unmarshal %v", err)
}
if container.V != 9 {
t.Fatalf("expected 9, but %d is set", container.V)
}
})
t.Run("filled number", func(t *testing.T) {
t.Parallel()
var container unmarshalableIntContainer
if err := yaml.Unmarshal([]byte(`value: 9`), &container); err != nil {
t.Fatalf("failed to unmarshal %v", err)
}
if container.V != 9 {
t.Fatalf("expected 9, but %d is set", container.V)
}
})
}
type unmarshalablePtrIntContainer struct {
V *int `yaml:"value"`
}
func TestUnmarshalablePtrInt(t *testing.T) {
t.Run("empty int", func(t *testing.T) {
t.Parallel()
var container unmarshalablePtrIntContainer
if err := yaml.Unmarshal([]byte(`value: 0`), &container); err != nil {
t.Fatalf("failed to unmarshal %v", err)
}
if container.V == nil || *container.V != 0 {
t.Fatalf("expected 0, but %q is set", *container.V)
}
})
t.Run("null", func(t *testing.T) {
t.Parallel()
var container unmarshalablePtrIntContainer
if err := yaml.Unmarshal([]byte(`value: null`), &container); err != nil {
t.Fatalf("failed to unmarshal %v", err)
}
if container.V != (*int)(nil) {
t.Fatalf("expected nil, but %q is set", *container.V)
}
})
}
type literalContainer struct {
v string
}
func (c *literalContainer) UnmarshalYAML(v []byte) error {
var lit string
if err := yaml.Unmarshal(v, &lit); err != nil {
return err
}
c.v = lit
return nil
}
func TestDecode_Literal(t *testing.T) {
yml := `---
value: |
{
"key": "value"
}
`
var v map[string]*literalContainer
if err := yaml.Unmarshal([]byte(yml), &v); err != nil {
t.Fatalf("failed to unmarshal %+v", err)
}
if v["value"] == nil {
t.Fatal("failed to unmarshal literal with bytes unmarshaler")
}
if v["value"].v == "" {
t.Fatal("failed to unmarshal literal with bytes unmarshaler")
}
}
func TestDecoder_UseOrderedMap(t *testing.T) {
yml := `
a: b
c: d
e:
f: g
h: i
j: k
`
var v interface{}
if err := yaml.NewDecoder(strings.NewReader(yml), yaml.UseOrderedMap()).Decode(&v); err != nil {
t.Fatalf("%+v", err)
}
if _, ok := v.(yaml.MapSlice); !ok {
t.Fatalf("failed to convert to ordered map: %T", v)
}
bytes, err := yaml.Marshal(v)
if err != nil {
t.Fatalf("%+v", err)
}
if string(yml) != "\n"+string(bytes) {
t.Fatalf("expected:[%s] actual:[%s]", string(yml), "\n"+string(bytes))
}
}
func TestDecoder_Stream(t *testing.T) {
yml := `
---
a: b
c: d
---
e: f
g: h
---
i: j
k: l
`
dec := yaml.NewDecoder(strings.NewReader(yml))
values := []map[string]string{}
for {
var v map[string]string
if err := dec.Decode(&v); err != nil {
if err == io.EOF {
break
}
t.Fatalf("%+v", err)
}
values = append(values, v)
}
if len(values) != 3 {
t.Fatal("failed to stream decoding")
}
if values[0]["a"] != "b" {
t.Fatal("failed to stream decoding")
}
if values[1]["e"] != "f" {
t.Fatal("failed to stream decoding")
}
if values[2]["i"] != "j" {
t.Fatal("failed to stream decoding")
}
}
type unmarshalYAMLWithAliasString string
func (v *unmarshalYAMLWithAliasString) UnmarshalYAML(b []byte) error {
var s string
if err := yaml.Unmarshal(b, &s); err != nil {
return err
}
*v = unmarshalYAMLWithAliasString(s)
return nil
}
type unmarshalYAMLWithAliasMap map[string]interface{}
func (v *unmarshalYAMLWithAliasMap) UnmarshalYAML(b []byte) error {
var m map[string]interface{}
if err := yaml.Unmarshal(b, &m); err != nil {
return err
}
*v = unmarshalYAMLWithAliasMap(m)
return nil
}
func TestDecoder_UnmarshalYAMLWithAlias(t *testing.T) {
yml := `
anchors:
x: &x "\"hello\" \"world\""
map: &y
a: b
c: d
d: *x
a: *x
b:
<<: *y
e: f
`
var v struct {
A unmarshalYAMLWithAliasString
B unmarshalYAMLWithAliasMap
}
if err := yaml.Unmarshal([]byte(yml), &v); err != nil {
t.Fatalf("%+v", err)
}
if v.A != `"hello" "world"` {
t.Fatal("failed to unmarshal with alias")
}
if len(v.B) != 4 {
t.Fatal("failed to unmarshal with alias")
}
if v.B["a"] != "b" {
t.Fatal("failed to unmarshal with alias")
}
if v.B["c"] != "d" {
t.Fatal("failed to unmarshal with alias")
}
if v.B["d"] != `"hello" "world"` {
t.Fatal("failed to unmarshal with alias")
}
}
type unmarshalString string
func (u *unmarshalString) UnmarshalYAML(b []byte) error {
*u = unmarshalString(string(b))
return nil
}
type unmarshalList struct {
v []map[string]unmarshalString
}
func (u *unmarshalList) UnmarshalYAML(b []byte) error {
expected := `
- b: c
d: |
hello
hello
f: g
- h: i`
actual := "\n" + string(b)
if expected != actual {
return xerrors.Errorf("unexpected bytes: expected [%q] but got [%q]", expected, actual)
}
var v []map[string]unmarshalString
if err := yaml.Unmarshal(b, &v); err != nil {
return err
}
u.v = v
return nil
}
func TestDecoder_UnmarshalBytesWithSeparatedList(t *testing.T) {
yml := `
a:
- b: c
d: |
hello
hello
f: g
- h: i
`
var v struct {
A unmarshalList
}
if err := yaml.Unmarshal([]byte(yml), &v); err != nil {
t.Fatal(err)
}
if len(v.A.v) != 2 {
t.Fatalf("failed to unmarshal %+v", v)
}
if len(v.A.v[0]) != 3 {
t.Fatalf("failed to unmarshal %+v", v.A.v[0])
}
if len(v.A.v[1]) != 1 {
t.Fatalf("failed to unmarshal %+v", v.A.v[1])
}
}
func TestDecoder_LiteralWithNewLine(t *testing.T) {
type A struct {
Node string `yaml:"b"`
LastNode string `yaml:"last"`
}
tests := []A{
A{
Node: "hello\nworld",
},
A{
Node: "hello\nworld\n",
},
A{
Node: "hello\nworld\n\n",
},
A{
LastNode: "hello\nworld",
},
A{
LastNode: "hello\nworld\n",
},
A{
LastNode: "hello\nworld\n\n",
},
}
// struct(want) -> Marshal -> Unmarchal -> struct(got)
for _, want := range tests {
bytes, _ := yaml.Marshal(want)
got := A{}
if err := yaml.Unmarshal(bytes, &got); err != nil {
t.Fatal(err)
}
if want.Node != got.Node {
t.Fatalf("expected:%q but got %q", want.Node, got.Node)
}
if want.LastNode != got.LastNode {
t.Fatalf("expected:%q but got %q", want.LastNode, got.LastNode)
}
}
}
func TestDecoder_TabCharacterAtRight(t *testing.T) {
yml := `
- a: [2 , 2]
b: [2 , 2]
c: [2 , 2]`
var v []map[string][]int
if err := yaml.Unmarshal([]byte(yml), &v); err != nil {
t.Fatal(err)
}
if len(v) != 1 {
t.Fatalf("failed to unmarshal %+v", v)
}
if len(v[0]) != 3 {
t.Fatalf("failed to unmarshal %+v", v)
}
}
func TestDecoder_Canonical(t *testing.T) {
yml := `
!!map {
? !!str "explicit":!!str "entry",
? !!str "implicit" : !!str "entry",
? !!null "" : !!null "",
}
`
var v interface{}
if err := yaml.Unmarshal([]byte(yml), &v); err != nil {
t.Fatalf("%+v", err)
}
m, ok := v.(map[string]interface{})
if !ok {
t.Fatalf("failed to decode canonical yaml: %+v", v)
}
if m["explicit"] != "entry" {
t.Fatalf("failed to decode canonical yaml: %+v", m)
}
if m["implicit"] != "entry" {
t.Fatalf("failed to decode canonical yaml: %+v", m)
}
if m["null"] != nil {
t.Fatalf("failed to decode canonical yaml: %+v", m)
}
}
func TestDecoder_DecodeFromFile(t *testing.T) {
yml := `
a: b
c: d
`
file, err := parser.ParseBytes([]byte(yml), 0)
if err != nil {
t.Fatal(err)
}
var v map[string]string
if err := yaml.NewDecoder(file).Decode(&v); err != nil {
t.Fatal(err)
}
if len(v) != 2 {
t.Fatal("failed to decode from ast.File")
}
if v["a"] != "b" {
t.Fatal("failed to decode from ast.File")
}
if v["c"] != "d" {
t.Fatal("failed to decode from ast.File")
}
}
func TestDecoder_DecodeWithNode(t *testing.T) {
t.Run("abstract node", func(t *testing.T) {
type T struct {
Text ast.Node `yaml:"text"`
}
var v T
if err := yaml.Unmarshal([]byte(`text: hello`), &v); err != nil {
t.Fatalf("%+v", err)
}
expected := "hello"
got := v.Text.String()
if expected != got {
t.Fatalf("failed to decode to ast.Node: expected %s but got %s", expected, got)
}
})
t.Run("concrete node", func(t *testing.T) {
type T struct {
Text *ast.StringNode `yaml:"text"`
}
var v T
if err := yaml.Unmarshal([]byte(`text: hello`), &v); err != nil {
t.Fatalf("%+v", err)
}
expected := "hello"
got := v.Text.String()
if expected != got {
t.Fatalf("failed to decode to ast.Node: expected %s but got %s", expected, got)
}
})
}
func TestRoundtripAnchorAlias(t *testing.T) {
t.Run("irreversible", func(t *testing.T) {
type foo struct {
K1 string
K2 string
}
type bar struct {
K1 string
K3 string
}
type doc struct {
Foo foo
Bar bar
}
yml := `
foo:
<<: &test-anchor
k1: "One"
k2: "Two"
bar:
<<: *test-anchor
k3: "Three"
`
var v doc
if err := yaml.Unmarshal([]byte(yml), &v); err != nil {
t.Fatalf("%+v", err)
}
bytes, err := yaml.Marshal(v)
if err != nil {
t.Fatalf("%+v", err)
}
expected := `
foo:
k1: One
k2: Two
bar:
k1: One
k3: Three
`
got := "\n" + string(bytes)
if expected != got {
t.Fatalf("expected:[%s] but got [%s]", expected, got)
}
})
t.Run("reversible", func(t *testing.T) {
type TestAnchor struct {
K1 string
}
type foo struct {
*TestAnchor `yaml:",inline,alias"`
K2 string
}
type bar struct {
*TestAnchor `yaml:",inline,alias"`
K3 string
}
type doc struct {
TestAnchor *TestAnchor `yaml:"test-anchor,anchor"`
Foo foo
Bar bar
}
yml := `
test-anchor: &test-anchor
k1: One
foo:
<<: *test-anchor
k2: Two
bar:
<<: *test-anchor
k3: Three
`
var v doc
if err := yaml.Unmarshal([]byte(yml), &v); err != nil {
t.Fatalf("%+v", err)
}
bytes, err := yaml.Marshal(v)
if err != nil {
t.Fatalf("%+v", err)
}
got := "\n" + string(bytes)
if yml != got {
t.Fatalf("expected:[%s] but got [%s]", yml, got)
}
})
}
golang-github-goccy-go-yaml-1.9.5/encode.go 0000664 0000000 0000000 00000051141 14167531274 0020522 0 ustar 00root root 0000000 0000000 package yaml
import (
"context"
"encoding"
"fmt"
"io"
"math"
"reflect"
"sort"
"strconv"
"strings"
"time"
"github.com/goccy/go-yaml/ast"
"github.com/goccy/go-yaml/internal/errors"
"github.com/goccy/go-yaml/parser"
"github.com/goccy/go-yaml/printer"
"github.com/goccy/go-yaml/token"
"golang.org/x/xerrors"
)
const (
// DefaultIndentSpaces default number of space for indent
DefaultIndentSpaces = 2
)
// Encoder writes YAML values to an output stream.
type Encoder struct {
writer io.Writer
opts []EncodeOption
indent int
indentSequence bool
singleQuote bool
isFlowStyle bool
isJSONStyle bool
useJSONMarshaler bool
anchorCallback func(*ast.AnchorNode, interface{}) error
anchorPtrToNameMap map[uintptr]string
useLiteralStyleIfMultiline bool
commentMap map[*Path]*Comment
line int
column int
offset int
indentNum int
indentLevel int
}
// NewEncoder returns a new encoder that writes to w.
// The Encoder should be closed after use to flush all data to w.
func NewEncoder(w io.Writer, opts ...EncodeOption) *Encoder {
return &Encoder{
writer: w,
opts: opts,
indent: DefaultIndentSpaces,
anchorPtrToNameMap: map[uintptr]string{},
line: 1,
column: 1,
offset: 0,
}
}
// Close closes the encoder by writing any remaining data.
// It does not write a stream terminating string "...".
func (e *Encoder) Close() error {
return nil
}
// Encode writes the YAML encoding of v to the stream.
// If multiple items are encoded to the stream,
// the second and subsequent document will be preceded with a "---" document separator,
// but the first will not.
//
// See the documentation for Marshal for details about the conversion of Go values to YAML.
func (e *Encoder) Encode(v interface{}) error {
return e.EncodeContext(context.Background(), v)
}
// EncodeContext writes the YAML encoding of v to the stream with context.Context.
func (e *Encoder) EncodeContext(ctx context.Context, v interface{}) error {
node, err := e.EncodeToNodeContext(ctx, v)
if err != nil {
return errors.Wrapf(err, "failed to encode to node")
}
if err := e.setCommentByCommentMap(node); err != nil {
return errors.Wrapf(err, "failed to set comment by comment map")
}
var p printer.Printer
e.writer.Write(p.PrintNode(node))
return nil
}
// EncodeToNode convert v to ast.Node.
func (e *Encoder) EncodeToNode(v interface{}) (ast.Node, error) {
return e.EncodeToNodeContext(context.Background(), v)
}
// EncodeToNodeContext convert v to ast.Node with context.Context.
func (e *Encoder) EncodeToNodeContext(ctx context.Context, v interface{}) (ast.Node, error) {
for _, opt := range e.opts {
if err := opt(e); err != nil {
return nil, errors.Wrapf(err, "failed to run option for encoder")
}
}
node, err := e.encodeValue(ctx, reflect.ValueOf(v), 1)
if err != nil {
return nil, errors.Wrapf(err, "failed to encode value")
}
return node, nil
}
func (e *Encoder) setCommentByCommentMap(node ast.Node) error {
if e.commentMap == nil {
return nil
}
for path, comment := range e.commentMap {
n, err := path.FilterNode(node)
if err != nil {
return errors.Wrapf(err, "failed to filter node")
}
comments := []*token.Token{}
for _, text := range comment.Texts {
comments = append(comments, token.New(text, text, nil))
}
commentGroup := ast.CommentGroup(comments)
switch comment.Position {
case CommentLinePosition:
if err := n.SetComment(commentGroup); err != nil {
return errors.Wrapf(err, "failed to set comment")
}
case CommentHeadPosition:
parent := ast.Parent(node, n)
if parent == nil {
return ErrUnsupportedHeadPositionType(node)
}
switch node := parent.(type) {
case *ast.MappingValueNode:
if err := node.SetComment(commentGroup); err != nil {
return errors.Wrapf(err, "failed to set comment")
}
case *ast.MappingNode:
if err := node.SetComment(commentGroup); err != nil {
return errors.Wrapf(err, "failed to set comment")
}
default:
return ErrUnsupportedHeadPositionType(node)
}
default:
return ErrUnknownCommentPositionType
}
}
return nil
}
func (e *Encoder) encodeDocument(doc []byte) (ast.Node, error) {
f, err := parser.ParseBytes(doc, 0)
if err != nil {
return nil, errors.Wrapf(err, "failed to parse yaml")
}
for _, docNode := range f.Docs {
if docNode.Body != nil {
return docNode.Body, nil
}
}
return nil, nil
}
func (e *Encoder) isInvalidValue(v reflect.Value) bool {
if !v.IsValid() {
return true
}
kind := v.Type().Kind()
if kind == reflect.Ptr && v.IsNil() {
return true
}
if kind == reflect.Interface && v.IsNil() {
return true
}
return false
}
type jsonMarshaler interface {
MarshalJSON() ([]byte, error)
}
func (e *Encoder) canEncodeByMarshaler(v reflect.Value) bool {
if !v.CanInterface() {
return false
}
iface := v.Interface()
switch iface.(type) {
case BytesMarshalerContext:
return true
case BytesMarshaler:
return true
case InterfaceMarshalerContext:
return true
case InterfaceMarshaler:
return true
case time.Time:
return true
case time.Duration:
return true
case encoding.TextMarshaler:
return true
case jsonMarshaler:
return e.useJSONMarshaler
}
return false
}
func (e *Encoder) encodeByMarshaler(ctx context.Context, v reflect.Value, column int) (ast.Node, error) {
iface := v.Interface()
if marshaler, ok := iface.(BytesMarshalerContext); ok {
doc, err := marshaler.MarshalYAML(ctx)
if err != nil {
return nil, errors.Wrapf(err, "failed to MarshalYAML")
}
node, err := e.encodeDocument(doc)
if err != nil {
return nil, errors.Wrapf(err, "failed to encode document")
}
return node, nil
}
if marshaler, ok := iface.(BytesMarshaler); ok {
doc, err := marshaler.MarshalYAML()
if err != nil {
return nil, errors.Wrapf(err, "failed to MarshalYAML")
}
node, err := e.encodeDocument(doc)
if err != nil {
return nil, errors.Wrapf(err, "failed to encode document")
}
return node, nil
}
if marshaler, ok := iface.(InterfaceMarshalerContext); ok {
marshalV, err := marshaler.MarshalYAML(ctx)
if err != nil {
return nil, errors.Wrapf(err, "failed to MarshalYAML")
}
return e.encodeValue(ctx, reflect.ValueOf(marshalV), column)
}
if marshaler, ok := iface.(InterfaceMarshaler); ok {
marshalV, err := marshaler.MarshalYAML()
if err != nil {
return nil, errors.Wrapf(err, "failed to MarshalYAML")
}
return e.encodeValue(ctx, reflect.ValueOf(marshalV), column)
}
if t, ok := iface.(time.Time); ok {
return e.encodeTime(t, column), nil
}
if t, ok := iface.(time.Duration); ok {
return e.encodeDuration(t, column), nil
}
if marshaler, ok := iface.(encoding.TextMarshaler); ok {
doc, err := marshaler.MarshalText()
if err != nil {
return nil, errors.Wrapf(err, "failed to MarshalText")
}
node, err := e.encodeDocument(doc)
if err != nil {
return nil, errors.Wrapf(err, "failed to encode document")
}
return node, nil
}
if e.useJSONMarshaler {
if marshaler, ok := iface.(jsonMarshaler); ok {
jsonBytes, err := marshaler.MarshalJSON()
if err != nil {
return nil, errors.Wrapf(err, "failed to MarshalJSON")
}
doc, err := JSONToYAML(jsonBytes)
if err != nil {
return nil, errors.Wrapf(err, "failed to convert json to yaml")
}
node, err := e.encodeDocument(doc)
if err != nil {
return nil, errors.Wrapf(err, "failed to encode document")
}
return node, nil
}
}
return nil, xerrors.Errorf("does not implemented Marshaler")
}
func (e *Encoder) encodeValue(ctx context.Context, v reflect.Value, column int) (ast.Node, error) {
if e.isInvalidValue(v) {
return e.encodeNil(), nil
}
if e.canEncodeByMarshaler(v) {
node, err := e.encodeByMarshaler(ctx, v, column)
if err != nil {
return nil, errors.Wrapf(err, "failed to encode by marshaler")
}
return node, nil
}
switch v.Type().Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return e.encodeInt(v.Int()), nil
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return e.encodeUint(v.Uint()), nil
case reflect.Float32:
return e.encodeFloat(v.Float(), 32), nil
case reflect.Float64:
return e.encodeFloat(v.Float(), 64), nil
case reflect.Ptr:
anchorName := e.anchorPtrToNameMap[v.Pointer()]
if anchorName != "" {
aliasName := anchorName
alias := ast.Alias(token.New("*", "*", e.pos(column)))
alias.Value = ast.String(token.New(aliasName, aliasName, e.pos(column)))
return alias, nil
}
return e.encodeValue(ctx, v.Elem(), column)
case reflect.Interface:
return e.encodeValue(ctx, v.Elem(), column)
case reflect.String:
return e.encodeString(v.String(), column), nil
case reflect.Bool:
return e.encodeBool(v.Bool()), nil
case reflect.Slice:
if mapSlice, ok := v.Interface().(MapSlice); ok {
return e.encodeMapSlice(ctx, mapSlice, column)
}
return e.encodeSlice(ctx, v)
case reflect.Array:
return e.encodeArray(ctx, v)
case reflect.Struct:
if v.CanInterface() {
if mapItem, ok := v.Interface().(MapItem); ok {
return e.encodeMapItem(ctx, mapItem, column)
}
if t, ok := v.Interface().(time.Time); ok {
return e.encodeTime(t, column), nil
}
}
return e.encodeStruct(ctx, v, column)
case reflect.Map:
return e.encodeMap(ctx, v, column), nil
default:
return nil, xerrors.Errorf("unknown value type %s", v.Type().String())
}
return nil, nil
}
func (e *Encoder) pos(column int) *token.Position {
return &token.Position{
Line: e.line,
Column: column,
Offset: e.offset,
IndentNum: e.indentNum,
IndentLevel: e.indentLevel,
}
}
func (e *Encoder) encodeNil() ast.Node {
value := "null"
return ast.Null(token.New(value, value, e.pos(e.column)))
}
func (e *Encoder) encodeInt(v int64) ast.Node {
value := fmt.Sprint(v)
return ast.Integer(token.New(value, value, e.pos(e.column)))
}
func (e *Encoder) encodeUint(v uint64) ast.Node {
value := fmt.Sprint(v)
return ast.Integer(token.New(value, value, e.pos(e.column)))
}
func (e *Encoder) encodeFloat(v float64, bitSize int) ast.Node {
if v == math.Inf(0) {
value := ".inf"
return ast.Infinity(token.New(value, value, e.pos(e.column)))
} else if v == math.Inf(-1) {
value := "-.inf"
return ast.Infinity(token.New(value, value, e.pos(e.column)))
} else if math.IsNaN(v) {
value := ".nan"
return ast.Nan(token.New(value, value, e.pos(e.column)))
}
value := strconv.FormatFloat(v, 'g', -1, bitSize)
if !strings.Contains(value, ".") && !strings.Contains(value, "e") {
// append x.0 suffix to keep float value context
value = fmt.Sprintf("%s.0", value)
}
return ast.Float(token.New(value, value, e.pos(e.column)))
}
func (e *Encoder) isNeedQuoted(v string) bool {
if e.isJSONStyle {
return true
}
if e.useLiteralStyleIfMultiline && strings.ContainsAny(v, "\n\r") {
return false
}
if token.IsNeedQuoted(v) {
return true
}
return false
}
func (e *Encoder) encodeString(v string, column int) ast.Node {
if e.isNeedQuoted(v) {
if e.singleQuote {
v = quoteWith(v, '\'')
} else {
v = strconv.Quote(v)
}
}
return ast.String(token.New(v, v, e.pos(column)))
}
func (e *Encoder) encodeBool(v bool) ast.Node {
value := fmt.Sprint(v)
return ast.Bool(token.New(value, value, e.pos(e.column)))
}
func (e *Encoder) encodeSlice(ctx context.Context, value reflect.Value) (ast.Node, error) {
if e.indentSequence {
e.column += e.indent
}
column := e.column
sequence := ast.Sequence(token.New("-", "-", e.pos(column)), e.isFlowStyle)
for i := 0; i < value.Len(); i++ {
node, err := e.encodeValue(ctx, value.Index(i), column)
if err != nil {
return nil, errors.Wrapf(err, "failed to encode value for slice")
}
sequence.Values = append(sequence.Values, node)
}
if e.indentSequence {
e.column -= e.indent
}
return sequence, nil
}
func (e *Encoder) encodeArray(ctx context.Context, value reflect.Value) (ast.Node, error) {
if e.indentSequence {
e.column += e.indent
}
column := e.column
sequence := ast.Sequence(token.New("-", "-", e.pos(column)), e.isFlowStyle)
for i := 0; i < value.Len(); i++ {
node, err := e.encodeValue(ctx, value.Index(i), column)
if err != nil {
return nil, errors.Wrapf(err, "failed to encode value for array")
}
sequence.Values = append(sequence.Values, node)
}
if e.indentSequence {
e.column -= e.indent
}
return sequence, nil
}
func (e *Encoder) encodeMapItem(ctx context.Context, item MapItem, column int) (*ast.MappingValueNode, error) {
k := reflect.ValueOf(item.Key)
v := reflect.ValueOf(item.Value)
value, err := e.encodeValue(ctx, v, column)
if err != nil {
return nil, errors.Wrapf(err, "failed to encode MapItem")
}
if m, ok := value.(*ast.MappingNode); ok {
m.AddColumn(e.indent)
}
return ast.MappingValue(
token.New("", "", e.pos(column)),
e.encodeString(k.Interface().(string), column),
value,
), nil
}
func (e *Encoder) encodeMapSlice(ctx context.Context, value MapSlice, column int) (ast.Node, error) {
node := ast.Mapping(token.New("", "", e.pos(column)), e.isFlowStyle)
for _, item := range value {
value, err := e.encodeMapItem(ctx, item, column)
if err != nil {
return nil, errors.Wrapf(err, "failed to encode MapItem for MapSlice")
}
node.Values = append(node.Values, value)
}
return node, nil
}
func (e *Encoder) encodeMap(ctx context.Context, value reflect.Value, column int) ast.Node {
node := ast.Mapping(token.New("", "", e.pos(column)), e.isFlowStyle)
keys := make([]interface{}, len(value.MapKeys()))
for i, k := range value.MapKeys() {
keys[i] = k.Interface()
}
sort.Slice(keys, func(i, j int) bool {
return fmt.Sprint(keys[i]) < fmt.Sprint(keys[j])
})
for _, key := range keys {
k := reflect.ValueOf(key)
v := value.MapIndex(k)
value, err := e.encodeValue(ctx, v, column)
if err != nil {
return nil
}
if value.Type() == ast.MappingType || value.Type() == ast.MappingValueType {
value.AddColumn(e.indent)
}
node.Values = append(node.Values, ast.MappingValue(
nil,
e.encodeString(fmt.Sprint(key), column),
value,
))
}
return node
}
// IsZeroer is used to check whether an object is zero to determine
// whether it should be omitted when marshaling with the omitempty flag.
// One notable implementation is time.Time.
type IsZeroer interface {
IsZero() bool
}
func (e *Encoder) isZeroValue(v reflect.Value) bool {
kind := v.Kind()
if z, ok := v.Interface().(IsZeroer); ok {
if (kind == reflect.Ptr || kind == reflect.Interface) && v.IsNil() {
return true
}
return z.IsZero()
}
switch kind {
case reflect.String:
return len(v.String()) == 0
case reflect.Interface, reflect.Ptr:
return v.IsNil()
case reflect.Slice:
return v.Len() == 0
case reflect.Map:
return v.Len() == 0
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return v.Int() == 0
case reflect.Float32, reflect.Float64:
return v.Float() == 0
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return v.Uint() == 0
case reflect.Bool:
return !v.Bool()
case reflect.Struct:
vt := v.Type()
for i := v.NumField() - 1; i >= 0; i-- {
if vt.Field(i).PkgPath != "" {
continue // private field
}
if !e.isZeroValue(v.Field(i)) {
return false
}
}
return true
}
return false
}
func (e *Encoder) encodeTime(v time.Time, column int) ast.Node {
value := v.Format(time.RFC3339Nano)
if e.isJSONStyle {
value = strconv.Quote(value)
}
return ast.String(token.New(value, value, e.pos(column)))
}
func (e *Encoder) encodeDuration(v time.Duration, column int) ast.Node {
value := v.String()
if e.isJSONStyle {
value = strconv.Quote(value)
}
return ast.String(token.New(value, value, e.pos(column)))
}
func (e *Encoder) encodeAnchor(anchorName string, value ast.Node, fieldValue reflect.Value, column int) (ast.Node, error) {
anchorNode := ast.Anchor(token.New("&", "&", e.pos(column)))
anchorNode.Name = ast.String(token.New(anchorName, anchorName, e.pos(column)))
anchorNode.Value = value
if e.anchorCallback != nil {
if err := e.anchorCallback(anchorNode, fieldValue.Interface()); err != nil {
return nil, errors.Wrapf(err, "failed to marshal anchor")
}
if snode, ok := anchorNode.Name.(*ast.StringNode); ok {
anchorName = snode.Value
}
}
if fieldValue.Kind() == reflect.Ptr {
e.anchorPtrToNameMap[fieldValue.Pointer()] = anchorName
}
return anchorNode, nil
}
func (e *Encoder) encodeStruct(ctx context.Context, value reflect.Value, column int) (ast.Node, error) {
node := ast.Mapping(token.New("", "", e.pos(column)), e.isFlowStyle)
structType := value.Type()
structFieldMap, err := structFieldMap(structType)
if err != nil {
return nil, errors.Wrapf(err, "failed to get struct field map")
}
hasInlineAnchorField := false
var inlineAnchorValue reflect.Value
for i := 0; i < value.NumField(); i++ {
field := structType.Field(i)
if isIgnoredStructField(field) {
continue
}
fieldValue := value.FieldByName(field.Name)
structField := structFieldMap[field.Name]
if structField.IsOmitEmpty && e.isZeroValue(fieldValue) {
// omit encoding
continue
}
value, err := e.encodeValue(ctx, fieldValue, column)
if err != nil {
return nil, errors.Wrapf(err, "failed to encode value")
}
if m, ok := value.(*ast.MappingNode); ok {
if !e.isFlowStyle && structField.IsFlow {
m.SetIsFlowStyle(true)
}
value.AddColumn(e.indent)
} else if s, ok := value.(*ast.SequenceNode); ok {
if !e.isFlowStyle && structField.IsFlow {
s.SetIsFlowStyle(true)
}
}
key := e.encodeString(structField.RenderName, column)
switch {
case structField.AnchorName != "":
anchorNode, err := e.encodeAnchor(structField.AnchorName, value, fieldValue, column)
if err != nil {
return nil, errors.Wrapf(err, "failed to encode anchor")
}
value = anchorNode
case structField.IsAutoAlias:
if fieldValue.Kind() != reflect.Ptr {
return nil, xerrors.Errorf(
"%s in struct is not pointer type. but required automatically alias detection",
structField.FieldName,
)
}
anchorName := e.anchorPtrToNameMap[fieldValue.Pointer()]
if anchorName == "" {
return nil, xerrors.Errorf(
"cannot find anchor name from pointer address for automatically alias detection",
)
}
aliasName := anchorName
alias := ast.Alias(token.New("*", "*", e.pos(column)))
alias.Value = ast.String(token.New(aliasName, aliasName, e.pos(column)))
value = alias
if structField.IsInline {
// if both used alias and inline, output `<<: *alias`
key = ast.MergeKey(token.New("<<", "<<", e.pos(column)))
}
case structField.AliasName != "":
aliasName := structField.AliasName
alias := ast.Alias(token.New("*", "*", e.pos(column)))
alias.Value = ast.String(token.New(aliasName, aliasName, e.pos(column)))
value = alias
if structField.IsInline {
// if both used alias and inline, output `<<: *alias`
key = ast.MergeKey(token.New("<<", "<<", e.pos(column)))
}
case structField.IsInline:
isAutoAnchor := structField.IsAutoAnchor
if !hasInlineAnchorField {
hasInlineAnchorField = isAutoAnchor
}
if isAutoAnchor {
inlineAnchorValue = fieldValue
}
mapNode, ok := value.(ast.MapNode)
if !ok {
return nil, xerrors.Errorf("inline value is must be map or struct type")
}
mapIter := mapNode.MapRange()
for mapIter.Next() {
key := mapIter.Key()
value := mapIter.Value()
keyName := key.GetToken().Value
if structFieldMap.isIncludedRenderName(keyName) {
// if declared same key name, skip encoding this field
continue
}
key.AddColumn(-e.indent)
value.AddColumn(-e.indent)
node.Values = append(node.Values, ast.MappingValue(nil, key, value))
}
continue
case structField.IsAutoAnchor:
anchorNode, err := e.encodeAnchor(structField.RenderName, value, fieldValue, column)
if err != nil {
return nil, errors.Wrapf(err, "failed to encode anchor")
}
value = anchorNode
}
node.Values = append(node.Values, ast.MappingValue(nil, key, value))
}
if hasInlineAnchorField {
node.AddColumn(e.indent)
anchorName := "anchor"
anchorNode := ast.Anchor(token.New("&", "&", e.pos(column)))
anchorNode.Name = ast.String(token.New(anchorName, anchorName, e.pos(column)))
anchorNode.Value = node
if e.anchorCallback != nil {
if err := e.anchorCallback(anchorNode, value.Addr().Interface()); err != nil {
return nil, errors.Wrapf(err, "failed to marshal anchor")
}
if snode, ok := anchorNode.Name.(*ast.StringNode); ok {
anchorName = snode.Value
}
}
if inlineAnchorValue.Kind() == reflect.Ptr {
e.anchorPtrToNameMap[inlineAnchorValue.Pointer()] = anchorName
}
return anchorNode, nil
}
return node, nil
}
golang-github-goccy-go-yaml-1.9.5/encode_test.go 0000664 0000000 0000000 00000057662 14167531274 0021577 0 ustar 00root root 0000000 0000000 package yaml_test
import (
"bytes"
"context"
"fmt"
"math"
"reflect"
"strconv"
"testing"
"time"
"github.com/goccy/go-yaml"
"github.com/goccy/go-yaml/ast"
)
var zero = 0
var emptyStr = ""
func TestEncoder(t *testing.T) {
tests := []struct {
source string
value interface{}
options []yaml.EncodeOption
}{
{
"null\n",
(*struct{})(nil),
nil,
},
{
"v: hi\n",
map[string]string{"v": "hi"},
nil,
},
{
"v: \"true\"\n",
map[string]string{"v": "true"},
nil,
},
{
"v: \"false\"\n",
map[string]string{"v": "false"},
nil,
},
{
"v: true\n",
map[string]interface{}{"v": true},
nil,
},
{
"v: false\n",
map[string]bool{"v": false},
nil,
},
{
"v: 10\n",
map[string]int{"v": 10},
nil,
},
{
"v: -10\n",
map[string]int{"v": -10},
nil,
},
{
"v: 4294967296\n",
map[string]int{"v": 4294967296},
nil,
},
{
"v: 0.1\n",
map[string]interface{}{"v": 0.1},
nil,
},
{
"v: 0.99\n",
map[string]float32{"v": 0.99},
nil,
},
{
"v: 0.123456789\n",
map[string]float64{"v": 0.123456789},
nil,
},
{
"v: -0.1\n",
map[string]float64{"v": -0.1},
nil,
},
{
"v: 1.0\n",
map[string]float64{"v": 1.0},
nil,
},
{
"v: 1e+06\n",
map[string]float64{"v": 1000000},
nil,
},
{
"v: .inf\n",
map[string]interface{}{"v": math.Inf(0)},
nil,
},
{
"v: -.inf\n",
map[string]interface{}{"v": math.Inf(-1)},
nil,
},
{
"v: .nan\n",
map[string]interface{}{"v": math.NaN()},
nil,
},
{
"v: null\n",
map[string]interface{}{"v": nil},
nil,
},
{
"v: \"\"\n",
map[string]string{"v": ""},
nil,
},
{
"v:\n- A\n- B\n",
map[string][]string{"v": {"A", "B"}},
nil,
},
{
"v:\n - A\n - B\n",
map[string][]string{"v": {"A", "B"}},
[]yaml.EncodeOption{
yaml.IndentSequence(true),
},
},
{
"v:\n- A\n- B\n",
map[string][2]string{"v": {"A", "B"}},
nil,
},
{
"v:\n - A\n - B\n",
map[string][2]string{"v": {"A", "B"}},
[]yaml.EncodeOption{
yaml.IndentSequence(true),
},
},
{
"a: -\n",
map[string]string{"a": "-"},
nil,
},
{
"123\n",
123,
nil,
},
{
"hello: world\n",
map[string]string{"hello": "world"},
nil,
},
{
"hello: |\n hello\n world\n",
map[string]string{"hello": "hello\nworld\n"},
nil,
},
{
"hello: |-\n hello\n world\n",
map[string]string{"hello": "hello\nworld"},
nil,
},
{
"hello: |+\n hello\n world\n\n",
map[string]string{"hello": "hello\nworld\n\n"},
nil,
},
{
"hello:\n hello: |\n hello\n world\n",
map[string]map[string]string{"hello": {"hello": "hello\nworld\n"}},
nil,
},
{
"hello: |\r hello\r world\n",
map[string]string{"hello": "hello\rworld\r"},
nil,
},
{
"hello: |\r\n hello\r\n world\n",
map[string]string{"hello": "hello\r\nworld\r\n"},
nil,
},
{
"v: |-\n username: hello\n password: hello123\n",
map[string]interface{}{"v": "username: hello\npassword: hello123"},
[]yaml.EncodeOption{
yaml.UseLiteralStyleIfMultiline(true),
},
},
{
"v: |-\n # comment\n username: hello\n password: hello123\n",
map[string]interface{}{"v": "# comment\nusername: hello\npassword: hello123"},
[]yaml.EncodeOption{
yaml.UseLiteralStyleIfMultiline(true),
},
},
{
"v: \"# comment\\nusername: hello\\npassword: hello123\"\n",
map[string]interface{}{"v": "# comment\nusername: hello\npassword: hello123"},
[]yaml.EncodeOption{
yaml.UseLiteralStyleIfMultiline(false),
},
},
{
"v:\n- A\n- 1\n- B:\n - 2\n - 3\n",
map[string]interface{}{
"v": []interface{}{
"A",
1,
map[string][]int{
"B": {2, 3},
},
},
},
nil,
},
{
"v:\n - A\n - 1\n - B:\n - 2\n - 3\n - 2\n",
map[string]interface{}{
"v": []interface{}{
"A",
1,
map[string][]int{
"B": {2, 3},
},
2,
},
},
[]yaml.EncodeOption{
yaml.IndentSequence(true),
},
},
{
"a:\n b: c\n",
map[string]interface{}{
"a": map[string]string{
"b": "c",
},
},
nil,
},
{
"t2: 2018-01-09T10:40:47Z\nt4: 2098-01-09T10:40:47Z\n",
map[string]string{
"t2": "2018-01-09T10:40:47Z",
"t4": "2098-01-09T10:40:47Z",
},
nil,
},
{
"a:\n b: c\n d: e\n",
map[string]interface{}{
"a": map[string]string{
"b": "c",
"d": "e",
},
},
nil,
},
{
"a: 3s\n",
map[string]string{
"a": "3s",
},
nil,
},
{
"a: \n",
map[string]string{"a": ""},
nil,
},
{
"a: \"1:1\"\n",
map[string]string{"a": "1:1"},
nil,
},
{
"a: 1.2.3.4\n",
map[string]string{"a": "1.2.3.4"},
nil,
},
{
"a: \"b: c\"\n",
map[string]string{"a": "b: c"},
nil,
},
{
"a: \"Hello #comment\"\n",
map[string]string{"a": "Hello #comment"},
nil,
},
{
"a: 100.5\n",
map[string]interface{}{
"a": 100.5,
},
nil,
},
{
"a: \"\\\\0\"\n",
map[string]string{"a": "\\0"},
nil,
},
{
"a: 1\nb: 2\nc: 3\nd: 4\nsub:\n e: 5\n",
map[string]interface{}{
"a": 1,
"b": 2,
"c": 3,
"d": 4,
"sub": map[string]int{
"e": 5,
},
},
nil,
},
{
"a: 1\nb: []\n",
struct {
A int
B []string
}{
1, ([]string)(nil),
},
nil,
},
{
"a: 1\nb: []\n",
struct {
A int
B []string
}{
1, []string{},
},
nil,
},
{
"a: {}\n",
struct {
A map[string]interface{}
}{
map[string]interface{}{},
},
nil,
},
{
"a: b\nc: d\n",
struct {
A string
C string `yaml:"c"`
}{
"b", "d",
},
nil,
},
{
"a: 1\n",
struct {
A int
B int `yaml:"-"`
}{
1, 0,
},
nil,
},
{
"a: \"\"\n",
struct {
A string
}{
"",
},
nil,
},
{
"a: null\n",
struct {
A *string
}{
nil,
},
nil,
},
{
"a: \"\"\n",
struct {
A *string
}{
&emptyStr,
},
nil,
},
{
"a: null\n",
struct {
A *int
}{
nil,
},
nil,
},
{
"a: 0\n",
struct {
A *int
}{
&zero,
},
nil,
},
// Conditional flag
{
"a: 1\n",
struct {
A int `yaml:"a,omitempty"`
B int `yaml:"b,omitempty"`
}{1, 0},
nil,
},
{
"{}\n",
struct {
A int `yaml:"a,omitempty"`
B int `yaml:"b,omitempty"`
}{0, 0},
nil,
},
{
"a:\n y: \"\"\n",
struct {
A *struct {
X string `yaml:"x,omitempty"`
Y string
}
}{&struct {
X string `yaml:"x,omitempty"`
Y string
}{}},
nil,
},
{
"a: {}\n",
struct {
A *struct {
X string `yaml:"x,omitempty"`
Y string `yaml:"y,omitempty"`
}
}{&struct {
X string `yaml:"x,omitempty"`
Y string `yaml:"y,omitempty"`
}{}},
nil,
},
{
"a: {x: 1}\n",
struct {
A *struct{ X, y int } `yaml:"a,omitempty,flow"`
}{&struct{ X, y int }{1, 2}},
nil,
},
{
"{}\n",
struct {
A *struct{ X, y int } `yaml:"a,omitempty,flow"`
}{nil},
nil,
},
{
"a: {x: 0}\n",
struct {
A *struct{ X, y int } `yaml:"a,omitempty,flow"`
}{&struct{ X, y int }{}},
nil,
},
{
"a: {x: 1}\n",
struct {
A struct{ X, y int } `yaml:"a,omitempty,flow"`
}{struct{ X, y int }{1, 2}},
nil,
},
{
"{}\n",
struct {
A struct{ X, y int } `yaml:"a,omitempty,flow"`
}{struct{ X, y int }{0, 1}},
nil,
},
{
"a: 1.0\n",
struct {
A float64 `yaml:"a,omitempty"`
B float64 `yaml:"b,omitempty"`
}{1, 0},
nil,
},
{
"a: 1\n",
struct {
A int
B []string `yaml:"b,omitempty"`
}{
1, []string{},
},
nil,
},
// Flow flag
{
"a: [1, 2]\n",
struct {
A []int `yaml:"a,flow"`
}{[]int{1, 2}},
nil,
},
{
"a: {b: c, d: e}\n",
&struct {
A map[string]string `yaml:"a,flow"`
}{map[string]string{"b": "c", "d": "e"}},
nil,
},
{
"a: {b: c, d: e}\n",
struct {
A struct {
B, D string
} `yaml:"a,flow"`
}{struct{ B, D string }{"c", "e"}},
nil,
},
// Multi bytes
{
"v: あいうえお\nv2: かきくけこ\n",
map[string]string{"v": "あいうえお", "v2": "かきくけこ"},
nil,
},
// time value
{
"v: 0001-01-01T00:00:00Z\n",
map[string]time.Time{"v": time.Time{}},
nil,
},
{
"v: 0001-01-01T00:00:00Z\n",
map[string]*time.Time{"v": &time.Time{}},
nil,
},
{
"v: null\n",
map[string]*time.Time{"v": nil},
nil,
},
{
"v: 30s\n",
map[string]time.Duration{"v": 30 * time.Second},
nil,
},
// Quote style
{
`v: '\'a\'b'` + "\n",
map[string]string{"v": `'a'b`},
[]yaml.EncodeOption{
yaml.UseSingleQuote(true),
},
},
{
`v: "'a'b"` + "\n",
map[string]string{"v": `'a'b`},
[]yaml.EncodeOption{
yaml.UseSingleQuote(false),
},
},
}
for _, test := range tests {
var buf bytes.Buffer
enc := yaml.NewEncoder(&buf, test.options...)
if err := enc.Encode(test.value); err != nil {
t.Fatalf("%+v", err)
}
if test.source != buf.String() {
t.Fatalf("expect = [%s], actual = [%s]", test.source, buf.String())
}
}
}
func TestEncodeStructIncludeMap(t *testing.T) {
type U struct {
M map[string]string
}
type T struct {
A U
}
bytes, err := yaml.Marshal(T{
A: U{
M: map[string]string{"x": "y"},
},
})
if err != nil {
t.Fatalf("%+v", err)
}
expect := "a:\n m:\n x: y\n"
actual := string(bytes)
if actual != expect {
t.Fatalf("unexpected output. expect:[%s] actual:[%s]", expect, actual)
}
}
func TestEncodeDefinedTypeKeyMap(t *testing.T) {
type K string
type U struct {
M map[K]string
}
bytes, err := yaml.Marshal(U{
M: map[K]string{K("x"): "y"},
})
if err != nil {
t.Fatalf("%+v", err)
}
expect := "m:\n x: y\n"
actual := string(bytes)
if actual != expect {
t.Fatalf("unexpected output. expect:[%s] actual:[%s]", expect, actual)
}
}
func TestEncodeWithAnchorAndAlias(t *testing.T) {
var buf bytes.Buffer
enc := yaml.NewEncoder(&buf)
type T struct {
A int
B string
}
var v struct {
A *T `yaml:"a,anchor=c"`
B *T `yaml:"b,alias=c"`
}
v.A = &T{A: 1, B: "hello"}
v.B = v.A
if err := enc.Encode(v); err != nil {
t.Fatalf("%+v", err)
}
expect := "a: &c\n a: 1\n b: hello\nb: *c\n"
if expect != buf.String() {
t.Fatalf("expect = [%s], actual = [%s]", expect, buf.String())
}
}
func TestEncodeWithAutoAlias(t *testing.T) {
var buf bytes.Buffer
enc := yaml.NewEncoder(&buf)
type T struct {
I int
S string
}
var v struct {
A *T `yaml:"a,anchor=a"`
B *T `yaml:"b,anchor=b"`
C *T `yaml:"c,alias"`
D *T `yaml:"d,alias"`
}
v.A = &T{I: 1, S: "hello"}
v.B = &T{I: 2, S: "world"}
v.C = v.A
v.D = v.B
if err := enc.Encode(v); err != nil {
t.Fatalf("%+v", err)
}
expect := `a: &a
i: 1
s: hello
b: &b
i: 2
s: world
c: *a
d: *b
`
if expect != buf.String() {
t.Fatalf("expect = [%s], actual = [%s]", expect, buf.String())
}
}
func TestEncodeWithImplicitAnchorAndAlias(t *testing.T) {
var buf bytes.Buffer
enc := yaml.NewEncoder(&buf)
type T struct {
I int
S string
}
var v struct {
A *T `yaml:"a,anchor"`
B *T `yaml:"b,anchor"`
C *T `yaml:"c,alias"`
D *T `yaml:"d,alias"`
}
v.A = &T{I: 1, S: "hello"}
v.B = &T{I: 2, S: "world"}
v.C = v.A
v.D = v.B
if err := enc.Encode(v); err != nil {
t.Fatalf("%+v", err)
}
expect := `a: &a
i: 1
s: hello
b: &b
i: 2
s: world
c: *a
d: *b
`
if expect != buf.String() {
t.Fatalf("expect = [%s], actual = [%s]", expect, buf.String())
}
}
func TestEncodeWithMerge(t *testing.T) {
type Person struct {
*Person `yaml:",omitempty,inline,alias"`
Name string `yaml:",omitempty"`
Age int `yaml:",omitempty"`
}
defaultPerson := &Person{
Name: "John Smith",
Age: 20,
}
people := []*Person{
{
Person: defaultPerson,
Name: "Ken",
Age: 10,
},
{
Person: defaultPerson,
},
}
var doc struct {
Default *Person `yaml:"default,anchor"`
People []*Person `yaml:"people"`
}
doc.Default = defaultPerson
doc.People = people
var buf bytes.Buffer
enc := yaml.NewEncoder(&buf)
if err := enc.Encode(doc); err != nil {
t.Fatalf("%+v", err)
}
expect := `default: &default
name: John Smith
age: 20
people:
- <<: *default
name: Ken
age: 10
- <<: *default
`
if expect != buf.String() {
t.Fatalf("expect = [%s], actual = [%s]", expect, buf.String())
}
}
func TestEncodeWithNestedYAML(t *testing.T) {
// Represents objects containing stringified YAML, and special chars
tests := []struct {
value interface{}
// If true, expects a different result between when using forced literal style or not
expectDifferent bool
}{
{
value: map[string]interface{}{"v": "# comment\nname: hello\npassword: hello123\nspecial: \":ghost:\"\ntext: |\n nested multiline!"},
expectDifferent: true,
},
{
value: map[string]interface{}{"v": "# comment\nusername: hello\npassword: hello123"},
expectDifferent: true,
},
{
value: map[string]interface{}{"v": "# comment\n"},
expectDifferent: true,
},
{
value: map[string]interface{}{"v": "\n"},
},
}
for _, test := range tests {
yamlBytesForced, err := yaml.MarshalWithOptions(test.value, yaml.UseLiteralStyleIfMultiline(true))
if err != nil {
t.Fatalf("%+v", err)
}
// Convert it back for proper equality testing
var unmarshaled interface{}
if err := yaml.Unmarshal(yamlBytesForced, &unmarshaled); err != nil {
t.Fatalf("%+v", err)
}
if !reflect.DeepEqual(test.value, unmarshaled) {
t.Fatalf("expected %v(%T). but actual %v(%T)", test.value, test.value, unmarshaled, unmarshaled)
}
if test.expectDifferent {
yamlBytesNotForced, err := yaml.MarshalWithOptions(test.value)
if err != nil {
t.Fatalf("%+v", err)
}
if string(yamlBytesForced) == string(yamlBytesNotForced) {
t.Fatalf("expected different strings when force literal style is not enabled. forced: %s, not forced: %s", string(yamlBytesForced), string(yamlBytesNotForced))
}
}
}
}
func TestEncoder_Inline(t *testing.T) {
type base struct {
A int
B string
}
var buf bytes.Buffer
enc := yaml.NewEncoder(&buf)
if err := enc.Encode(struct {
*base `yaml:",inline"`
C bool
}{
base: &base{
A: 1,
B: "hello",
},
C: true,
}); err != nil {
t.Fatalf("%+v", err)
}
expect := `
a: 1
b: hello
c: true
`
actual := "\n" + buf.String()
if expect != actual {
t.Fatalf("inline marshal error: expect=[%s] actual=[%s]", expect, actual)
}
}
func TestEncoder_InlineAndConflictKey(t *testing.T) {
type base struct {
A int
B string
}
var buf bytes.Buffer
enc := yaml.NewEncoder(&buf)
if err := enc.Encode(struct {
*base `yaml:",inline"`
A int // conflict
C bool
}{
base: &base{
A: 1,
B: "hello",
},
A: 0, // default value
C: true,
}); err != nil {
t.Fatalf("%+v", err)
}
expect := `
b: hello
a: 0
c: true
`
actual := "\n" + buf.String()
if expect != actual {
t.Fatalf("inline marshal error: expect=[%s] actual=[%s]", expect, actual)
}
}
func TestEncoder_Flow(t *testing.T) {
var buf bytes.Buffer
enc := yaml.NewEncoder(&buf, yaml.Flow(true))
var v struct {
A int
B string
C struct {
D int
E string
}
F []int
}
v.A = 1
v.B = "hello"
v.C.D = 3
v.C.E = "world"
v.F = []int{1, 2}
if err := enc.Encode(v); err != nil {
t.Fatalf("%+v", err)
}
expect := `
{a: 1, b: hello, c: {d: 3, e: world}, f: [1, 2]}
`
actual := "\n" + buf.String()
if expect != actual {
t.Fatalf("flow style marshal error: expect=[%s] actual=[%s]", expect, actual)
}
}
func TestEncoder_FlowRecursive(t *testing.T) {
var v struct {
M map[string][]int `yaml:",flow"`
}
v.M = map[string][]int{
"test": []int{1, 2, 3},
}
var buf bytes.Buffer
if err := yaml.NewEncoder(&buf).Encode(v); err != nil {
t.Fatalf("%+v", err)
}
expect := `
m: {test: [1, 2, 3]}
`
actual := "\n" + buf.String()
if expect != actual {
t.Fatalf("flow style marshal error: expect=[%s] actual=[%s]", expect, actual)
}
}
func TestEncoder_JSON(t *testing.T) {
var buf bytes.Buffer
enc := yaml.NewEncoder(&buf, yaml.JSON())
type st struct {
I int8
S string
F float32
}
if err := enc.Encode(struct {
I int
U uint
S string
F float64
Struct *st
Slice []int
Map map[string]interface{}
Time time.Time
Duration time.Duration
}{
I: -10,
U: 10,
S: "hello",
F: 3.14,
Struct: &st{
I: 2,
S: "world",
F: 1.23,
},
Slice: []int{1, 2, 3, 4, 5},
Map: map[string]interface{}{
"a": 1,
"b": 1.23,
"c": "json",
},
Time: time.Time{},
Duration: 5 * time.Minute,
}); err != nil {
t.Fatalf("%+v", err)
}
expect := `
{"i": -10, "u": 10, "s": "hello", "f": 3.14, "struct": {"i": 2, "s": "world", "f": 1.23}, "slice": [1, 2, 3, 4, 5], "map": {"a": 1, "b": 1.23, "c": "json"}, "time": "0001-01-01T00:00:00Z", "duration": "5m0s"}
`
actual := "\n" + buf.String()
if expect != actual {
t.Fatalf("JSON style marshal error: expect=[%s] actual=[%s]", expect, actual)
}
}
func TestEncoder_MarshalAnchor(t *testing.T) {
type Host struct {
Hostname string
Username string
Password string
}
type HostDecl struct {
Host *Host `yaml:",anchor"`
}
type Queue struct {
Name string `yaml:","`
*Host `yaml:",alias"`
}
var doc struct {
Hosts []*HostDecl `yaml:"hosts"`
Queues []*Queue `yaml:"queues"`
}
host1 := &Host{
Hostname: "host1.example.com",
Username: "userA",
Password: "pass1",
}
host2 := &Host{
Hostname: "host2.example.com",
Username: "userB",
Password: "pass2",
}
doc.Hosts = []*HostDecl{
{
Host: host1,
},
{
Host: host2,
},
}
doc.Queues = []*Queue{
{
Name: "queue",
Host: host1,
}, {
Name: "queue2",
Host: host2,
},
}
hostIdx := 1
opt := yaml.MarshalAnchor(func(anchor *ast.AnchorNode, value interface{}) error {
if _, ok := value.(*Host); ok {
nameNode := anchor.Name.(*ast.StringNode)
nameNode.Value = fmt.Sprintf("host%d", hostIdx)
hostIdx++
}
return nil
})
var buf bytes.Buffer
if err := yaml.NewEncoder(&buf, opt).Encode(doc); err != nil {
t.Fatalf("%+v", err)
}
expect := `
hosts:
- host: &host1
hostname: host1.example.com
username: userA
password: pass1
- host: &host2
hostname: host2.example.com
username: userB
password: pass2
queues:
- name: queue
host: *host1
- name: queue2
host: *host2
`
if "\n"+buf.String() != expect {
t.Fatalf("unexpected output. %s", buf.String())
}
}
type useJSONMarshalerTest struct{}
func (t useJSONMarshalerTest) MarshalJSON() ([]byte, error) {
return []byte(`{"a":[1, 2, 3]}`), nil
}
func TestEncoder_UseJSONMarshaler(t *testing.T) {
got, err := yaml.MarshalWithOptions(useJSONMarshalerTest{}, yaml.UseJSONMarshaler())
if err != nil {
t.Fatal(err)
}
expected := `
a:
- 1
- 2
- 3
`
if expected != "\n"+string(got) {
t.Fatalf("failed to use json marshaler. expected [%q] but got [%q]", expected, string(got))
}
}
func Example_Marshal_Node() {
type T struct {
Text ast.Node `yaml:"text"`
}
stringNode, err := yaml.ValueToNode("node example")
if err != nil {
panic(err)
}
bytes, err := yaml.Marshal(T{Text: stringNode})
if err != nil {
panic(err)
}
fmt.Println(string(bytes))
// OUTPUT:
// text: node example
}
func Example_Marshal_ExplicitAnchorAlias() {
type T struct {
A int
B string
}
var v struct {
C *T `yaml:"c,anchor=x"`
D *T `yaml:"d,alias=x"`
}
v.C = &T{A: 1, B: "hello"}
v.D = v.C
bytes, err := yaml.Marshal(v)
if err != nil {
panic(err)
}
fmt.Println(string(bytes))
// OUTPUT:
// c: &x
// a: 1
// b: hello
// d: *x
}
func Example_Marshal_ImplicitAnchorAlias() {
type T struct {
I int
S string
}
var v struct {
A *T `yaml:"a,anchor"`
B *T `yaml:"b,anchor"`
C *T `yaml:"c,alias"`
D *T `yaml:"d,alias"`
}
v.A = &T{I: 1, S: "hello"}
v.B = &T{I: 2, S: "world"}
v.C = v.A // C has same pointer address to A
v.D = v.B // D has same pointer address to B
bytes, err := yaml.Marshal(v)
if err != nil {
panic(err)
}
fmt.Println(string(bytes))
// OUTPUT:
// a: &a
// i: 1
// s: hello
// b: &b
// i: 2
// s: world
// c: *a
// d: *b
}
type tMarshal []string
func (t *tMarshal) MarshalYAML() ([]byte, error) {
var buf bytes.Buffer
buf.WriteString("tags:\n")
for i, v := range *t {
if i == 0 {
fmt.Fprintf(&buf, "- %s\n", v)
} else {
fmt.Fprintf(&buf, " %s\n", v)
}
}
return buf.Bytes(), nil
}
func Test_Marshaler(t *testing.T) {
const expected = `- hello-world
`
// sanity check
var l []string
if err := yaml.Unmarshal([]byte(expected), &l); err != nil {
t.Fatalf("failed to parse string: %s", err)
}
buf, err := yaml.Marshal(tMarshal{"hello-world"})
if err != nil {
t.Fatalf("failed to marshal: %s", err)
}
if string(buf) != expected {
t.Fatalf("expected '%s', got '%s'", expected, buf)
}
t.Logf("%s", buf)
}
type marshalContext struct{}
func (c *marshalContext) MarshalYAML(ctx context.Context) ([]byte, error) {
v, ok := ctx.Value("k").(int)
if !ok {
return nil, fmt.Errorf("cannot get valid context")
}
if v != 1 {
return nil, fmt.Errorf("cannot get valid context")
}
return []byte("1"), nil
}
func Test_MarshalerContext(t *testing.T) {
ctx := context.WithValue(context.Background(), "k", 1)
bytes, err := yaml.MarshalContext(ctx, &marshalContext{})
if err != nil {
t.Fatalf("%+v", err)
}
if string(bytes) != "1\n" {
t.Fatalf("failed marshal: %q", string(bytes))
}
}
type SlowMarshaler struct {
A string
B int
}
type FastMarshaler struct {
A string
B int
}
type TextMarshaler int64
type TextMarshalerContainer struct {
Field TextMarshaler `yaml:"field"`
}
func (v SlowMarshaler) MarshalYAML() ([]byte, error) {
var buf bytes.Buffer
buf.WriteString("tags:\n")
buf.WriteString("- slow-marshaler\n")
buf.WriteString("a: " + v.A + "\n")
buf.WriteString("b: " + strconv.FormatInt(int64(v.B), 10) + "\n")
return buf.Bytes(), nil
}
func (v FastMarshaler) MarshalYAML() (interface{}, error) {
return yaml.MapSlice{
{"tags", []string{"fast-marshaler"}},
{"a", v.A},
{"b", v.B},
}, nil
}
func (t TextMarshaler) MarshalText() ([]byte, error) {
return []byte(strconv.FormatInt(int64(t), 8)), nil
}
func Example_MarshalYAML() {
var slow SlowMarshaler
slow.A = "Hello slow poke"
slow.B = 100
buf, err := yaml.Marshal(slow)
if err != nil {
panic(err.Error())
}
fmt.Println(string(buf))
var fast FastMarshaler
fast.A = "Hello speed demon"
fast.B = 100
buf, err = yaml.Marshal(fast)
if err != nil {
panic(err.Error())
}
fmt.Println(string(buf))
text := TextMarshalerContainer{
Field: 11,
}
buf, err = yaml.Marshal(text)
if err != nil {
panic(err.Error())
}
fmt.Println(string(buf))
// OUTPUT:
// tags:
// - slow-marshaler
// a: Hello slow poke
// b: 100
//
// tags:
// - fast-marshaler
// a: Hello speed demon
// b: 100
//
// field: 13
}
func TestMarshalIndentWithMultipleText(t *testing.T) {
t.Run("depth1", func(t *testing.T) {
b, err := yaml.MarshalWithOptions(map[string]interface{}{
"key": []string{`line1
line2
line3`},
}, yaml.Indent(2))
if err != nil {
t.Fatal(err)
}
got := string(b)
expected := `key:
- |-
line1
line2
line3
`
if expected != got {
t.Fatalf("failed to encode.\nexpected:\n%s\nbut got:\n%s\n", expected, got)
}
})
t.Run("depth2", func(t *testing.T) {
b, err := yaml.MarshalWithOptions(map[string]interface{}{
"key": map[string]interface{}{
"key2": []string{`line1
line2
line3`},
},
}, yaml.Indent(2))
if err != nil {
t.Fatal(err)
}
got := string(b)
expected := `key:
key2:
- |-
line1
line2
line3
`
if expected != got {
t.Fatalf("failed to encode.\nexpected:\n%s\nbut got:\n%s\n", expected, got)
}
})
}
type bytesMarshaler struct{}
func (b *bytesMarshaler) MarshalYAML() ([]byte, error) {
return yaml.Marshal(map[string]interface{}{"d": "foo"})
}
func TestBytesMarshaler(t *testing.T) {
b, err := yaml.Marshal(map[string]interface{}{
"a": map[string]interface{}{
"b": map[string]interface{}{
"c": &bytesMarshaler{},
},
},
})
if err != nil {
t.Fatal(err)
}
expected := `
a:
b:
c:
d: foo
`
got := "\n" + string(b)
if expected != got {
t.Fatalf("failed to encode. expected %s but got %s", expected, got)
}
}
golang-github-goccy-go-yaml-1.9.5/error.go 0000664 0000000 0000000 00000003420 14167531274 0020413 0 ustar 00root root 0000000 0000000 package yaml
import (
"github.com/goccy/go-yaml/ast"
"golang.org/x/xerrors"
)
var (
ErrInvalidQuery = xerrors.New("invalid query")
ErrInvalidPath = xerrors.New("invalid path instance")
ErrInvalidPathString = xerrors.New("invalid path string")
ErrNotFoundNode = xerrors.New("node not found")
ErrUnknownCommentPositionType = xerrors.New("unknown comment position type")
ErrInvalidCommentMapValue = xerrors.New("invalid comment map value. it must be not nil value")
)
func ErrUnsupportedHeadPositionType(node ast.Node) error {
return xerrors.Errorf("unsupported comment head position for %s", node.Type())
}
// IsInvalidQueryError whether err is ErrInvalidQuery or not.
func IsInvalidQueryError(err error) bool {
return xerrors.Is(err, ErrInvalidQuery)
}
// IsInvalidPathError whether err is ErrInvalidPath or not.
func IsInvalidPathError(err error) bool {
return xerrors.Is(err, ErrInvalidPath)
}
// IsInvalidPathStringError whether err is ErrInvalidPathString or not.
func IsInvalidPathStringError(err error) bool {
return xerrors.Is(err, ErrInvalidPathString)
}
// IsNotFoundNodeError whether err is ErrNotFoundNode or not.
func IsNotFoundNodeError(err error) bool {
return xerrors.Is(err, ErrNotFoundNode)
}
// IsInvalidTokenTypeError whether err is ast.ErrInvalidTokenType or not.
func IsInvalidTokenTypeError(err error) bool {
return xerrors.Is(err, ast.ErrInvalidTokenType)
}
// IsInvalidAnchorNameError whether err is ast.ErrInvalidAnchorName or not.
func IsInvalidAnchorNameError(err error) bool {
return xerrors.Is(err, ast.ErrInvalidAnchorName)
}
// IsInvalidAliasNameError whether err is ast.ErrInvalidAliasName or not.
func IsInvalidAliasNameError(err error) bool {
return xerrors.Is(err, ast.ErrInvalidAliasName)
}
golang-github-goccy-go-yaml-1.9.5/go.mod 0000664 0000000 0000000 00000000345 14167531274 0020044 0 ustar 00root root 0000000 0000000 module github.com/goccy/go-yaml
go 1.12
require (
github.com/fatih/color v1.10.0
github.com/go-playground/validator/v10 v10.4.1
github.com/mattn/go-colorable v0.1.8
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1
)
golang-github-goccy-go-yaml-1.9.5/go.sum 0000664 0000000 0000000 00000007442 14167531274 0020076 0 ustar 00root root 0000000 0000000 github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg=
github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE=
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae h1:/WDfKMnPU+m5M4xB+6x4kaepxRw6jWvR5iDRdvjHgy8=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
golang-github-goccy-go-yaml-1.9.5/internal/ 0000775 0000000 0000000 00000000000 14167531274 0020550 5 ustar 00root root 0000000 0000000 golang-github-goccy-go-yaml-1.9.5/internal/errors/ 0000775 0000000 0000000 00000000000 14167531274 0022064 5 ustar 00root root 0000000 0000000 golang-github-goccy-go-yaml-1.9.5/internal/errors/error.go 0000664 0000000 0000000 00000011072 14167531274 0023545 0 ustar 00root root 0000000 0000000 package errors
import (
"bytes"
"fmt"
"github.com/goccy/go-yaml/printer"
"github.com/goccy/go-yaml/token"
"golang.org/x/xerrors"
)
const (
defaultColorize = false
defaultIncludeSource = true
)
var (
ErrDecodeRequiredPointerType = xerrors.New("required pointer type value")
)
// Wrapf wrap error for stack trace
func Wrapf(err error, msg string, args ...interface{}) error {
return &wrapError{
baseError: &baseError{},
err: xerrors.Errorf(msg, args...),
nextErr: err,
frame: xerrors.Caller(1),
}
}
// ErrSyntax create syntax error instance with message and token
func ErrSyntax(msg string, tk *token.Token) *syntaxError {
return &syntaxError{
baseError: &baseError{},
msg: msg,
token: tk,
frame: xerrors.Caller(1),
}
}
type baseError struct {
state fmt.State
verb rune
}
func (e *baseError) Error() string {
return ""
}
func (e *baseError) chainStateAndVerb(err error) {
wrapErr, ok := err.(*wrapError)
if ok {
wrapErr.state = e.state
wrapErr.verb = e.verb
}
syntaxErr, ok := err.(*syntaxError)
if ok {
syntaxErr.state = e.state
syntaxErr.verb = e.verb
}
}
type wrapError struct {
*baseError
err error
nextErr error
frame xerrors.Frame
}
type myprinter struct {
xerrors.Printer
colored bool
inclSource bool
}
func (e *wrapError) As(target interface{}) bool {
err := e.nextErr
for {
if wrapErr, ok := err.(*wrapError); ok {
err = wrapErr.nextErr
continue
}
break
}
return xerrors.As(err, target)
}
func (e *wrapError) Unwrap() error {
return e.nextErr
}
func (e *wrapError) PrettyPrint(p xerrors.Printer, colored, inclSource bool) error {
return e.FormatError(&myprinter{Printer: p, colored: colored, inclSource: inclSource})
}
func (e *wrapError) FormatError(p xerrors.Printer) error {
if _, ok := p.(*myprinter); !ok {
p = &myprinter{
Printer: p,
colored: defaultColorize,
inclSource: defaultIncludeSource,
}
}
if e.verb == 'v' && e.state.Flag('+') {
// print stack trace for debugging
p.Print(e.err, "\n")
e.frame.Format(p)
e.chainStateAndVerb(e.nextErr)
return e.nextErr
}
err := e.nextErr
for {
if wrapErr, ok := err.(*wrapError); ok {
err = wrapErr.nextErr
continue
}
break
}
e.chainStateAndVerb(err)
if fmtErr, ok := err.(xerrors.Formatter); ok {
fmtErr.FormatError(p)
} else {
p.Print(err)
}
return nil
}
type wrapState struct {
org fmt.State
}
func (s *wrapState) Write(b []byte) (n int, err error) {
return s.org.Write(b)
}
func (s *wrapState) Width() (wid int, ok bool) {
return s.org.Width()
}
func (s *wrapState) Precision() (prec int, ok bool) {
return s.org.Precision()
}
func (s *wrapState) Flag(c int) bool {
// set true to 'printDetail' forced because when p.Detail() is false, xerrors.Printer no output any text
if c == '#' {
// ignore '#' keyword because xerrors.FormatError doesn't set true to printDetail.
// ( see https://github.com/golang/xerrors/blob/master/adaptor.go#L39-L43 )
return false
}
return true
}
func (e *wrapError) Format(state fmt.State, verb rune) {
e.state = state
e.verb = verb
xerrors.FormatError(e, &wrapState{org: state}, verb)
}
func (e *wrapError) Error() string {
var buf bytes.Buffer
e.PrettyPrint(&Sink{&buf}, defaultColorize, defaultIncludeSource)
return buf.String()
}
type syntaxError struct {
*baseError
msg string
token *token.Token
frame xerrors.Frame
}
func (e *syntaxError) PrettyPrint(p xerrors.Printer, colored, inclSource bool) error {
return e.FormatError(&myprinter{Printer: p, colored: colored, inclSource: inclSource})
}
func (e *syntaxError) FormatError(p xerrors.Printer) error {
var pp printer.Printer
var colored, inclSource bool
if mp, ok := p.(*myprinter); ok {
colored = mp.colored
inclSource = mp.inclSource
}
pos := fmt.Sprintf("[%d:%d] ", e.token.Position.Line, e.token.Position.Column)
msg := pp.PrintErrorMessage(fmt.Sprintf("%s%s", pos, e.msg), colored)
if inclSource {
msg += "\n" + pp.PrintErrorToken(e.token, colored)
}
p.Print(msg)
if e.verb == 'v' && e.state.Flag('+') {
// %+v
// print stack trace for debugging
e.frame.Format(p)
}
return nil
}
type PrettyPrinter interface {
PrettyPrint(xerrors.Printer, bool, bool) error
}
type Sink struct{ *bytes.Buffer }
func (es *Sink) Print(args ...interface{}) {
fmt.Fprint(es.Buffer, args...)
}
func (es *Sink) Printf(f string, args ...interface{}) {
fmt.Fprintf(es.Buffer, f, args...)
}
func (es *Sink) Detail() bool {
return false
}
func (e *syntaxError) Error() string {
var buf bytes.Buffer
e.PrettyPrint(&Sink{&buf}, defaultColorize, defaultIncludeSource)
return buf.String()
}
golang-github-goccy-go-yaml-1.9.5/lexer/ 0000775 0000000 0000000 00000000000 14167531274 0020053 5 ustar 00root root 0000000 0000000 golang-github-goccy-go-yaml-1.9.5/lexer/lexer.go 0000664 0000000 0000000 00000000564 14167531274 0021526 0 ustar 00root root 0000000 0000000 package lexer
import (
"io"
"github.com/goccy/go-yaml/scanner"
"github.com/goccy/go-yaml/token"
)
// Tokenize split to token instances from string
func Tokenize(src string) token.Tokens {
var s scanner.Scanner
s.Init(src)
var tokens token.Tokens
for {
subTokens, err := s.Scan()
if err == io.EOF {
break
}
tokens.Add(subTokens...)
}
return tokens
}
golang-github-goccy-go-yaml-1.9.5/lexer/lexer_test.go 0000664 0000000 0000000 00000017731 14167531274 0022571 0 ustar 00root root 0000000 0000000 package lexer_test
import (
"sort"
"strings"
"testing"
"github.com/goccy/go-yaml/lexer"
"github.com/goccy/go-yaml/token"
)
func TestTokenize(t *testing.T) {
sources := []string{
"null\n",
"{}\n",
"v: hi\n",
"v: \"true\"\n",
"v: \"false\"\n",
"v: true\n",
"v: false\n",
"v: 10\n",
"v: -10\n",
"v: 42\n",
"v: 4294967296\n",
"v: \"10\"\n",
"v: 0.1\n",
"v: 0.99\n",
"v: -0.1\n",
"v: .inf\n",
"v: -.inf\n",
"v: .nan\n",
"v: null\n",
"v: \"\"\n",
"v:\n- A\n- B\n",
"v:\n- A\n- |-\n B\n C\n",
"v:\n- A\n- 1\n- B:\n - 2\n - 3\n",
"a:\n b: c\n",
"a: '-'\n",
"123\n",
"hello: world\n",
"a: null\n",
"a: {x: 1}\n",
"a: [1, 2]\n",
"t2: 2018-01-09T10:40:47Z\nt4: 2098-01-09T10:40:47Z\n",
"a: {b: c, d: e}\n",
"a: 3s\n",
"a: \n",
"a: \"1:1\"\n",
"a: \"\\0\"\n",
"a: !!binary gIGC\n",
"a: !!binary |\n " + strings.Repeat("kJCQ", 17) + "kJ\n CQ\n",
"b: 2\na: 1\nd: 4\nc: 3\nsub:\n e: 5\n",
"a: 1.2.3.4\n",
"a: \"2015-02-24T18:19:39Z\"\n",
"a: 'b: c'\n",
"a: 'Hello #comment'\n",
"a: 100.5\n",
"a: bogus\n",
}
for _, src := range sources {
lexer.Tokenize(src).Dump()
}
}
type testToken struct {
line int
column int
value string
}
func TestSingleLineToken_ValueLineColumnPosition(t *testing.T) {
tests := []struct {
name string
src string
expect map[int]string // Column -> Value map.
}{
{
name: "single quote, single value array",
src: "test: ['test']",
expect: map[int]string{
1: "test",
5: ":",
7: "[",
8: "test",
14: "]",
},
},
{
name: "double quote, single value array",
src: `test: ["test"]`,
expect: map[int]string{
1: "test",
5: ":",
7: "[",
8: "test",
14: "]",
},
},
{
name: "no quotes, single value array",
src: "test: [somevalue]",
expect: map[int]string{
1: "test",
5: ":",
7: "[",
8: "somevalue",
17: "]",
},
},
{
name: "single quote, multi value array",
src: "myarr: ['1','2','3', '444' , '55','66' , '77' ]",
expect: map[int]string{
1: "myarr",
6: ":",
8: "[",
9: "1",
12: ",",
13: "2",
16: ",",
17: "3",
20: ",",
22: "444",
28: ",",
30: "55",
34: ",",
35: "66",
40: ",",
43: "77",
49: "]",
},
},
{
name: "double quote, multi value array",
src: `myarr: ["1","2","3", "444" , "55","66" , "77" ]`,
expect: map[int]string{
1: "myarr",
6: ":",
8: "[",
9: "1",
12: ",",
13: "2",
16: ",",
17: "3",
20: ",",
22: "444",
28: ",",
30: "55",
34: ",",
35: "66",
40: ",",
43: "77",
49: "]",
},
},
{
name: "no quote, multi value array",
src: "numbers: [1, 5, 99,100, 3, 7 ]",
expect: map[int]string{
1: "numbers",
8: ":",
10: "[",
11: "1",
12: ",",
14: "5",
15: ",",
17: "99",
19: ",",
20: "100",
23: ",",
25: "3",
26: ",",
28: "7",
30: "]",
},
},
{
name: "double quotes, nested arrays",
src: `Strings: ["1",["2",["3"]]]`,
expect: map[int]string{
1: "Strings",
8: ":",
10: "[",
11: "1",
14: ",",
15: "[",
16: "2",
19: ",",
20: "[",
21: "3",
24: "]",
25: "]",
26: "]",
},
},
{
name: "mixed quotes, nested arrays",
src: `Values: [1,['2',"3",4,["5",6]]]`,
expect: map[int]string{
1: "Values",
7: ":",
9: "[",
10: "1",
11: ",",
12: "[",
13: "2",
16: ",",
17: "3",
20: ",",
21: "4",
22: ",",
23: "[",
24: "5",
27: ",",
28: "6",
29: "]",
30: "]",
31: "]",
},
},
{
name: "double quote, empty array",
src: `Empty: ["", ""]`,
expect: map[int]string{
1: "Empty",
6: ":",
8: "[",
9: "",
11: ",",
13: "",
15: "]",
},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
got := lexer.Tokenize(tc.src)
sort.Slice(got, func(i, j int) bool {
return got[i].Position.Column < got[j].Position.Column
})
var expected []testToken
for k, v := range tc.expect {
tt := testToken{
line: 1,
column: k,
value: v,
}
expected = append(expected, tt)
}
sort.Slice(expected, func(i, j int) bool {
return expected[i].column < expected[j].column
})
if len(got) != len(expected) {
t.Errorf("Tokenize(%s) token count mismatch, expected:%d got:%d", tc.src, len(expected), len(got))
}
for i, tok := range got {
if !tokenMatches(tok, expected[i]) {
t.Errorf("Tokenize(%s) expected:%+v got line:%d column:%d value:%s", tc.src, expected[i], tok.Position.Line, tok.Position.Column, tok.Value)
}
}
})
}
}
func tokenMatches(t *token.Token, e testToken) bool {
return t != nil && t.Position != nil &&
t.Value == e.value &&
t.Position.Line == e.line &&
t.Position.Column == e.column
}
func TestMultiLineToken_ValueLineColumnPosition(t *testing.T) {
tests := []struct {
name string
src string
expect []testToken
}{
{
name: "double quote",
src: `one: "1 2 3 4 5"
two: "1 2
3 4
5"
three: "1 2 3 4
5"`,
expect: []testToken{
{
line: 1,
column: 1,
value: "one",
},
{
line: 1,
column: 4,
value: ":",
},
{
line: 1,
column: 6,
value: "1 2 3 4 5",
},
{
line: 2,
column: 1,
value: "two",
},
{
line: 2,
column: 4,
value: ":",
},
{
line: 2,
column: 6,
value: "1 2 3 4 5",
},
{
line: 5,
column: 1,
value: "three",
},
{
line: 5,
column: 6,
value: ":",
},
{
line: 5,
column: 8,
value: "1 2 3 4 5",
},
},
},
{
name: "single quote in an array",
src: `arr: ['1', 'and
two']
last: 'hello'`,
expect: []testToken{
{
line: 1,
column: 1,
value: "arr",
},
{
line: 1,
column: 4,
value: ":",
},
{
line: 1,
column: 6,
value: "[",
},
{
line: 1,
column: 7,
value: "1",
},
{
line: 1,
column: 10,
value: ",",
},
{
line: 1,
column: 12,
value: "and two",
},
{
line: 2,
column: 5,
value: "]",
},
{
line: 3,
column: 1,
value: "last",
},
{
line: 3,
column: 5,
value: ":",
},
{
line: 3,
column: 7,
value: "hello",
},
},
},
{
name: "single quote and double quote",
src: `foo: "test
bar"
foo2: 'bar2'`,
expect: []testToken{
{
line: 1,
column: 1,
value: "foo",
},
{
line: 1,
column: 4,
value: ":",
},
{
line: 1,
column: 6,
value: "test bar",
},
{
line: 7,
column: 1,
value: "foo2",
},
{
line: 7,
column: 5,
value: ":",
},
{
line: 7,
column: 7,
value: "bar2",
},
},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
got := lexer.Tokenize(tc.src)
sort.Slice(got, func(i, j int) bool {
// sort by line, then column
if got[i].Position.Line < got[j].Position.Line {
return true
} else if got[i].Position.Line == got[j].Position.Line {
return got[i].Position.Column < got[j].Position.Column
}
return false
})
sort.Slice(tc.expect, func(i, j int) bool {
if tc.expect[i].line < tc.expect[j].line {
return true
} else if tc.expect[i].line == tc.expect[j].line {
return tc.expect[i].column < tc.expect[j].column
}
return false
})
if len(got) != len(tc.expect) {
t.Errorf("Tokenize() token count mismatch, expected:%d got:%d", len(tc.expect), len(got))
}
for i, tok := range got {
if !tokenMatches(tok, tc.expect[i]) {
t.Errorf("Tokenize() expected:%+v got line:%d column:%d value:%s", tc.expect[i], tok.Position.Line, tok.Position.Column, tok.Value)
}
}
})
}
}
golang-github-goccy-go-yaml-1.9.5/option.go 0000664 0000000 0000000 00000013576 14167531274 0020607 0 ustar 00root root 0000000 0000000 package yaml
import (
"io"
"github.com/goccy/go-yaml/ast"
)
// DecodeOption functional option type for Decoder
type DecodeOption func(d *Decoder) error
// ReferenceReaders pass to Decoder that reference to anchor defined by passed readers
func ReferenceReaders(readers ...io.Reader) DecodeOption {
return func(d *Decoder) error {
d.referenceReaders = append(d.referenceReaders, readers...)
return nil
}
}
// ReferenceFiles pass to Decoder that reference to anchor defined by passed files
func ReferenceFiles(files ...string) DecodeOption {
return func(d *Decoder) error {
d.referenceFiles = files
return nil
}
}
// ReferenceDirs pass to Decoder that reference to anchor defined by files under the passed dirs
func ReferenceDirs(dirs ...string) DecodeOption {
return func(d *Decoder) error {
d.referenceDirs = dirs
return nil
}
}
// RecursiveDir search yaml file recursively from passed dirs by ReferenceDirs option
func RecursiveDir(isRecursive bool) DecodeOption {
return func(d *Decoder) error {
d.isRecursiveDir = isRecursive
return nil
}
}
// Validator set StructValidator instance to Decoder
func Validator(v StructValidator) DecodeOption {
return func(d *Decoder) error {
d.validator = v
return nil
}
}
// Strict enable DisallowUnknownField and DisallowDuplicateKey
func Strict() DecodeOption {
return func(d *Decoder) error {
d.disallowUnknownField = true
d.disallowDuplicateKey = true
return nil
}
}
// DisallowUnknownField causes the Decoder to return an error when the destination
// is a struct and the input contains object keys which do not match any
// non-ignored, exported fields in the destination.
func DisallowUnknownField() DecodeOption {
return func(d *Decoder) error {
d.disallowUnknownField = true
return nil
}
}
// DisallowDuplicateKey causes an error when mapping keys that are duplicates
func DisallowDuplicateKey() DecodeOption {
return func(d *Decoder) error {
d.disallowDuplicateKey = true
return nil
}
}
// UseOrderedMap can be interpreted as a map,
// and uses MapSlice ( ordered map ) aggressively if there is no type specification
func UseOrderedMap() DecodeOption {
return func(d *Decoder) error {
d.useOrderedMap = true
return nil
}
}
// UseJSONUnmarshaler if neither `BytesUnmarshaler` nor `InterfaceUnmarshaler` is implemented
// and `UnmashalJSON([]byte)error` is implemented, convert the argument from `YAML` to `JSON` and then call it.
func UseJSONUnmarshaler() DecodeOption {
return func(d *Decoder) error {
d.useJSONUnmarshaler = true
return nil
}
}
// EncodeOption functional option type for Encoder
type EncodeOption func(e *Encoder) error
// Indent change indent number
func Indent(spaces int) EncodeOption {
return func(e *Encoder) error {
e.indent = spaces
return nil
}
}
// IndentSequence causes sequence values to be indented the same value as Indent
func IndentSequence(indent bool) EncodeOption {
return func(e *Encoder) error {
e.indentSequence = indent
return nil
}
}
// UseSingleQuote determines if single or double quotes should be preferred for strings.
func UseSingleQuote(sq bool) EncodeOption {
return func(e *Encoder) error {
e.singleQuote = sq
return nil
}
}
// Flow encoding by flow style
func Flow(isFlowStyle bool) EncodeOption {
return func(e *Encoder) error {
e.isFlowStyle = isFlowStyle
return nil
}
}
// UseLiteralStyleIfMultiline causes encoding multiline strings with a literal syntax,
// no matter what characters they include
func UseLiteralStyleIfMultiline(useLiteralStyleIfMultiline bool) EncodeOption {
return func(e *Encoder) error {
e.useLiteralStyleIfMultiline = useLiteralStyleIfMultiline
return nil
}
}
// JSON encode in JSON format
func JSON() EncodeOption {
return func(e *Encoder) error {
e.isJSONStyle = true
e.isFlowStyle = true
return nil
}
}
// MarshalAnchor call back if encoder find an anchor during encoding
func MarshalAnchor(callback func(*ast.AnchorNode, interface{}) error) EncodeOption {
return func(e *Encoder) error {
e.anchorCallback = callback
return nil
}
}
// UseJSONMarshaler if neither `BytesMarshaler` nor `InterfaceMarshaler`
// nor `encoding.TextMarshaler` is implemented and `MarshalJSON()([]byte, error)` is implemented,
// call `MarshalJSON` to convert the returned `JSON` to `YAML` for processing.
func UseJSONMarshaler() EncodeOption {
return func(e *Encoder) error {
e.useJSONMarshaler = true
return nil
}
}
// CommentPosition type of the position for comment.
type CommentPosition int
const (
CommentLinePosition CommentPosition = iota
CommentHeadPosition
)
func (p CommentPosition) String() string {
switch p {
case CommentLinePosition:
return "Line"
case CommentHeadPosition:
return "Head"
default:
return ""
}
}
// LineComment create a one-line comment for CommentMap.
func LineComment(text string) *Comment {
return &Comment{
Texts: []string{text},
Position: CommentLinePosition,
}
}
// HeadComment create a multiline comment for CommentMap.
func HeadComment(texts ...string) *Comment {
return &Comment{
Texts: texts,
Position: CommentHeadPosition,
}
}
// Comment raw data for comment.
type Comment struct {
Texts []string
Position CommentPosition
}
// CommentMap map of the position of the comment and the comment information.
type CommentMap map[string]*Comment
// WithComment add a comment using the location and text information given in the CommentMap.
func WithComment(cm CommentMap) EncodeOption {
return func(e *Encoder) error {
commentMap := map[*Path]*Comment{}
for k, v := range cm {
path, err := PathString(k)
if err != nil {
return err
}
commentMap[path] = v
}
e.commentMap = commentMap
return nil
}
}
// CommentToMap apply the position and content of comments in a YAML document to a CommentMap.
func CommentToMap(cm CommentMap) DecodeOption {
return func(d *Decoder) error {
if cm == nil {
return ErrInvalidCommentMapValue
}
d.toCommentMap = cm
return nil
}
}
golang-github-goccy-go-yaml-1.9.5/parser/ 0000775 0000000 0000000 00000000000 14167531274 0020230 5 ustar 00root root 0000000 0000000 golang-github-goccy-go-yaml-1.9.5/parser/context.go 0000664 0000000 0000000 00000007035 14167531274 0022250 0 ustar 00root root 0000000 0000000 package parser
import (
"fmt"
"strings"
"github.com/goccy/go-yaml/token"
)
// context context at parsing
type context struct {
parent *context
idx int
size int
tokens token.Tokens
mode Mode
path string
}
var pathSpecialChars = []string{
"$", "*", ".", "[", "]",
}
func containsPathSpecialChar(path string) bool {
for _, char := range pathSpecialChars {
if strings.Contains(path, char) {
return true
}
}
return false
}
func normalizePath(path string) string {
if containsPathSpecialChar(path) {
return fmt.Sprintf("'%s'", path)
}
return path
}
func (c *context) withChild(path string) *context {
ctx := c.copy()
path = normalizePath(path)
ctx.path += fmt.Sprintf(".%s", path)
return ctx
}
func (c *context) withIndex(idx uint) *context {
ctx := c.copy()
ctx.path += fmt.Sprintf("[%d]", idx)
return ctx
}
func (c *context) copy() *context {
return &context{
parent: c,
idx: c.idx,
size: c.size,
tokens: append(token.Tokens{}, c.tokens...),
mode: c.mode,
path: c.path,
}
}
func (c *context) next() bool {
return c.idx < c.size
}
func (c *context) previousToken() *token.Token {
if c.idx > 0 {
return c.tokens[c.idx-1]
}
return nil
}
func (c *context) insertToken(idx int, tk *token.Token) {
if c.parent != nil {
c.parent.insertToken(idx, tk)
}
if c.size < idx {
return
}
if c.size == idx {
curToken := c.tokens[c.size-1]
tk.Next = curToken
curToken.Prev = tk
c.tokens = append(c.tokens, tk)
c.size = len(c.tokens)
return
}
curToken := c.tokens[idx]
tk.Next = curToken
curToken.Prev = tk
c.tokens = append(c.tokens[:idx+1], c.tokens[idx:]...)
c.tokens[idx] = tk
c.size = len(c.tokens)
}
func (c *context) currentToken() *token.Token {
if c.idx >= c.size {
return nil
}
return c.tokens[c.idx]
}
func (c *context) nextToken() *token.Token {
if c.idx+1 >= c.size {
return nil
}
return c.tokens[c.idx+1]
}
func (c *context) afterNextToken() *token.Token {
if c.idx+2 >= c.size {
return nil
}
return c.tokens[c.idx+2]
}
func (c *context) nextNotCommentToken() *token.Token {
for i := c.idx + 1; i < c.size; i++ {
tk := c.tokens[i]
if tk.Type == token.CommentType {
continue
}
return tk
}
return nil
}
func (c *context) afterNextNotCommentToken() *token.Token {
notCommentTokenCount := 0
for i := c.idx + 1; i < c.size; i++ {
tk := c.tokens[i]
if tk.Type == token.CommentType {
continue
}
notCommentTokenCount++
if notCommentTokenCount == 2 {
return tk
}
}
return nil
}
func (c *context) enabledComment() bool {
return c.mode&ParseComments != 0
}
func (c *context) isCurrentCommentToken() bool {
tk := c.currentToken()
if tk == nil {
return false
}
return tk.Type == token.CommentType
}
func (c *context) progressIgnoreComment(num int) {
if c.parent != nil {
c.parent.progressIgnoreComment(num)
}
if c.size <= c.idx+num {
c.idx = c.size
} else {
c.idx += num
}
}
func (c *context) progress(num int) {
if c.isCurrentCommentToken() {
return
}
c.progressIgnoreComment(num)
}
func newContext(tokens token.Tokens, mode Mode) *context {
filteredTokens := []*token.Token{}
if mode&ParseComments != 0 {
filteredTokens = tokens
} else {
for _, tk := range tokens {
if tk.Type == token.CommentType {
continue
}
// keep prev/next reference between tokens containing comments
// https://github.com/goccy/go-yaml/issues/254
filteredTokens = append(filteredTokens, tk)
}
}
return &context{
idx: 0,
size: len(filteredTokens),
tokens: token.Tokens(filteredTokens),
mode: mode,
path: "$",
}
}
golang-github-goccy-go-yaml-1.9.5/parser/parser.go 0000664 0000000 0000000 00000044457 14167531274 0022071 0 ustar 00root root 0000000 0000000 package parser
import (
"fmt"
"io/ioutil"
"strings"
"github.com/goccy/go-yaml/ast"
"github.com/goccy/go-yaml/internal/errors"
"github.com/goccy/go-yaml/lexer"
"github.com/goccy/go-yaml/token"
"golang.org/x/xerrors"
)
type parser struct{}
func (p *parser) parseMapping(ctx *context) (ast.Node, error) {
mapTk := ctx.currentToken()
node := ast.Mapping(mapTk, true)
node.SetPath(ctx.path)
ctx.progress(1) // skip MappingStart token
for ctx.next() {
tk := ctx.currentToken()
if tk.Type == token.MappingEndType {
node.End = tk
return node, nil
} else if tk.Type == token.CollectEntryType {
ctx.progress(1)
continue
}
value, err := p.parseMappingValue(ctx)
if err != nil {
return nil, errors.Wrapf(err, "failed to parse mapping value in mapping node")
}
mvnode, ok := value.(*ast.MappingValueNode)
if !ok {
return nil, errors.ErrSyntax("failed to parse flow mapping node", value.GetToken())
}
node.Values = append(node.Values, mvnode)
ctx.progress(1)
}
return nil, errors.ErrSyntax("unterminated flow mapping", node.GetToken())
}
func (p *parser) parseSequence(ctx *context) (ast.Node, error) {
node := ast.Sequence(ctx.currentToken(), true)
node.SetPath(ctx.path)
ctx.progress(1) // skip SequenceStart token
for ctx.next() {
tk := ctx.currentToken()
if tk.Type == token.SequenceEndType {
node.End = tk
break
} else if tk.Type == token.CollectEntryType {
ctx.progress(1)
continue
}
value, err := p.parseToken(ctx.withIndex(uint(len(node.Values))), tk)
if err != nil {
return nil, errors.Wrapf(err, "failed to parse sequence value in flow sequence node")
}
node.Values = append(node.Values, value)
ctx.progress(1)
}
return node, nil
}
func (p *parser) parseTag(ctx *context) (ast.Node, error) {
tagToken := ctx.currentToken()
node := ast.Tag(tagToken)
node.SetPath(ctx.path)
ctx.progress(1) // skip tag token
var (
value ast.Node
err error
)
switch token.ReservedTagKeyword(tagToken.Value) {
case token.MappingTag,
token.OrderedMapTag:
value, err = p.parseMapping(ctx)
case token.IntegerTag,
token.FloatTag,
token.StringTag,
token.BinaryTag,
token.TimestampTag,
token.NullTag:
typ := ctx.currentToken().Type
if typ == token.LiteralType || typ == token.FoldedType {
value, err = p.parseLiteral(ctx)
} else {
value = p.parseScalarValue(ctx.currentToken())
}
case token.SequenceTag,
token.SetTag:
err = errors.ErrSyntax(fmt.Sprintf("sorry, currently not supported %s tag", tagToken.Value), tagToken)
default:
// custom tag
value, err = p.parseToken(ctx, ctx.currentToken())
}
if err != nil {
return nil, errors.Wrapf(err, "failed to parse tag value")
}
node.Value = value
return node, nil
}
func (p *parser) removeLeftSideNewLineCharacter(src string) string {
// CR or LF or CRLF
return strings.TrimLeft(strings.TrimLeft(strings.TrimLeft(src, "\r"), "\n"), "\r\n")
}
func (p *parser) existsNewLineCharacter(src string) bool {
if strings.Index(src, "\n") > 0 {
return true
}
if strings.Index(src, "\r") > 0 {
return true
}
return false
}
func (p *parser) validateMapKey(tk *token.Token) error {
if tk.Type != token.StringType {
return nil
}
origin := p.removeLeftSideNewLineCharacter(tk.Origin)
if p.existsNewLineCharacter(origin) {
return errors.ErrSyntax("unexpected key name", tk)
}
return nil
}
func (p *parser) createNullToken(base *token.Token) *token.Token {
pos := *(base.Position)
pos.Column++
return token.New("null", "null", &pos)
}
func (p *parser) parseMapValue(ctx *context, key ast.Node, colonToken *token.Token) (ast.Node, error) {
node, err := p.createMapValueNode(ctx, key, colonToken)
if err != nil {
return nil, errors.Wrapf(err, "failed to create map value node")
}
if node != nil && node.GetPath() == "" {
node.SetPath(ctx.path)
}
return node, nil
}
func (p *parser) createMapValueNode(ctx *context, key ast.Node, colonToken *token.Token) (ast.Node, error) {
tk := ctx.currentToken()
if tk == nil {
nullToken := p.createNullToken(colonToken)
ctx.insertToken(ctx.idx, nullToken)
return ast.Null(nullToken), nil
}
if tk.Position.Column == key.GetToken().Position.Column && tk.Type == token.StringType {
// in this case,
// ----
// key:
// next
nullToken := p.createNullToken(colonToken)
ctx.insertToken(ctx.idx, nullToken)
return ast.Null(nullToken), nil
}
if tk.Position.Column < key.GetToken().Position.Column {
// in this case,
// ----
// key:
// next
nullToken := p.createNullToken(colonToken)
ctx.insertToken(ctx.idx, nullToken)
return ast.Null(nullToken), nil
}
value, err := p.parseToken(ctx, ctx.currentToken())
if err != nil {
return nil, errors.Wrapf(err, "failed to parse mapping 'value' node")
}
return value, nil
}
func (p *parser) validateMapValue(ctx *context, key, value ast.Node) error {
keyColumn := key.GetToken().Position.Column
valueColumn := value.GetToken().Position.Column
if keyColumn != valueColumn {
return nil
}
if value.Type() != ast.StringType {
return nil
}
ntk := ctx.nextToken()
if ntk == nil || (ntk.Type != token.MappingValueType && ntk.Type != token.SequenceEntryType) {
return errors.ErrSyntax("could not found expected ':' token", value.GetToken())
}
return nil
}
func (p *parser) parseMappingValue(ctx *context) (ast.Node, error) {
key, err := p.parseMapKey(ctx)
if err != nil {
return nil, errors.Wrapf(err, "failed to parse map key")
}
keyText := key.GetToken().Value
key.SetPath(ctx.withChild(keyText).path)
if err := p.validateMapKey(key.GetToken()); err != nil {
return nil, errors.Wrapf(err, "validate mapping key error")
}
ctx.progress(1) // progress to mapping value token
tk := ctx.currentToken() // get mapping value token
if tk == nil {
return nil, errors.ErrSyntax("unexpected map", key.GetToken())
}
ctx.progress(1) // progress to value token
if err := p.setSameLineCommentIfExists(ctx.withChild(keyText), key); err != nil {
return nil, errors.Wrapf(err, "failed to set same line comment to node")
}
if key.GetComment() != nil {
// if current token is comment, GetComment() is not nil.
// then progress to value token
ctx.progressIgnoreComment(1)
}
value, err := p.parseMapValue(ctx.withChild(keyText), key, tk)
if err != nil {
return nil, errors.Wrapf(err, "failed to parse map value")
}
if err := p.validateMapValue(ctx, key, value); err != nil {
return nil, errors.Wrapf(err, "failed to validate map value")
}
mvnode := ast.MappingValue(tk, key, value)
mvnode.SetPath(ctx.withChild(keyText).path)
node := ast.Mapping(tk, false, mvnode)
node.SetPath(ctx.withChild(keyText).path)
ntk := ctx.nextNotCommentToken()
antk := ctx.afterNextNotCommentToken()
for antk != nil && antk.Type == token.MappingValueType &&
ntk.Position.Column == key.GetToken().Position.Column {
ctx.progressIgnoreComment(1)
value, err := p.parseToken(ctx, ctx.currentToken())
if err != nil {
return nil, errors.Wrapf(err, "failed to parse mapping node")
}
switch value.Type() {
case ast.MappingType:
c := value.(*ast.MappingNode)
comment := c.GetComment()
for idx, v := range c.Values {
if idx == 0 && comment != nil {
if err := v.SetComment(comment); err != nil {
return nil, errors.Wrapf(err, "failed to set comment token to node")
}
}
node.Values = append(node.Values, v)
}
case ast.MappingValueType:
node.Values = append(node.Values, value.(*ast.MappingValueNode))
default:
return nil, xerrors.Errorf("failed to parse mapping value node node is %s", value.Type())
}
ntk = ctx.nextNotCommentToken()
antk = ctx.afterNextNotCommentToken()
}
if len(node.Values) == 1 {
return mvnode, nil
}
return node, nil
}
func (p *parser) parseSequenceEntry(ctx *context) (ast.Node, error) {
tk := ctx.currentToken()
sequenceNode := ast.Sequence(tk, false)
sequenceNode.SetPath(ctx.path)
curColumn := tk.Position.Column
for tk.Type == token.SequenceEntryType {
ctx.progress(1) // skip sequence token
tk = ctx.currentToken()
if tk == nil {
return nil, errors.ErrSyntax("empty sequence entry", ctx.previousToken())
}
var comment *ast.CommentGroupNode
if tk.Type == token.CommentType {
comment = p.parseCommentOnly(ctx)
tk = ctx.currentToken()
if tk.Type != token.SequenceEntryType {
break
}
ctx.progress(1) // skip sequence token
}
value, err := p.parseToken(ctx.withIndex(uint(len(sequenceNode.Values))), ctx.currentToken())
if err != nil {
return nil, errors.Wrapf(err, "failed to parse sequence")
}
if comment != nil {
comment.SetPath(ctx.withIndex(uint(len(sequenceNode.Values))).path)
sequenceNode.ValueComments = append(sequenceNode.ValueComments, comment)
} else {
sequenceNode.ValueComments = append(sequenceNode.ValueComments, nil)
}
sequenceNode.Values = append(sequenceNode.Values, value)
tk = ctx.nextNotCommentToken()
if tk == nil {
break
}
if tk.Type != token.SequenceEntryType {
break
}
if tk.Position.Column != curColumn {
break
}
ctx.progressIgnoreComment(1)
}
return sequenceNode, nil
}
func (p *parser) parseAnchor(ctx *context) (ast.Node, error) {
tk := ctx.currentToken()
anchor := ast.Anchor(tk)
anchor.SetPath(ctx.path)
ntk := ctx.nextToken()
if ntk == nil {
return nil, errors.ErrSyntax("unexpected anchor. anchor name is undefined", tk)
}
ctx.progress(1) // skip anchor token
name, err := p.parseToken(ctx, ctx.currentToken())
if err != nil {
return nil, errors.Wrapf(err, "failed to parser anchor name node")
}
anchor.Name = name
ntk = ctx.nextToken()
if ntk == nil {
return nil, errors.ErrSyntax("unexpected anchor. anchor value is undefined", ctx.currentToken())
}
ctx.progress(1)
value, err := p.parseToken(ctx, ctx.currentToken())
if err != nil {
return nil, errors.Wrapf(err, "failed to parser anchor name node")
}
anchor.Value = value
return anchor, nil
}
func (p *parser) parseAlias(ctx *context) (ast.Node, error) {
tk := ctx.currentToken()
alias := ast.Alias(tk)
alias.SetPath(ctx.path)
ntk := ctx.nextToken()
if ntk == nil {
return nil, errors.ErrSyntax("unexpected alias. alias name is undefined", tk)
}
ctx.progress(1) // skip alias token
name, err := p.parseToken(ctx, ctx.currentToken())
if err != nil {
return nil, errors.Wrapf(err, "failed to parser alias name node")
}
alias.Value = name
return alias, nil
}
func (p *parser) parseMapKey(ctx *context) (ast.Node, error) {
tk := ctx.currentToken()
if value := p.parseScalarValue(tk); value != nil {
return value, nil
}
switch tk.Type {
case token.MergeKeyType:
return ast.MergeKey(tk), nil
case token.MappingKeyType:
return p.parseMappingKey(ctx)
}
return nil, errors.ErrSyntax("unexpected mapping key", tk)
}
func (p *parser) parseStringValue(tk *token.Token) ast.Node {
switch tk.Type {
case token.StringType,
token.SingleQuoteType,
token.DoubleQuoteType:
return ast.String(tk)
}
return nil
}
func (p *parser) parseScalarValueWithComment(ctx *context, tk *token.Token) (ast.Node, error) {
node := p.parseScalarValue(tk)
if node == nil {
return nil, nil
}
node.SetPath(ctx.path)
if p.isSameLineComment(ctx.nextToken(), node) {
ctx.progress(1)
if err := p.setSameLineCommentIfExists(ctx, node); err != nil {
return nil, errors.Wrapf(err, "failed to set same line comment to node")
}
}
return node, nil
}
func (p *parser) parseScalarValue(tk *token.Token) ast.Node {
if node := p.parseStringValue(tk); node != nil {
return node
}
switch tk.Type {
case token.NullType:
return ast.Null(tk)
case token.BoolType:
return ast.Bool(tk)
case token.IntegerType,
token.BinaryIntegerType,
token.OctetIntegerType,
token.HexIntegerType:
return ast.Integer(tk)
case token.FloatType:
return ast.Float(tk)
case token.InfinityType:
return ast.Infinity(tk)
case token.NanType:
return ast.Nan(tk)
}
return nil
}
func (p *parser) parseDirective(ctx *context) (ast.Node, error) {
node := ast.Directive(ctx.currentToken())
ctx.progress(1) // skip directive token
value, err := p.parseToken(ctx, ctx.currentToken())
if err != nil {
return nil, errors.Wrapf(err, "failed to parse directive value")
}
node.Value = value
ctx.progress(1)
tk := ctx.currentToken()
if tk == nil {
// Since current token is nil, use the previous token to specify
// the syntax error location.
return nil, errors.ErrSyntax("unexpected directive value. document not started", ctx.previousToken())
}
if tk.Type != token.DocumentHeaderType {
return nil, errors.ErrSyntax("unexpected directive value. document not started", ctx.currentToken())
}
return node, nil
}
func (p *parser) parseLiteral(ctx *context) (ast.Node, error) {
node := ast.Literal(ctx.currentToken())
ctx.progress(1) // skip literal/folded token
tk := ctx.currentToken()
var comment *ast.CommentGroupNode
if tk.Type == token.CommentType {
comment = p.parseCommentOnly(ctx)
comment.SetPath(ctx.path)
if err := node.SetComment(comment); err != nil {
return nil, errors.Wrapf(err, "failed to set comment to literal")
}
tk = ctx.currentToken()
}
value, err := p.parseToken(ctx, tk)
if err != nil {
return nil, errors.Wrapf(err, "failed to parse literal/folded value")
}
snode, ok := value.(*ast.StringNode)
if !ok {
return nil, errors.ErrSyntax("unexpected token. required string token", value.GetToken())
}
node.Value = snode
return node, nil
}
func (p *parser) isSameLineComment(tk *token.Token, node ast.Node) bool {
if tk == nil {
return false
}
if tk.Type != token.CommentType {
return false
}
return tk.Position.Line == node.GetToken().Position.Line
}
func (p *parser) setSameLineCommentIfExists(ctx *context, node ast.Node) error {
tk := ctx.currentToken()
if !p.isSameLineComment(tk, node) {
return nil
}
comment := ast.CommentGroup([]*token.Token{tk})
comment.SetPath(ctx.path)
if err := node.SetComment(comment); err != nil {
return errors.Wrapf(err, "failed to set comment token to ast.Node")
}
return nil
}
func (p *parser) parseDocument(ctx *context) (*ast.DocumentNode, error) {
startTk := ctx.currentToken()
ctx.progress(1) // skip document header token
body, err := p.parseToken(ctx, ctx.currentToken())
if err != nil {
return nil, errors.Wrapf(err, "failed to parse document body")
}
node := ast.Document(startTk, body)
if ntk := ctx.nextToken(); ntk != nil && ntk.Type == token.DocumentEndType {
node.End = ntk
ctx.progress(1)
}
return node, nil
}
func (p *parser) parseCommentOnly(ctx *context) *ast.CommentGroupNode {
commentTokens := []*token.Token{}
for {
tk := ctx.currentToken()
if tk == nil {
break
}
if tk.Type != token.CommentType {
break
}
commentTokens = append(commentTokens, tk)
ctx.progressIgnoreComment(1) // skip comment token
}
return ast.CommentGroup(commentTokens)
}
func (p *parser) parseComment(ctx *context) (ast.Node, error) {
group := p.parseCommentOnly(ctx)
node, err := p.parseToken(ctx, ctx.currentToken())
if err != nil {
return nil, errors.Wrapf(err, "failed to parse node after comment")
}
if node == nil {
return group, nil
}
group.SetPath(node.GetPath())
if err := node.SetComment(group); err != nil {
return nil, errors.Wrapf(err, "failed to set comment token to node")
}
return node, nil
}
func (p *parser) parseMappingKey(ctx *context) (ast.Node, error) {
keyTk := ctx.currentToken()
node := ast.MappingKey(keyTk)
node.SetPath(ctx.path)
ctx.progress(1) // skip mapping key token
value, err := p.parseToken(ctx.withChild(keyTk.Value), ctx.currentToken())
if err != nil {
return nil, errors.Wrapf(err, "failed to parse map key")
}
node.Value = value
return node, nil
}
func (p *parser) parseToken(ctx *context, tk *token.Token) (ast.Node, error) {
node, err := p.createNodeFromToken(ctx, tk)
if err != nil {
return nil, errors.Wrapf(err, "failed to create node from token")
}
if node != nil && node.GetPath() == "" {
node.SetPath(ctx.path)
}
return node, nil
}
func (p *parser) createNodeFromToken(ctx *context, tk *token.Token) (ast.Node, error) {
if tk == nil {
return nil, nil
}
if tk.NextType() == token.MappingValueType {
node, err := p.parseMappingValue(ctx)
return node, err
}
node, err := p.parseScalarValueWithComment(ctx, tk)
if err != nil {
return nil, errors.Wrapf(err, "failed to parse scalar value")
}
if node != nil {
return node, nil
}
switch tk.Type {
case token.CommentType:
return p.parseComment(ctx)
case token.MappingKeyType:
return p.parseMappingKey(ctx)
case token.DocumentHeaderType:
return p.parseDocument(ctx)
case token.MappingStartType:
return p.parseMapping(ctx)
case token.SequenceStartType:
return p.parseSequence(ctx)
case token.SequenceEntryType:
return p.parseSequenceEntry(ctx)
case token.AnchorType:
return p.parseAnchor(ctx)
case token.AliasType:
return p.parseAlias(ctx)
case token.DirectiveType:
return p.parseDirective(ctx)
case token.TagType:
return p.parseTag(ctx)
case token.LiteralType, token.FoldedType:
return p.parseLiteral(ctx)
}
return nil, nil
}
func (p *parser) parse(tokens token.Tokens, mode Mode) (*ast.File, error) {
ctx := newContext(tokens, mode)
file := &ast.File{Docs: []*ast.DocumentNode{}}
for ctx.next() {
node, err := p.parseToken(ctx, ctx.currentToken())
if err != nil {
return nil, errors.Wrapf(err, "failed to parse")
}
ctx.progressIgnoreComment(1)
if node == nil {
continue
}
if doc, ok := node.(*ast.DocumentNode); ok {
file.Docs = append(file.Docs, doc)
} else {
file.Docs = append(file.Docs, ast.Document(nil, node))
}
}
return file, nil
}
type Mode uint
const (
ParseComments Mode = 1 << iota // parse comments and add them to AST
)
// ParseBytes parse from byte slice, and returns ast.File
func ParseBytes(bytes []byte, mode Mode) (*ast.File, error) {
tokens := lexer.Tokenize(string(bytes))
f, err := Parse(tokens, mode)
if err != nil {
return nil, errors.Wrapf(err, "failed to parse")
}
return f, nil
}
// Parse parse from token instances, and returns ast.File
func Parse(tokens token.Tokens, mode Mode) (*ast.File, error) {
var p parser
f, err := p.parse(tokens, mode)
if err != nil {
return nil, errors.Wrapf(err, "failed to parse")
}
return f, nil
}
// Parse parse from filename, and returns ast.File
func ParseFile(filename string, mode Mode) (*ast.File, error) {
file, err := ioutil.ReadFile(filename)
if err != nil {
return nil, errors.Wrapf(err, "failed to read file: %s", filename)
}
f, err := ParseBytes(file, mode)
if err != nil {
return nil, errors.Wrapf(err, "failed to parse")
}
f.Name = filename
return f, nil
}
golang-github-goccy-go-yaml-1.9.5/parser/parser_test.go 0000664 0000000 0000000 00000024375 14167531274 0023125 0 ustar 00root root 0000000 0000000 package parser_test
import (
"fmt"
"path/filepath"
"reflect"
"strings"
"testing"
"github.com/goccy/go-yaml/ast"
"github.com/goccy/go-yaml/lexer"
"github.com/goccy/go-yaml/parser"
"github.com/goccy/go-yaml/token"
)
func TestParser(t *testing.T) {
sources := []string{
"null\n",
"{}\n",
"v: hi\n",
"v: \"true\"\n",
"v: \"false\"\n",
"v: true\n",
"v: false\n",
"v: 10\n",
"v: -10\n",
"v: 42\n",
"v: 4294967296\n",
"v: \"10\"\n",
"v: 0.1\n",
"v: 0.99\n",
"v: -0.1\n",
"v: .inf\n",
"v: -.inf\n",
"v: .nan\n",
"v: null\n",
"v: \"\"\n",
"v:\n- A\n- B\n",
"a: '-'\n",
"123\n",
"hello: world\n",
"a: null\n",
"v:\n- A\n- 1\n- B:\n - 2\n - 3\n",
"a:\n b: c\n",
"a: {x: 1}\n",
"t2: 2018-01-09T10:40:47Z\nt4: 2098-01-09T10:40:47Z\n",
"a: [1, 2]\n",
"a: {b: c, d: e}\n",
"a: 3s\n",
"a: \n",
"a: \"1:1\"\n",
"a: 1.2.3.4\n",
"a: \"2015-02-24T18:19:39Z\"\n",
"a: 'b: c'\n",
"a: 'Hello #comment'\n",
"a: abc <> ghi",
"a: <-\n B\n C\n",
"v: |-\n 0\n",
"v: |-\n 0\nx: 0",
`"a\n1\nb"`,
`{"a":"b"}`,
`!!map {
? !!str "explicit":!!str "entry",
? !!str "implicit" : !!str "entry",
? !!null "" : !!null "",
}`,
}
for _, src := range sources {
if _, err := parser.Parse(lexer.Tokenize(src), 0); err != nil {
t.Fatalf("parse error: source [%s]: %+v", src, err)
}
}
}
func TestParseComplicatedDocument(t *testing.T) {
tests := []struct {
source string
expect string
}{
{
`
american:
- Boston Red Sox
- Detroit Tigers
- New York Yankees
national:
- New York Mets
- Chicago Cubs
- Atlanta Braves
`, `
american:
- Boston Red Sox
- Detroit Tigers
- New York Yankees
national:
- New York Mets
- Chicago Cubs
- Atlanta Braves
`,
},
{
`
a:
b: c
d: e
f: g
h:
i: j
k:
l: m
n: o
p: q
r: s
`, `
a:
b: c
d: e
f: g
h:
i: j
k:
l: m
n: o
p: q
r: s
`,
},
{
`
- a:
- b
- c
- d
`, `
- a:
- b
- c
- d
`,
},
{
`
- a
- b
- c
- d
- e
- f
`, `
- a
- b
- c - d - e
- f
`,
},
{
`
a: 0 - 1
`,
`
a: 0 - 1
`,
},
{`
- a:
b: c
d: e
- f:
g: h
`,
`
- a:
b: c
d: e
- f: null
g: h
`,
},
{
`
a:
b
c
d: e
`, `
a: b c
d: e
`,
},
{
`
a
b
c
`, `
a b c
`,
},
{
`
a:
- b
- c
`, `
a:
- b
- c
`,
},
{
`
- a :
b: c
`, `
- a: null
b: c
`,
},
{
`
- a:
b
c
d
hoge: fuga
`, `
- a: b c d
hoge: fuga
`,
},
{
`
- a # ' " # - : %
- b # " # - : % '
- c # # - : % ' "
- d # - : % ' " #
- e # : % ' " # -
- f # % ' : # - :
`,
`
- a
- b
- c
- d
- e
- f
`,
},
{
`
# comment
a: # comment
# comment
b: c # comment
# comment
d: e # comment
# comment
`,
`
a:
b: c
d: e
`,
},
{
`
a: b#notcomment
`,
`
a: b#notcomment
`,
},
{
`
anchored: &anchor foo
aliased: *anchor
`,
`
anchored: &anchor foo
aliased: *anchor
`,
},
{
`
---
- &CENTER { x: 1, y: 2 }
- &LEFT { x: 0, y: 2 }
- &BIG { r: 10 }
- &SMALL { r: 1 }
# All the following maps are equal:
- # Explicit keys
x: 1
y: 2
r: 10
label: center/big
- # Merge one map
<< : *CENTER
r: 10
label: center/big
- # Merge multiple maps
<< : [ *CENTER, *BIG ]
label: center/big
- # Override
<< : [ *BIG, *LEFT, *SMALL ]
x: 1
label: center/big
`,
`
---
- &CENTER {x: 1, y: 2}
- &LEFT {x: 0, y: 2}
- &BIG {r: 10}
- &SMALL {r: 1}
- x: 1
y: 2
r: 10
label: center/big
- <<: *CENTER
r: 10
label: center/big
- <<: [*CENTER, *BIG]
label: center/big
- <<: [*BIG, *LEFT, *SMALL]
x: 1
label: center/big
`,
},
{
`
a:
- - b
- - c
- d
`,
`
a:
- - b
- - c
- d
`,
},
{
`
a:
b:
c: d
e:
f: g
h: i
j: k
`,
`
a:
b:
c: d
e:
f: g
h: i
j: k
`,
},
{
`
---
a: 1
b: 2
...
---
c: 3
d: 4
...
`,
`
---
a: 1
b: 2
...
---
c: 3
d: 4
...
`,
},
{
`
a:
b: |
{
[ 1, 2 ]
}
c: d
`,
`
a:
b: |
{
[ 1, 2 ]
}
c: d
`,
},
{
`
|
hoge
fuga
piyo`,
`
|
hoge
fuga
piyo
`,
},
{
`
a: |
bbbbbbb
ccccccc
d: eeeeeeeeeeeeeeeee
`,
`
a: |
bbbbbbb
ccccccc
d: eeeeeeeeeeeeeeeee
`,
},
{
`
a: b
c
`,
`
a: b c
`,
},
{
`
a:
b: c
`,
`
a:
b: c
`,
},
{
`
a: b
c: d
`,
`
a: b
c: d
`,
},
{
`
- ab - cd
- ef - gh
`,
`
- ab - cd
- ef - gh
`,
},
{
`
- 0 - 1
- 2 - 3
`,
`
- 0 - 1 - 2 - 3
`,
},
{
`
a - b - c: value
`,
`
a - b - c: value
`,
},
{
`
a:
-
b: c
d: e
-
f: g
h: i
`,
`
a:
- b: c
d: e
- f: g
h: i
`,
},
{
`
a: |-
value
b: c
`,
`
a: |-
value
b: c
`,
},
{
`
a: |+
value
b: c
`,
`
a: |+
value
b: c
`,
},
{
`
- key1: val
key2:
(
foo
+
bar
)
`,
`
- key1: val
key2: ( foo + bar )
`,
},
}
for _, test := range tests {
t.Run(test.source, func(t *testing.T) {
tokens := lexer.Tokenize(test.source)
f, err := parser.Parse(tokens, 0)
if err != nil {
t.Fatalf("%+v", err)
}
var v Visitor
for _, doc := range f.Docs {
ast.Walk(&v, doc.Body)
}
expect := fmt.Sprintf("\n%+v\n", f)
if test.expect != expect {
tokens.Dump()
t.Fatalf("unexpected output: [%s] != [%s]", test.expect, expect)
}
})
}
}
func TestNewLineChar(t *testing.T) {
for _, f := range []string{
"lf.yml",
"cr.yml",
"crlf.yml",
} {
ast, err := parser.ParseFile(filepath.Join("testdata", f), 0)
if err != nil {
t.Fatalf("%+v", err)
}
actual := fmt.Sprintf("%v\n", ast)
expect := `a: "a"
b: 1
`
if expect != actual {
t.Fatal("unexpected result")
}
}
}
func TestSyntaxError(t *testing.T) {
tests := []struct {
source string
expect string
}{
{
`
a:
- b
c: d
e: f
g: h`,
`
[3:3] unexpected key name
2 | a:
> 3 | - b
4 | c: d
^
5 | e: f
6 | g: h`,
},
{
`
a
- b: c`,
`
[2:1] unexpected key name
> 2 | a
3 | - b: c
^
`,
},
{
`%YAML 1.1 {}`,
`
[1:2] unexpected directive value. document not started
> 1 | %YAML 1.1 {}
^
`,
},
{
`{invalid`,
`
[1:2] unexpected map
> 1 | {invalid
^
`,
},
{
`{ "key": "value" `,
`
[1:1] unterminated flow mapping
> 1 | { "key": "value"
^
`,
},
{
`
a:
- b: c
- `,
`
[4:1] empty sequence entry
2 | a:
3 | - b: c
> 4 | -
^
`,
},
}
for _, test := range tests {
t.Run(test.source, func(t *testing.T) {
_, err := parser.ParseBytes([]byte(test.source), 0)
if err == nil {
t.Fatal("cannot catch syntax error")
}
actual := "\n" + err.Error()
if test.expect != actual {
t.Fatalf("expected: [%s] but got [%s]", test.expect, actual)
}
})
}
}
func TestComment(t *testing.T) {
tests := []struct {
name string
yaml string
}{
{
name: "map with comment",
yaml: `
# commentA
a: #commentB
# commentC
b: c # commentD
# commentE
d: e # commentF
# commentG
f: g # commentH
# commentI
f: g # commentJ
# commentK
`,
},
{
name: "sequence with comment",
yaml: `
# commentA
- a # commentB
# commentC
- b: # commentD
# commentE
- d # commentF
- e # commentG
# commentH
`,
},
{
name: "anchor and alias",
yaml: `
a: &x b # commentA
c: *x # commentB
`,
},
{
name: "multiline",
yaml: `
# foo comment
# foo comment2
foo: # map key comment
# bar above comment
# bar above comment2
bar: 10 # comment for bar
# baz above comment
# baz above comment2
baz: bbbb # comment for baz
piyo: # sequence key comment
# sequence1 above comment 1
# sequence1 above comment 2
- sequence1 # sequence1
# sequence2 above comment 1
# sequence2 above comment 2
- sequence2 # sequence2
# sequence3 above comment 1
# sequence3 above comment 2
- false # sequence3
# foo2 comment
# foo2 comment2
foo2: &anchor text # anchor comment
# foo3 comment
# foo3 comment2
foo3: *anchor # alias comment
`,
},
{
name: "literal",
yaml: `
foo: | # comment
x: 42
`,
},
{
name: "folded",
yaml: `
foo: > # comment
x: 42
`,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
f, err := parser.ParseBytes([]byte(test.yaml), parser.ParseComments)
if err != nil {
t.Fatalf("%+v", err)
}
got := "\n" + f.String() + "\n"
if test.yaml != got {
t.Fatalf("expected:%s\ngot:%s", test.yaml, got)
}
})
}
}
func TestNodePath(t *testing.T) {
yml := `
a: # commentA
b: # commentB
c: foo # commentC
d: bar # commentD
e: baz # commentE
f: # commentF
g: hoge # commentG
h: # commentH
- list1 # comment list1
- list2 # comment list2
- list3 # comment list3
i: fuga # commentI
j: piyo # commentJ
k.l.m.n: moge # commentKLMN
`
f, err := parser.ParseBytes([]byte(yml), parser.ParseComments)
if err != nil {
t.Fatalf("%+v", err)
}
var capturer pathCapturer
for _, doc := range f.Docs {
ast.Walk(&capturer, doc.Body)
}
commentPaths := []string{}
for i := 0; i < capturer.capturedNum; i++ {
if capturer.orderedTypes[i] == ast.CommentType {
commentPaths = append(commentPaths, capturer.orderedPaths[i])
}
}
expectedPaths := []string{
"$.a",
"$.a.b",
"$.a.b.c",
"$.a.b.d",
"$.a.b.e",
"$.a.f",
"$.a.f.g",
"$.a.h",
"$.a.h[0]",
"$.a.h[1]",
"$.a.h[2]",
"$.a.i",
"$.j",
"$.'k.l.m.n'",
}
if !reflect.DeepEqual(expectedPaths, commentPaths) {
t.Fatalf("failed to get YAMLPath to the comment node:\nexpected[%s]\ngot [%s]", expectedPaths, commentPaths)
}
}
type pathCapturer struct {
capturedNum int
orderedPaths []string
orderedTypes []ast.NodeType
orderedTokens []*token.Token
}
func (c *pathCapturer) Visit(node ast.Node) ast.Visitor {
c.capturedNum++
c.orderedPaths = append(c.orderedPaths, node.GetPath())
c.orderedTypes = append(c.orderedTypes, node.Type())
c.orderedTokens = append(c.orderedTokens, node.GetToken())
return c
}
type Visitor struct {
}
func (v *Visitor) Visit(node ast.Node) ast.Visitor {
tk := node.GetToken()
tk.Prev = nil
tk.Next = nil
return v
}
golang-github-goccy-go-yaml-1.9.5/parser/testdata/ 0000775 0000000 0000000 00000000000 14167531274 0022041 5 ustar 00root root 0000000 0000000 golang-github-goccy-go-yaml-1.9.5/parser/testdata/cr.yml 0000664 0000000 0000000 00000000015 14167531274 0023164 0 ustar 00root root 0000000 0000000 a: "a"
b: 1
golang-github-goccy-go-yaml-1.9.5/parser/testdata/crlf.yml 0000664 0000000 0000000 00000000020 14167531274 0023502 0 ustar 00root root 0000000 0000000 a: "a"
b: 1
golang-github-goccy-go-yaml-1.9.5/parser/testdata/lf.yml 0000664 0000000 0000000 00000000015 14167531274 0023161 0 ustar 00root root 0000000 0000000 a: "a"
b: 1
golang-github-goccy-go-yaml-1.9.5/path.go 0000664 0000000 0000000 00000051342 14167531274 0020224 0 ustar 00root root 0000000 0000000 package yaml
import (
"bytes"
"fmt"
"io"
"strconv"
"strings"
"github.com/goccy/go-yaml/ast"
"github.com/goccy/go-yaml/internal/errors"
"github.com/goccy/go-yaml/parser"
"github.com/goccy/go-yaml/printer"
)
// PathString create Path from string
//
// YAMLPath rule
// $ : the root object/element
// . : child operator
// .. : recursive descent
// [num] : object/element of array by number
// [*] : all objects/elements for array.
//
// If you want to use reserved characters such as `.` and `*` as a key name,
// enclose them in single quotation as follows ( $.foo.'bar.baz-*'.hoge ).
// If you want to use a single quote with reserved characters, escape it with `\` ( $.foo.'bar.baz\'s value'.hoge ).
func PathString(s string) (*Path, error) {
buf := []rune(s)
length := len(buf)
cursor := 0
builder := &PathBuilder{}
for cursor < length {
c := buf[cursor]
switch c {
case '$':
builder = builder.Root()
cursor++
case '.':
b, buf, c, err := parsePathDot(builder, buf, cursor)
if err != nil {
return nil, errors.Wrapf(err, "failed to parse path of dot")
}
length = len(buf)
builder = b
cursor = c
case '[':
b, buf, c, err := parsePathIndex(builder, buf, cursor)
if err != nil {
return nil, errors.Wrapf(err, "failed to parse path of index")
}
length = len(buf)
builder = b
cursor = c
default:
return nil, errors.Wrapf(ErrInvalidPathString, "invalid path at %d", cursor)
}
}
return builder.Build(), nil
}
func parsePathRecursive(b *PathBuilder, buf []rune, cursor int) (*PathBuilder, []rune, int, error) {
length := len(buf)
cursor += 2 // skip .. characters
start := cursor
for ; cursor < length; cursor++ {
c := buf[cursor]
switch c {
case '$':
return nil, nil, 0, errors.Wrapf(ErrInvalidPathString, "specified '$' after '..' character")
case '*':
return nil, nil, 0, errors.Wrapf(ErrInvalidPathString, "specified '*' after '..' character")
case '.', '[':
goto end
case ']':
return nil, nil, 0, errors.Wrapf(ErrInvalidPathString, "specified ']' after '..' character")
}
}
end:
if start == cursor {
return nil, nil, 0, errors.Wrapf(ErrInvalidPathString, "not found recursive selector")
}
return b.Recursive(string(buf[start:cursor])), buf, cursor, nil
}
func parsePathDot(b *PathBuilder, buf []rune, cursor int) (*PathBuilder, []rune, int, error) {
length := len(buf)
if cursor+1 < length && buf[cursor+1] == '.' {
b, buf, c, err := parsePathRecursive(b, buf, cursor)
if err != nil {
return nil, nil, 0, errors.Wrapf(err, "failed to parse path of recursive")
}
return b, buf, c, nil
}
cursor++ // skip . character
start := cursor
// if started single quote, looking for end single quote char
if cursor < length && buf[cursor] == '\'' {
return parseQuotedKey(b, buf, cursor)
}
for ; cursor < length; cursor++ {
c := buf[cursor]
switch c {
case '$':
return nil, nil, 0, errors.Wrapf(ErrInvalidPathString, "specified '$' after '.' character")
case '*':
return nil, nil, 0, errors.Wrapf(ErrInvalidPathString, "specified '*' after '.' character")
case '.', '[':
goto end
case ']':
return nil, nil, 0, errors.Wrapf(ErrInvalidPathString, "specified ']' after '.' character")
}
}
end:
if start == cursor {
return nil, nil, 0, errors.Wrapf(ErrInvalidPathString, "cloud not find by empty key")
}
return b.child(string(buf[start:cursor])), buf, cursor, nil
}
func parseQuotedKey(b *PathBuilder, buf []rune, cursor int) (*PathBuilder, []rune, int, error) {
cursor++ // skip single quote
start := cursor
length := len(buf)
var foundEndDelim bool
for ; cursor < length; cursor++ {
switch buf[cursor] {
case '\\':
buf = append(append([]rune{}, buf[:cursor]...), buf[cursor+1:]...)
length = len(buf)
case '\'':
foundEndDelim = true
goto end
}
}
end:
if !foundEndDelim {
return nil, nil, 0, errors.Wrapf(ErrInvalidPathString, "could not find end delimiter for key")
}
if start == cursor {
return nil, nil, 0, errors.Wrapf(ErrInvalidPathString, "could not find by empty key")
}
selector := buf[start:cursor]
cursor++
if cursor < length {
switch buf[cursor] {
case '$':
return nil, nil, 0, errors.Wrapf(ErrInvalidPathString, "specified '$' after '.' character")
case '*':
return nil, nil, 0, errors.Wrapf(ErrInvalidPathString, "specified '*' after '.' character")
case ']':
return nil, nil, 0, errors.Wrapf(ErrInvalidPathString, "specified ']' after '.' character")
}
}
return b.child(string(selector)), buf, cursor, nil
}
func parsePathIndex(b *PathBuilder, buf []rune, cursor int) (*PathBuilder, []rune, int, error) {
length := len(buf)
cursor++ // skip '[' character
if length <= cursor {
return nil, nil, 0, errors.Wrapf(ErrInvalidPathString, "unexpected end of YAML Path")
}
c := buf[cursor]
switch c {
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '*':
start := cursor
cursor++
for ; cursor < length; cursor++ {
c := buf[cursor]
switch c {
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
continue
}
break
}
if buf[cursor] != ']' {
return nil, nil, 0, errors.Wrapf(ErrInvalidPathString, "invalid character %s at %d", string(buf[cursor]), cursor)
}
numOrAll := string(buf[start:cursor])
if numOrAll == "*" {
return b.IndexAll(), buf, cursor + 1, nil
}
num, err := strconv.ParseInt(numOrAll, 10, 64)
if err != nil {
return nil, nil, 0, errors.Wrapf(err, "failed to parse number")
}
return b.Index(uint(num)), buf, cursor + 1, nil
}
return nil, nil, 0, errors.Wrapf(ErrInvalidPathString, "invalid character %s at %d", c, cursor)
}
// Path represent YAMLPath ( like a JSONPath ).
type Path struct {
node pathNode
}
// String path to text.
func (p *Path) String() string {
return p.node.String()
}
// Read decode from r and set extracted value by YAMLPath to v.
func (p *Path) Read(r io.Reader, v interface{}) error {
node, err := p.ReadNode(r)
if err != nil {
return errors.Wrapf(err, "failed to read node")
}
if err := Unmarshal([]byte(node.String()), v); err != nil {
return errors.Wrapf(err, "failed to unmarshal")
}
return nil
}
// ReadNode create AST from r and extract node by YAMLPath.
func (p *Path) ReadNode(r io.Reader) (ast.Node, error) {
if p.node == nil {
return nil, ErrInvalidPath
}
var buf bytes.Buffer
if _, err := io.Copy(&buf, r); err != nil {
return nil, errors.Wrapf(err, "failed to copy from reader")
}
f, err := parser.ParseBytes(buf.Bytes(), 0)
if err != nil {
return nil, errors.Wrapf(err, "failed to parse yaml")
}
node, err := p.FilterFile(f)
if err != nil {
return nil, errors.Wrapf(err, "failed to filter from ast.File")
}
return node, nil
}
// Filter filter from target by YAMLPath and set it to v.
func (p *Path) Filter(target, v interface{}) error {
b, err := Marshal(target)
if err != nil {
return errors.Wrapf(err, "failed to marshal target value")
}
if err := p.Read(bytes.NewBuffer(b), v); err != nil {
return errors.Wrapf(err, "failed to read")
}
return nil
}
// FilterFile filter from ast.File by YAMLPath.
func (p *Path) FilterFile(f *ast.File) (ast.Node, error) {
for _, doc := range f.Docs {
node, err := p.FilterNode(doc.Body)
if err != nil {
return nil, errors.Wrapf(err, "failed to filter node by path ( %s )", p.node)
}
if node != nil {
return node, nil
}
}
return nil, errors.Wrapf(ErrNotFoundNode, "failed to find path ( %s )", p.node)
}
// FilterNode filter from node by YAMLPath.
func (p *Path) FilterNode(node ast.Node) (ast.Node, error) {
n, err := p.node.filter(node)
if err != nil {
return nil, errors.Wrapf(err, "failed to filter node by path ( %s )", p.node)
}
return n, nil
}
// MergeFromReader merge YAML text into ast.File.
func (p *Path) MergeFromReader(dst *ast.File, src io.Reader) error {
var buf bytes.Buffer
if _, err := io.Copy(&buf, src); err != nil {
return errors.Wrapf(err, "failed to copy from reader")
}
file, err := parser.ParseBytes(buf.Bytes(), 0)
if err != nil {
return errors.Wrapf(err, "failed to parse")
}
if err := p.MergeFromFile(dst, file); err != nil {
return errors.Wrapf(err, "failed to merge file")
}
return nil
}
// MergeFromFile merge ast.File into ast.File.
func (p *Path) MergeFromFile(dst *ast.File, src *ast.File) error {
base, err := p.FilterFile(dst)
if err != nil {
return errors.Wrapf(err, "failed to filter file")
}
for _, doc := range src.Docs {
if err := ast.Merge(base, doc); err != nil {
return errors.Wrapf(err, "failed to merge")
}
}
return nil
}
// MergeFromNode merge ast.Node into ast.File.
func (p *Path) MergeFromNode(dst *ast.File, src ast.Node) error {
base, err := p.FilterFile(dst)
if err != nil {
return errors.Wrapf(err, "failed to filter file")
}
if err := ast.Merge(base, src); err != nil {
return errors.Wrapf(err, "failed to merge")
}
return nil
}
// ReplaceWithReader replace ast.File with io.Reader.
func (p *Path) ReplaceWithReader(dst *ast.File, src io.Reader) error {
var buf bytes.Buffer
if _, err := io.Copy(&buf, src); err != nil {
return errors.Wrapf(err, "failed to copy from reader")
}
file, err := parser.ParseBytes(buf.Bytes(), 0)
if err != nil {
return errors.Wrapf(err, "failed to parse")
}
if err := p.ReplaceWithFile(dst, file); err != nil {
return errors.Wrapf(err, "failed to replace file")
}
return nil
}
// ReplaceWithFile replace ast.File with ast.File.
func (p *Path) ReplaceWithFile(dst *ast.File, src *ast.File) error {
for _, doc := range src.Docs {
if err := p.ReplaceWithNode(dst, doc); err != nil {
return errors.Wrapf(err, "failed to replace file by path ( %s )", p.node)
}
}
return nil
}
// ReplaceNode replace ast.File with ast.Node.
func (p *Path) ReplaceWithNode(dst *ast.File, node ast.Node) error {
for _, doc := range dst.Docs {
if node.Type() == ast.DocumentType {
node = node.(*ast.DocumentNode).Body
}
if err := p.node.replace(doc.Body, node); err != nil {
return errors.Wrapf(err, "failed to replace node by path ( %s )", p.node)
}
}
return nil
}
// AnnotateSource add annotation to passed source ( see section 5.1 in README.md ).
func (p *Path) AnnotateSource(source []byte, colored bool) ([]byte, error) {
file, err := parser.ParseBytes([]byte(source), 0)
if err != nil {
return nil, err
}
node, err := p.FilterFile(file)
if err != nil {
return nil, err
}
var pp printer.Printer
return []byte(pp.PrintErrorToken(node.GetToken(), colored)), nil
}
// PathBuilder represent builder for YAMLPath.
type PathBuilder struct {
root *rootNode
node pathNode
}
// Root add '$' to current path.
func (b *PathBuilder) Root() *PathBuilder {
root := newRootNode()
return &PathBuilder{root: root, node: root}
}
// IndexAll add '[*]' to current path.
func (b *PathBuilder) IndexAll() *PathBuilder {
b.node = b.node.chain(newIndexAllNode())
return b
}
// Recursive add '..selector' to current path.
func (b *PathBuilder) Recursive(selector string) *PathBuilder {
b.node = b.node.chain(newRecursiveNode(selector))
return b
}
func (b *PathBuilder) containsReservedPathCharacters(path string) bool {
if strings.Contains(path, ".") {
return true
}
if strings.Contains(path, "*") {
return true
}
return false
}
func (b *PathBuilder) enclosedSingleQuote(name string) bool {
return strings.HasPrefix(name, "'") && strings.HasSuffix(name, "'")
}
func (b *PathBuilder) normalizeSelectorName(name string) string {
if b.enclosedSingleQuote(name) {
// already escaped name
return name
}
if b.containsReservedPathCharacters(name) {
escapedName := strings.ReplaceAll(name, `'`, `\'`)
return "'" + escapedName + "'"
}
return name
}
func (b *PathBuilder) child(name string) *PathBuilder {
b.node = b.node.chain(newSelectorNode(name))
return b
}
// Child add '.name' to current path.
func (b *PathBuilder) Child(name string) *PathBuilder {
return b.child(b.normalizeSelectorName(name))
}
// Index add '[idx]' to current path.
func (b *PathBuilder) Index(idx uint) *PathBuilder {
b.node = b.node.chain(newIndexNode(idx))
return b
}
// Build build YAMLPath.
func (b *PathBuilder) Build() *Path {
return &Path{node: b.root}
}
type pathNode interface {
fmt.Stringer
chain(pathNode) pathNode
filter(ast.Node) (ast.Node, error)
replace(ast.Node, ast.Node) error
}
type basePathNode struct {
child pathNode
}
func (n *basePathNode) chain(node pathNode) pathNode {
n.child = node
return node
}
type rootNode struct {
*basePathNode
}
func newRootNode() *rootNode {
return &rootNode{basePathNode: &basePathNode{}}
}
func (n *rootNode) String() string {
s := "$"
if n.child != nil {
s += n.child.String()
}
return s
}
func (n *rootNode) filter(node ast.Node) (ast.Node, error) {
if n.child == nil {
return nil, nil
}
filtered, err := n.child.filter(node)
if err != nil {
return nil, errors.Wrapf(err, "failed to filter")
}
return filtered, nil
}
func (n *rootNode) replace(node ast.Node, target ast.Node) error {
if n.child == nil {
return nil
}
if err := n.child.replace(node, target); err != nil {
return errors.Wrapf(err, "failed to replace")
}
return nil
}
type selectorNode struct {
*basePathNode
selector string
}
func newSelectorNode(selector string) *selectorNode {
return &selectorNode{
basePathNode: &basePathNode{},
selector: selector,
}
}
func (n *selectorNode) filter(node ast.Node) (ast.Node, error) {
switch node.Type() {
case ast.MappingType:
for _, value := range node.(*ast.MappingNode).Values {
key := value.Key.GetToken().Value
if key == n.selector {
if n.child == nil {
return value.Value, nil
}
filtered, err := n.child.filter(value.Value)
if err != nil {
return nil, errors.Wrapf(err, "failed to filter")
}
return filtered, nil
}
}
case ast.MappingValueType:
value := node.(*ast.MappingValueNode)
key := value.Key.GetToken().Value
if key == n.selector {
if n.child == nil {
return value.Value, nil
}
filtered, err := n.child.filter(value.Value)
if err != nil {
return nil, errors.Wrapf(err, "failed to filter")
}
return filtered, nil
}
default:
return nil, errors.Wrapf(ErrInvalidQuery, "expected node type is map or map value. but got %s", node.Type())
}
return nil, nil
}
func (n *selectorNode) replaceMapValue(value *ast.MappingValueNode, target ast.Node) error {
key := value.Key.GetToken().Value
if key != n.selector {
return nil
}
if n.child == nil {
if err := value.Replace(target); err != nil {
return errors.Wrapf(err, "failed to replace")
}
} else {
if err := n.child.replace(value.Value, target); err != nil {
return errors.Wrapf(err, "failed to replace")
}
}
return nil
}
func (n *selectorNode) replace(node ast.Node, target ast.Node) error {
switch node.Type() {
case ast.MappingType:
for _, value := range node.(*ast.MappingNode).Values {
if err := n.replaceMapValue(value, target); err != nil {
return errors.Wrapf(err, "failed to replace map value")
}
}
case ast.MappingValueType:
value := node.(*ast.MappingValueNode)
if err := n.replaceMapValue(value, target); err != nil {
return errors.Wrapf(err, "failed to replace map value")
}
default:
return errors.Wrapf(ErrInvalidQuery, "expected node type is map or map value. but got %s", node.Type())
}
return nil
}
func (n *selectorNode) String() string {
s := fmt.Sprintf(".%s", n.selector)
if n.child != nil {
s += n.child.String()
}
return s
}
type indexNode struct {
*basePathNode
selector uint
}
func newIndexNode(selector uint) *indexNode {
return &indexNode{
basePathNode: &basePathNode{},
selector: selector,
}
}
func (n *indexNode) filter(node ast.Node) (ast.Node, error) {
if node.Type() != ast.SequenceType {
return nil, errors.Wrapf(ErrInvalidQuery, "expected sequence type node. but got %s", node.Type())
}
sequence := node.(*ast.SequenceNode)
if n.selector >= uint(len(sequence.Values)) {
return nil, errors.Wrapf(ErrInvalidQuery, "expected index is %d. but got sequences has %d items", n.selector, sequence.Values)
}
value := sequence.Values[n.selector]
if n.child == nil {
return value, nil
}
filtered, err := n.child.filter(value)
if err != nil {
return nil, errors.Wrapf(err, "failed to filter")
}
return filtered, nil
}
func (n *indexNode) replace(node ast.Node, target ast.Node) error {
if node.Type() != ast.SequenceType {
return errors.Wrapf(ErrInvalidQuery, "expected sequence type node. but got %s", node.Type())
}
sequence := node.(*ast.SequenceNode)
if n.selector >= uint(len(sequence.Values)) {
return errors.Wrapf(ErrInvalidQuery, "expected index is %d. but got sequences has %d items", n.selector, sequence.Values)
}
if n.child == nil {
if err := sequence.Replace(int(n.selector), target); err != nil {
return errors.Wrapf(err, "failed to replace")
}
return nil
}
if err := n.child.replace(sequence.Values[n.selector], target); err != nil {
return errors.Wrapf(err, "failed to replace")
}
return nil
}
func (n *indexNode) String() string {
s := fmt.Sprintf("[%d]", n.selector)
if n.child != nil {
s += n.child.String()
}
return s
}
type indexAllNode struct {
*basePathNode
}
func newIndexAllNode() *indexAllNode {
return &indexAllNode{
basePathNode: &basePathNode{},
}
}
func (n *indexAllNode) String() string {
s := "[*]"
if n.child != nil {
s += n.child.String()
}
return s
}
func (n *indexAllNode) filter(node ast.Node) (ast.Node, error) {
if node.Type() != ast.SequenceType {
return nil, errors.Wrapf(ErrInvalidQuery, "expected sequence type node. but got %s", node.Type())
}
sequence := node.(*ast.SequenceNode)
if n.child == nil {
return sequence, nil
}
out := *sequence
out.Values = []ast.Node{}
for _, value := range sequence.Values {
filtered, err := n.child.filter(value)
if err != nil {
return nil, errors.Wrapf(err, "failed to filter")
}
out.Values = append(out.Values, filtered)
}
return &out, nil
}
func (n *indexAllNode) replace(node ast.Node, target ast.Node) error {
if node.Type() != ast.SequenceType {
return errors.Wrapf(ErrInvalidQuery, "expected sequence type node. but got %s", node.Type())
}
sequence := node.(*ast.SequenceNode)
if n.child == nil {
for idx := range sequence.Values {
if err := sequence.Replace(idx, target); err != nil {
return errors.Wrapf(err, "failed to replace")
}
}
return nil
}
for _, value := range sequence.Values {
if err := n.child.replace(value, target); err != nil {
return errors.Wrapf(err, "failed to replace")
}
}
return nil
}
type recursiveNode struct {
*basePathNode
selector string
}
func newRecursiveNode(selector string) *recursiveNode {
return &recursiveNode{
basePathNode: &basePathNode{},
selector: selector,
}
}
func (n *recursiveNode) String() string {
s := fmt.Sprintf("..%s", n.selector)
if n.child != nil {
s += n.child.String()
}
return s
}
func (n *recursiveNode) filterNode(node ast.Node) (*ast.SequenceNode, error) {
sequence := &ast.SequenceNode{BaseNode: &ast.BaseNode{}}
switch typedNode := node.(type) {
case *ast.MappingNode:
for _, value := range typedNode.Values {
seq, err := n.filterNode(value)
if err != nil {
return nil, errors.Wrapf(err, "failed to filter")
}
sequence.Values = append(sequence.Values, seq.Values...)
}
case *ast.MappingValueNode:
key := typedNode.Key.GetToken().Value
if n.selector == key {
sequence.Values = append(sequence.Values, typedNode.Value)
}
seq, err := n.filterNode(typedNode.Value)
if err != nil {
return nil, errors.Wrapf(err, "failed to filter")
}
sequence.Values = append(sequence.Values, seq.Values...)
case *ast.SequenceNode:
for _, value := range typedNode.Values {
seq, err := n.filterNode(value)
if err != nil {
return nil, errors.Wrapf(err, "failed to filter")
}
sequence.Values = append(sequence.Values, seq.Values...)
}
}
return sequence, nil
}
func (n *recursiveNode) filter(node ast.Node) (ast.Node, error) {
sequence, err := n.filterNode(node)
if err != nil {
return nil, errors.Wrapf(err, "failed to filter")
}
sequence.Start = node.GetToken()
return sequence, nil
}
func (n *recursiveNode) replaceNode(node ast.Node, target ast.Node) error {
switch typedNode := node.(type) {
case *ast.MappingNode:
for _, value := range typedNode.Values {
if err := n.replaceNode(value, target); err != nil {
return errors.Wrapf(err, "failed to replace")
}
}
case *ast.MappingValueNode:
key := typedNode.Key.GetToken().Value
if n.selector == key {
if err := typedNode.Replace(target); err != nil {
return errors.Wrapf(err, "failed to replace")
}
}
if err := n.replaceNode(typedNode.Value, target); err != nil {
return errors.Wrapf(err, "failed to replace")
}
case *ast.SequenceNode:
for _, value := range typedNode.Values {
if err := n.replaceNode(value, target); err != nil {
return errors.Wrapf(err, "failed to replace")
}
}
}
return nil
}
func (n *recursiveNode) replace(node ast.Node, target ast.Node) error {
if err := n.replaceNode(node, target); err != nil {
return errors.Wrapf(err, "failed to replace")
}
return nil
}
golang-github-goccy-go-yaml-1.9.5/path_test.go 0000664 0000000 0000000 00000031010 14167531274 0021251 0 ustar 00root root 0000000 0000000 package yaml_test
import (
"fmt"
"log"
"reflect"
"strings"
"testing"
"github.com/goccy/go-yaml"
"github.com/goccy/go-yaml/parser"
)
func builder() *yaml.PathBuilder { return &yaml.PathBuilder{} }
func TestPathBuilder(t *testing.T) {
tests := []struct {
expected string
path *yaml.Path
}{
{
expected: `$.a.b[0]`,
path: builder().Root().Child("a").Child("b").Index(0).Build(),
},
{
expected: `$.'a.b'.'c*d'`,
path: builder().Root().Child("a.b").Child("c*d").Build(),
},
{
expected: `$.'a.b-*'.c`,
path: builder().Root().Child("a.b-*").Child("c").Build(),
},
{
expected: `$.'a'.b`,
path: builder().Root().Child("'a'").Child("b").Build(),
},
{
expected: `$.'a.b'.c`,
path: builder().Root().Child("'a.b'").Child("c").Build(),
},
}
for _, test := range tests {
t.Run(test.expected, func(t *testing.T) {
expected := test.expected
got := test.path.String()
if expected != got {
t.Fatalf("failed to build path. expected:[%q] but got:[%q]", expected, got)
}
})
}
}
func TestPath(t *testing.T) {
yml := `
store:
book:
- author: john
price: 10
- author: ken
price: 12
bicycle:
color: red
price: 19.95
`
tests := []struct {
name string
path *yaml.Path
expected interface{}
}{
{
name: "$.store.book[0].author",
path: builder().Root().Child("store").Child("book").Index(0).Child("author").Build(),
expected: "john",
},
{
name: "$.store.book[1].price",
path: builder().Root().Child("store").Child("book").Index(1).Child("price").Build(),
expected: uint64(12),
},
{
name: "$.store.book[*].author",
path: builder().Root().Child("store").Child("book").IndexAll().Child("author").Build(),
expected: []interface{}{"john", "ken"},
},
{
name: "$.store.book[0]",
path: builder().Root().Child("store").Child("book").Index(0).Build(),
expected: map[string]interface{}{"author": "john", "price": uint64(10)},
},
{
name: "$..author",
path: builder().Root().Recursive("author").Build(),
expected: []interface{}{"john", "ken"},
},
{
name: "$.store.bicycle.price",
path: builder().Root().Child("store").Child("bicycle").Child("price").Build(),
expected: float64(19.95),
},
}
t.Run("PathString", func(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
path, err := yaml.PathString(test.name)
if err != nil {
t.Fatalf("%+v", err)
}
if test.name != path.String() {
t.Fatalf("expected %s but actual %s", test.name, path.String())
}
})
}
})
t.Run("string", func(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
if test.name != test.path.String() {
t.Fatalf("expected %s but actual %s", test.name, test.path.String())
}
})
}
})
t.Run("read", func(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
var v interface{}
if err := test.path.Read(strings.NewReader(yml), &v); err != nil {
t.Fatalf("%+v", err)
}
if !reflect.DeepEqual(test.expected, v) {
t.Fatalf("expected %v(%T). but actual %v(%T)", test.expected, test.expected, v, v)
}
})
}
})
t.Run("filter", func(t *testing.T) {
var target interface{}
if err := yaml.Unmarshal([]byte(yml), &target); err != nil {
t.Fatalf("failed to unmarshal: %+v", err)
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
var v interface{}
if err := test.path.Filter(target, &v); err != nil {
t.Fatalf("%+v", err)
}
if !reflect.DeepEqual(test.expected, v) {
t.Fatalf("expected %v(%T). but actual %v(%T)", test.expected, test.expected, v, v)
}
})
}
})
}
func TestPath_ReservedKeyword(t *testing.T) {
tests := []struct {
name string
path string
src string
expected interface{}
failure bool
}{
{
name: "quoted path",
path: `$.'a.b.c'.foo`,
src: `
a.b.c:
foo: bar
`,
expected: "bar",
},
{
name: "contains quote key",
path: `$.a'b`,
src: `a'b: 10`,
expected: uint64(10),
},
{
name: "escaped quote",
path: `$.'alice\'s age'`,
src: `alice's age: 10`,
expected: uint64(10),
},
{
name: "directly use white space",
path: `$.a b`,
src: `a b: 10`,
expected: uint64(10),
},
{
name: "empty quoted key",
path: `$.''`,
src: `a: 10`,
failure: true,
},
{
name: "unterminated quote",
path: `$.'abcd`,
src: `abcd: 10`,
failure: true,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
path, err := yaml.PathString(test.path)
if test.failure {
if err == nil {
t.Fatal("expected error")
}
return
} else {
if err != nil {
t.Fatalf("%+v", err)
}
}
file, err := parser.ParseBytes([]byte(test.src), 0)
if err != nil {
t.Fatal(err)
}
var v interface{}
if err := path.Read(file, &v); err != nil {
t.Fatalf("%+v", err)
}
if v != test.expected {
t.Fatalf("failed to get value. expected:[%v] but got:[%v]", test.expected, v)
}
})
}
}
func TestPath_Invalid(t *testing.T) {
tests := []struct {
path string
src string
}{
{
path: "$.wrong",
src: "foo: bar",
},
}
for _, test := range tests {
path, err := yaml.PathString(test.path)
if err != nil {
t.Fatal(err)
}
t.Run("path.Read", func(t *testing.T) {
file, err := parser.ParseBytes([]byte(test.src), 0)
if err != nil {
t.Fatal(err)
}
var v interface{}
err = path.Read(file, &v)
if err == nil {
t.Fatal("expected error")
}
if !yaml.IsNotFoundNodeError(err) {
t.Fatalf("unexpected error %s", err)
}
})
t.Run("path.ReadNode", func(t *testing.T) {
file, err := parser.ParseBytes([]byte(test.src), 0)
if err != nil {
t.Fatal(err)
}
_, err = path.ReadNode(file)
if err == nil {
t.Fatal("expected error")
}
if !yaml.IsNotFoundNodeError(err) {
t.Fatalf("unexpected error %s", err)
}
})
}
}
func TestPath_Merge(t *testing.T) {
tests := []struct {
path string
dst string
src string
expected string
}{
{
"$.c",
`
a: 1
b: 2
c:
d: 3
e: 4
`,
`
f: 5
g: 6
`,
`
a: 1
b: 2
c:
d: 3
e: 4
f: 5
g: 6
`,
},
{
"$.a.b",
`
a:
b:
- 1
- 2
`,
`
- 3
- map:
- 4
- 5
`,
`
a:
b:
- 1
- 2
- 3
- map:
- 4
- 5
`,
},
}
for _, test := range tests {
t.Run(test.path, func(t *testing.T) {
path, err := yaml.PathString(test.path)
if err != nil {
t.Fatalf("%+v", err)
}
t.Run("FromReader", func(t *testing.T) {
file, err := parser.ParseBytes([]byte(test.dst), 0)
if err != nil {
t.Fatalf("%+v", err)
}
if err := path.MergeFromReader(file, strings.NewReader(test.src)); err != nil {
t.Fatalf("%+v", err)
}
actual := "\n" + file.String() + "\n"
if test.expected != actual {
t.Fatalf("expected: %q. but got %q", test.expected, actual)
}
})
t.Run("FromFile", func(t *testing.T) {
file, err := parser.ParseBytes([]byte(test.dst), 0)
if err != nil {
t.Fatalf("%+v", err)
}
src, err := parser.ParseBytes([]byte(test.src), 0)
if err != nil {
t.Fatalf("%+v", err)
}
if err := path.MergeFromFile(file, src); err != nil {
t.Fatalf("%+v", err)
}
actual := "\n" + file.String() + "\n"
if test.expected != actual {
t.Fatalf("expected: %q. but got %q", test.expected, actual)
}
})
t.Run("FromNode", func(t *testing.T) {
file, err := parser.ParseBytes([]byte(test.dst), 0)
if err != nil {
t.Fatalf("%+v", err)
}
src, err := parser.ParseBytes([]byte(test.src), 0)
if err != nil {
t.Fatalf("%+v", err)
}
if len(src.Docs) == 0 {
t.Fatalf("failed to parse")
}
if err := path.MergeFromNode(file, src.Docs[0]); err != nil {
t.Fatalf("%+v", err)
}
actual := "\n" + file.String() + "\n"
if test.expected != actual {
t.Fatalf("expected: %q. but got %q", test.expected, actual)
}
})
})
}
}
func TestPath_Replace(t *testing.T) {
tests := []struct {
path string
dst string
src string
expected string
}{
{
"$.a",
`
a: 1
b: 2
`,
`3`,
`
a: 3
b: 2
`,
},
{
"$.b",
`
b: 1
c: 2
`,
`
d: e
f:
g: h
i: j
`,
`
b:
d: e
f:
g: h
i: j
c: 2
`,
},
{
"$.a.b[0]",
`
a:
b:
- hello
c: 2
`,
`world`,
`
a:
b:
- world
c: 2
`,
},
{
"$.books[*].author",
`
books:
- name: book_a
author: none
- name: book_b
author: none
pictures:
- name: picture_a
author: none
- name: picture_b
author: none
building:
author: none
`,
`ken`,
`
books:
- name: book_a
author: ken
- name: book_b
author: ken
pictures:
- name: picture_a
author: none
- name: picture_b
author: none
building:
author: none
`,
},
{
"$..author",
`
books:
- name: book_a
author: none
- name: book_b
author: none
pictures:
- name: picture_a
author: none
- name: picture_b
author: none
building:
author: none
`,
`ken`,
`
books:
- name: book_a
author: ken
- name: book_b
author: ken
pictures:
- name: picture_a
author: ken
- name: picture_b
author: ken
building:
author: ken
`,
},
}
for _, test := range tests {
t.Run(test.path, func(t *testing.T) {
path, err := yaml.PathString(test.path)
if err != nil {
t.Fatalf("%+v", err)
}
t.Run("WithReader", func(t *testing.T) {
file, err := parser.ParseBytes([]byte(test.dst), 0)
if err != nil {
t.Fatalf("%+v", err)
}
if err := path.ReplaceWithReader(file, strings.NewReader(test.src)); err != nil {
t.Fatalf("%+v", err)
}
actual := "\n" + file.String() + "\n"
if test.expected != actual {
t.Fatalf("expected: %q. but got %q", test.expected, actual)
}
})
t.Run("WithFile", func(t *testing.T) {
file, err := parser.ParseBytes([]byte(test.dst), 0)
if err != nil {
t.Fatalf("%+v", err)
}
src, err := parser.ParseBytes([]byte(test.src), 0)
if err != nil {
t.Fatalf("%+v", err)
}
if err := path.ReplaceWithFile(file, src); err != nil {
t.Fatalf("%+v", err)
}
actual := "\n" + file.String() + "\n"
if test.expected != actual {
t.Fatalf("expected: %q. but got %q", test.expected, actual)
}
})
t.Run("WithNode", func(t *testing.T) {
file, err := parser.ParseBytes([]byte(test.dst), 0)
if err != nil {
t.Fatalf("%+v", err)
}
src, err := parser.ParseBytes([]byte(test.src), 0)
if err != nil {
t.Fatalf("%+v", err)
}
if len(src.Docs) == 0 {
t.Fatalf("failed to parse")
}
if err := path.ReplaceWithNode(file, src.Docs[0]); err != nil {
t.Fatalf("%+v", err)
}
actual := "\n" + file.String() + "\n"
if test.expected != actual {
t.Fatalf("expected: %q. but got %q", test.expected, actual)
}
})
})
}
}
func ExamplePath_AnnotateSource() {
yml := `
a: 1
b: "hello"
`
var v struct {
A int
B string
}
if err := yaml.Unmarshal([]byte(yml), &v); err != nil {
panic(err)
}
if v.A != 2 {
// output error with YAML source
path, err := yaml.PathString("$.a")
if err != nil {
log.Fatal(err)
}
source, err := path.AnnotateSource([]byte(yml), false)
if err != nil {
log.Fatal(err)
}
fmt.Printf("a value expected 2 but actual %d:\n%s\n", v.A, string(source))
}
// OUTPUT:
// a value expected 2 but actual 1:
// > 2 | a: 1
// ^
// 3 | b: "hello"
}
func ExamplePath_AnnotateSourceWithComment() {
yml := `
# This is my document
doc:
# This comment should be line 3
map:
# And below should be line 5
- value1
- value2
other: value3
`
path, err := yaml.PathString("$.doc.map[0]")
if err != nil {
log.Fatal(err)
}
msg, err := path.AnnotateSource([]byte(yml), false)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(msg))
// OUTPUT:
// 4 | # This comment should be line 3
// 5 | map:
// 6 | # And below should be line 5
// > 7 | - value1
// ^
// 8 | - value2
// 9 | other: value3
// 10 |
}
func ExamplePath_PathString() {
yml := `
store:
book:
- author: john
price: 10
- author: ken
price: 12
bicycle:
color: red
price: 19.95
`
path, err := yaml.PathString("$.store.book[*].author")
if err != nil {
log.Fatal(err)
}
var authors []string
if err := path.Read(strings.NewReader(yml), &authors); err != nil {
log.Fatal(err)
}
fmt.Println(authors)
// OUTPUT:
// [john ken]
}
golang-github-goccy-go-yaml-1.9.5/printer/ 0000775 0000000 0000000 00000000000 14167531274 0020417 5 ustar 00root root 0000000 0000000 golang-github-goccy-go-yaml-1.9.5/printer/printer.go 0000664 0000000 0000000 00000017617 14167531274 0022445 0 ustar 00root root 0000000 0000000 package printer
import (
"fmt"
"math"
"strings"
"github.com/fatih/color"
"github.com/goccy/go-yaml/ast"
"github.com/goccy/go-yaml/token"
)
// Property additional property set for each the token
type Property struct {
Prefix string
Suffix string
}
// PrintFunc returns property instance
type PrintFunc func() *Property
// Printer create text from token collection or ast
type Printer struct {
LineNumber bool
LineNumberFormat func(num int) string
MapKey PrintFunc
Anchor PrintFunc
Alias PrintFunc
Bool PrintFunc
String PrintFunc
Number PrintFunc
}
func defaultLineNumberFormat(num int) string {
return fmt.Sprintf("%2d | ", num)
}
func (p *Printer) property(tk *token.Token) *Property {
prop := &Property{}
switch tk.PreviousType() {
case token.AnchorType:
if p.Anchor != nil {
return p.Anchor()
}
return prop
case token.AliasType:
if p.Alias != nil {
return p.Alias()
}
return prop
}
switch tk.NextType() {
case token.MappingValueType:
if p.MapKey != nil {
return p.MapKey()
}
return prop
}
switch tk.Type {
case token.BoolType:
if p.Bool != nil {
return p.Bool()
}
return prop
case token.AnchorType:
if p.Anchor != nil {
return p.Anchor()
}
return prop
case token.AliasType:
if p.Anchor != nil {
return p.Alias()
}
return prop
case token.StringType, token.SingleQuoteType, token.DoubleQuoteType:
if p.String != nil {
return p.String()
}
return prop
case token.IntegerType, token.FloatType:
if p.Number != nil {
return p.Number()
}
return prop
default:
}
return prop
}
// PrintTokens create text from token collection
func (p *Printer) PrintTokens(tokens token.Tokens) string {
if len(tokens) == 0 {
return ""
}
if p.LineNumber {
if p.LineNumberFormat == nil {
p.LineNumberFormat = defaultLineNumberFormat
}
}
texts := []string{}
lineNumber := tokens[0].Position.Line
for _, tk := range tokens {
lines := strings.Split(tk.Origin, "\n")
prop := p.property(tk)
header := ""
if p.LineNumber {
header = p.LineNumberFormat(lineNumber)
}
if len(lines) == 1 {
line := prop.Prefix + lines[0] + prop.Suffix
if len(texts) == 0 {
texts = append(texts, header+line)
lineNumber++
} else {
text := texts[len(texts)-1]
texts[len(texts)-1] = text + line
}
} else {
for idx, src := range lines {
if p.LineNumber {
header = p.LineNumberFormat(lineNumber)
}
line := prop.Prefix + src + prop.Suffix
if idx == 0 {
if len(texts) == 0 {
texts = append(texts, header+line)
lineNumber++
} else {
text := texts[len(texts)-1]
texts[len(texts)-1] = text + line
}
} else {
texts = append(texts, fmt.Sprintf("%s%s", header, line))
lineNumber++
}
}
}
}
return strings.Join(texts, "\n")
}
// PrintNode create text from ast.Node
func (p *Printer) PrintNode(node ast.Node) []byte {
return []byte(fmt.Sprintf("%+v\n", node))
}
const escape = "\x1b"
func format(attr color.Attribute) string {
return fmt.Sprintf("%s[%dm", escape, attr)
}
func (p *Printer) setDefaultColorSet() {
p.Bool = func() *Property {
return &Property{
Prefix: format(color.FgHiMagenta),
Suffix: format(color.Reset),
}
}
p.Number = func() *Property {
return &Property{
Prefix: format(color.FgHiMagenta),
Suffix: format(color.Reset),
}
}
p.MapKey = func() *Property {
return &Property{
Prefix: format(color.FgHiCyan),
Suffix: format(color.Reset),
}
}
p.Anchor = func() *Property {
return &Property{
Prefix: format(color.FgHiYellow),
Suffix: format(color.Reset),
}
}
p.Alias = func() *Property {
return &Property{
Prefix: format(color.FgHiYellow),
Suffix: format(color.Reset),
}
}
p.String = func() *Property {
return &Property{
Prefix: format(color.FgHiGreen),
Suffix: format(color.Reset),
}
}
}
func (p *Printer) PrintErrorMessage(msg string, isColored bool) string {
if isColored {
return fmt.Sprintf("%s%s%s",
format(color.FgHiRed),
msg,
format(color.Reset),
)
}
return msg
}
func (p *Printer) removeLeftSideNewLineChar(src string) string {
return strings.TrimLeft(strings.TrimLeft(strings.TrimLeft(src, "\r"), "\n"), "\r\n")
}
func (p *Printer) removeRightSideNewLineChar(src string) string {
return strings.TrimRight(strings.TrimRight(strings.TrimRight(src, "\r"), "\n"), "\r\n")
}
func (p *Printer) removeRightSideWhiteSpaceChar(src string) string {
return p.removeRightSideNewLineChar(strings.TrimRight(src, " "))
}
func (p *Printer) newLineCount(s string) int {
src := []rune(s)
size := len(src)
cnt := 0
for i := 0; i < size; i++ {
c := src[i]
switch c {
case '\r':
if i+1 < size && src[i+1] == '\n' {
i++
}
cnt++
case '\n':
cnt++
}
}
return cnt
}
func (p *Printer) isNewLineLastChar(s string) bool {
for i := len(s) - 1; i > 0; i-- {
c := s[i]
switch c {
case ' ':
continue
case '\n', '\r':
return true
}
break
}
return false
}
func (p *Printer) printBeforeTokens(tk *token.Token, minLine, extLine int) token.Tokens {
for {
if tk.Prev == nil {
break
}
if tk.Prev.Position.Line < minLine {
break
}
tk = tk.Prev
}
minTk := tk.Clone()
if minTk.Prev != nil {
// add white spaces to minTk by prev token
prev := minTk.Prev
whiteSpaceLen := len(prev.Origin) - len(strings.TrimRight(prev.Origin, " "))
minTk.Origin = strings.Repeat(" ", whiteSpaceLen) + minTk.Origin
}
minTk.Origin = p.removeLeftSideNewLineChar(minTk.Origin)
tokens := token.Tokens{minTk}
tk = minTk.Next
for tk != nil && tk.Position.Line <= extLine {
clonedTk := tk.Clone()
tokens.Add(clonedTk)
tk = clonedTk.Next
}
lastTk := tokens[len(tokens)-1]
trimmedOrigin := p.removeRightSideWhiteSpaceChar(lastTk.Origin)
suffix := lastTk.Origin[len(trimmedOrigin):]
lastTk.Origin = trimmedOrigin
if lastTk.Next != nil && len(suffix) > 1 {
next := lastTk.Next.Clone()
// add suffix to header of next token
if suffix[0] == '\n' || suffix[0] == '\r' {
suffix = suffix[1:]
}
next.Origin = suffix + next.Origin
lastTk.Next = next
}
return tokens
}
func (p *Printer) printAfterTokens(tk *token.Token, maxLine int) token.Tokens {
tokens := token.Tokens{}
if tk == nil {
return tokens
}
if tk.Position.Line > maxLine {
return tokens
}
minTk := tk.Clone()
minTk.Origin = p.removeLeftSideNewLineChar(minTk.Origin)
tokens.Add(minTk)
tk = minTk.Next
for tk != nil && tk.Position.Line <= maxLine {
clonedTk := tk.Clone()
tokens.Add(clonedTk)
tk = clonedTk.Next
}
return tokens
}
func (p *Printer) setupErrorTokenFormat(annotateLine int, isColored bool) {
prefix := func(annotateLine, num int) string {
if annotateLine == num {
return fmt.Sprintf("> %2d | ", num)
}
return fmt.Sprintf(" %2d | ", num)
}
p.LineNumber = true
p.LineNumberFormat = func(num int) string {
if isColored {
fn := color.New(color.Bold, color.FgHiWhite).SprintFunc()
return fn(prefix(annotateLine, num))
}
return prefix(annotateLine, num)
}
if isColored {
p.setDefaultColorSet()
}
}
func (p *Printer) PrintErrorToken(tk *token.Token, isColored bool) string {
errToken := tk
curLine := tk.Position.Line
curExtLine := curLine + p.newLineCount(p.removeLeftSideNewLineChar(tk.Origin))
if p.isNewLineLastChar(tk.Origin) {
// if last character ( exclude white space ) is new line character, ignore it.
curExtLine--
}
minLine := int(math.Max(float64(curLine-3), 1))
maxLine := curExtLine + 3
p.setupErrorTokenFormat(curLine, isColored)
beforeTokens := p.printBeforeTokens(tk, minLine, curExtLine)
lastTk := beforeTokens[len(beforeTokens)-1]
afterTokens := p.printAfterTokens(lastTk.Next, maxLine)
beforeSource := p.PrintTokens(beforeTokens)
prefixSpaceNum := len(fmt.Sprintf(" %2d | ", curLine))
annotateLine := strings.Repeat(" ", prefixSpaceNum+errToken.Position.Column-1) + "^"
afterSource := p.PrintTokens(afterTokens)
return fmt.Sprintf("%s\n%s\n%s", beforeSource, annotateLine, afterSource)
}
golang-github-goccy-go-yaml-1.9.5/printer/printer_test.go 0000664 0000000 0000000 00000010136 14167531274 0023471 0 ustar 00root root 0000000 0000000 package printer_test
import (
"fmt"
"testing"
"github.com/goccy/go-yaml/lexer"
"github.com/goccy/go-yaml/printer"
)
func Test_Printer(t *testing.T) {
yml := `---
text: aaaa
text2: aaaa
bbbb
cccc
dddd
eeee
text3: ffff
gggg
hhhh
iiii
jjjj
bool: true
number: 10
anchor: &x 1
alias: *x
`
t.Run("print starting from tokens[3]", func(t *testing.T) {
tokens := lexer.Tokenize(yml)
var p printer.Printer
actual := "\n" + p.PrintErrorToken(tokens[3], false)
expect := `
1 | ---
> 2 | text: aaaa
^
3 | text2: aaaa
4 | bbbb
5 | cccc
6 | dddd
7 | eeee
8 | `
if actual != expect {
t.Fatalf("unexpected output: expect:[%s]\n actual:[%s]", expect, actual)
}
})
t.Run("print starting from tokens[4]", func(t *testing.T) {
tokens := lexer.Tokenize(yml)
var p printer.Printer
actual := "\n" + p.PrintErrorToken(tokens[4], false)
expect := `
1 | ---
2 | text: aaaa
> 3 | text2: aaaa
4 | bbbb
5 | cccc
6 | dddd
7 | eeee
^
`
if actual != expect {
t.Fatalf("unexpected output: expect:[%s]\n actual:[%s]", expect, actual)
}
})
t.Run("print starting from tokens[6]", func(t *testing.T) {
tokens := lexer.Tokenize(yml)
var p printer.Printer
actual := "\n" + p.PrintErrorToken(tokens[6], false)
expect := `
1 | ---
2 | text: aaaa
> 3 | text2: aaaa
4 | bbbb
5 | cccc
6 | dddd
7 | eeee
^
8 | text3: ffff
9 | gggg
10 | hhhh
11 | iiii
12 | jjjj
13 | `
if actual != expect {
t.Fatalf("unexpected output: expect:[%s]\n actual:[%s]", expect, actual)
}
})
t.Run("print error token with document header", func(t *testing.T) {
tokens := lexer.Tokenize(`---
a:
b:
c:
d: e
f: g
h: i
---
`)
expect := `
3 | b:
4 | c:
5 | d: e
> 6 | f: g
^
7 | h: i
8 |
9 | ---`
var p printer.Printer
actual := "\n" + p.PrintErrorToken(tokens[12], false)
if actual != expect {
t.Fatalf("unexpected output: expect:[%s]\n actual:[%s]", expect, actual)
}
})
t.Run("output with color", func(t *testing.T) {
t.Run("token6", func(t *testing.T) {
tokens := lexer.Tokenize(yml)
var p printer.Printer
t.Logf("\n%s", p.PrintErrorToken(tokens[6], true))
})
t.Run("token9", func(t *testing.T) {
tokens := lexer.Tokenize(yml)
var p printer.Printer
t.Logf("\n%s", p.PrintErrorToken(tokens[9], true))
})
t.Run("token12", func(t *testing.T) {
tokens := lexer.Tokenize(yml)
var p printer.Printer
t.Logf("\n%s", p.PrintErrorToken(tokens[12], true))
})
})
t.Run("print error message", func(t *testing.T) {
var p printer.Printer
src := "message"
msg := p.PrintErrorMessage(src, false)
if msg != src {
t.Fatal("unexpected result")
}
p.PrintErrorMessage(src, true)
})
}
func TestPrinter_Anchor(t *testing.T) {
expected := `
anchor: &x 1
alias: *x`
tokens := lexer.Tokenize(expected)
var p printer.Printer
got := p.PrintTokens(tokens)
if expected != got {
t.Fatalf("unexpected output: expect:[%s]\n actual:[%s]", expected, got)
}
}
func Test_Printer_Multiline(t *testing.T) {
yml := `
text1: 'aaaa
bbbb
cccc'
text2: "ffff
gggg
hhhh"
text3: hello
`
tc := []struct {
token int
want string
}{
{
token: 2,
want: `
> 2 | text1: 'aaaa
3 | bbbb
4 | cccc'
^
5 | text2: "ffff
6 | gggg
7 | hhhh"`,
},
{token: 3,
want: `
2 | text1: 'aaaa
3 | bbbb
4 | cccc'
> 5 | text2: "ffff
6 | gggg
7 | hhhh"
^
8 | text3: hello`,
},
{token: 5,
want: `
2 | text1: 'aaaa
3 | bbbb
4 | cccc'
> 5 | text2: "ffff
6 | gggg
7 | hhhh"
^
8 | text3: hello`,
},
{token: 6,
want: `
5 | text2: "ffff
6 | gggg
7 | hhhh"
> 8 | text3: hello
^
`,
},
}
for _, tt := range tc {
name := fmt.Sprintf("print starting from tokens[%d]", tt.token)
t.Run(name, func(t *testing.T) {
tokens := lexer.Tokenize(yml)
var p printer.Printer
got := "\n" + p.PrintErrorToken(tokens[tt.token], false)
want := tt.want
if got != want {
t.Fatalf("PrintErrorToken() got: %s\n want:%s\n", want, got)
}
})
}
}
golang-github-goccy-go-yaml-1.9.5/scanner/ 0000775 0000000 0000000 00000000000 14167531274 0020365 5 ustar 00root root 0000000 0000000 golang-github-goccy-go-yaml-1.9.5/scanner/context.go 0000664 0000000 0000000 00000006765 14167531274 0022416 0 ustar 00root root 0000000 0000000 package scanner
import (
"sync"
"github.com/goccy/go-yaml/token"
)
// Context context at scanning
type Context struct {
idx int
size int
notSpaceCharPos int
notSpaceOrgCharPos int
src []rune
buf []rune
obuf []rune
tokens token.Tokens
isRawFolded bool
isLiteral bool
isFolded bool
isSingleLine bool
literalOpt string
}
var (
ctxPool = sync.Pool{
New: func() interface{} {
return createContext()
},
}
)
func createContext() *Context {
return &Context{
idx: 0,
tokens: token.Tokens{},
isSingleLine: true,
}
}
func newContext(src []rune) *Context {
ctx := ctxPool.Get().(*Context)
ctx.reset(src)
return ctx
}
func (c *Context) release() {
ctxPool.Put(c)
}
func (c *Context) reset(src []rune) {
c.idx = 0
c.size = len(src)
c.src = src
c.tokens = c.tokens[:0]
c.resetBuffer()
c.isSingleLine = true
}
func (c *Context) resetBuffer() {
c.buf = c.buf[:0]
c.obuf = c.obuf[:0]
c.notSpaceCharPos = 0
c.notSpaceOrgCharPos = 0
}
func (c *Context) isSaveIndentMode() bool {
return c.isLiteral || c.isFolded || c.isRawFolded
}
func (c *Context) breakLiteral() {
c.isLiteral = false
c.isRawFolded = false
c.isFolded = false
c.literalOpt = ""
}
func (c *Context) addToken(tk *token.Token) {
if tk == nil {
return
}
c.tokens = append(c.tokens, tk)
}
func (c *Context) addBuf(r rune) {
if len(c.buf) == 0 && r == ' ' {
return
}
c.buf = append(c.buf, r)
if r != ' ' && r != '\t' {
c.notSpaceCharPos = len(c.buf)
}
}
func (c *Context) addOriginBuf(r rune) {
c.obuf = append(c.obuf, r)
if r != ' ' && r != '\t' {
c.notSpaceOrgCharPos = len(c.obuf)
}
}
func (c *Context) removeRightSpaceFromBuf() int {
trimmedBuf := c.obuf[:c.notSpaceOrgCharPos]
buflen := len(trimmedBuf)
diff := len(c.obuf) - buflen
if diff > 0 {
c.obuf = c.obuf[:buflen]
c.buf = c.bufferedSrc()
}
return diff
}
func (c *Context) isDocument() bool {
return c.isLiteral || c.isFolded || c.isRawFolded
}
func (c *Context) isEOS() bool {
return len(c.src)-1 <= c.idx
}
func (c *Context) isNextEOS() bool {
return len(c.src)-1 <= c.idx+1
}
func (c *Context) next() bool {
return c.idx < c.size
}
func (c *Context) source(s, e int) string {
return string(c.src[s:e])
}
func (c *Context) previousChar() rune {
if c.idx > 0 {
return c.src[c.idx-1]
}
return rune(0)
}
func (c *Context) currentChar() rune {
return c.src[c.idx]
}
func (c *Context) nextChar() rune {
if c.size > c.idx+1 {
return c.src[c.idx+1]
}
return rune(0)
}
func (c *Context) repeatNum(r rune) int {
cnt := 0
for i := c.idx; i < c.size; i++ {
if c.src[i] == r {
cnt++
} else {
break
}
}
return cnt
}
func (c *Context) progress(num int) {
c.idx += num
}
func (c *Context) nextPos() int {
return c.idx + 1
}
func (c *Context) existsBuffer() bool {
return len(c.bufferedSrc()) != 0
}
func (c *Context) bufferedSrc() []rune {
src := c.buf[:c.notSpaceCharPos]
if len(src) > 0 && src[len(src)-1] == '\n' && c.isDocument() && c.literalOpt == "-" {
// remove end '\n' character
src = src[:len(src)-1]
}
return src
}
func (c *Context) bufferedToken(pos *token.Position) *token.Token {
if c.idx == 0 {
return nil
}
source := c.bufferedSrc()
if len(source) == 0 {
return nil
}
var tk *token.Token
if c.isDocument() {
tk = token.String(string(source), string(c.obuf), pos)
} else {
tk = token.New(string(source), string(c.obuf), pos)
}
c.resetBuffer()
return tk
}
golang-github-goccy-go-yaml-1.9.5/scanner/scanner.go 0000664 0000000 0000000 00000052312 14167531274 0022350 0 ustar 00root root 0000000 0000000 package scanner
import (
"io"
"strings"
"github.com/goccy/go-yaml/token"
"golang.org/x/xerrors"
)
// IndentState state for indent
type IndentState int
const (
// IndentStateEqual equals previous indent
IndentStateEqual IndentState = iota
// IndentStateUp more indent than previous
IndentStateUp
// IndentStateDown less indent than previous
IndentStateDown
// IndentStateKeep uses not indent token
IndentStateKeep
)
// Scanner holds the scanner's internal state while processing a given text.
// It can be allocated as part of another data structure but must be initialized via Init before use.
type Scanner struct {
source []rune
sourcePos int
sourceSize int
line int
column int
offset int
prevIndentLevel int
prevIndentNum int
prevIndentColumn int
docStartColumn int
indentLevel int
indentNum int
isFirstCharAtLine bool
isAnchor bool
startedFlowSequenceNum int
startedFlowMapNum int
indentState IndentState
savedPos *token.Position
}
func (s *Scanner) pos() *token.Position {
return &token.Position{
Line: s.line,
Column: s.column,
Offset: s.offset,
IndentNum: s.indentNum,
IndentLevel: s.indentLevel,
}
}
func (s *Scanner) bufferedToken(ctx *Context) *token.Token {
if s.savedPos != nil {
tk := ctx.bufferedToken(s.savedPos)
s.savedPos = nil
return tk
}
size := len(ctx.buf)
return ctx.bufferedToken(&token.Position{
Line: s.line,
Column: s.column - size,
Offset: s.offset - size,
IndentNum: s.indentNum,
IndentLevel: s.indentLevel,
})
}
func (s *Scanner) progressColumn(ctx *Context, num int) {
s.column += num
s.offset += num
ctx.progress(num)
}
func (s *Scanner) progressLine(ctx *Context) {
s.column = 1
s.line++
s.offset++
s.indentNum = 0
s.isFirstCharAtLine = true
s.isAnchor = false
ctx.progress(1)
}
func (s *Scanner) isNeededKeepPreviousIndentNum(ctx *Context, c rune) bool {
if !s.isChangedToIndentStateUp() {
return false
}
if ctx.isDocument() {
return true
}
if c == '-' && ctx.existsBuffer() {
return true
}
return false
}
func (s *Scanner) isNewLineChar(c rune) bool {
if c == '\n' {
return true
}
if c == '\r' {
return true
}
return false
}
func (s *Scanner) newLineCount(src []rune) int {
size := len(src)
cnt := 0
for i := 0; i < size; i++ {
c := src[i]
switch c {
case '\r':
if i+1 < size && src[i+1] == '\n' {
i++
}
cnt++
case '\n':
cnt++
}
}
return cnt
}
func (s *Scanner) updateIndentState(ctx *Context) {
indentNumBasedIndentState := s.indentState
if s.prevIndentNum < s.indentNum {
s.indentLevel = s.prevIndentLevel + 1
indentNumBasedIndentState = IndentStateUp
} else if s.prevIndentNum == s.indentNum {
s.indentLevel = s.prevIndentLevel
indentNumBasedIndentState = IndentStateEqual
} else {
indentNumBasedIndentState = IndentStateDown
if s.prevIndentLevel > 0 {
s.indentLevel = s.prevIndentLevel - 1
}
}
if s.prevIndentColumn > 0 {
if s.prevIndentColumn < s.column {
s.indentState = IndentStateUp
} else if s.prevIndentColumn != s.column || indentNumBasedIndentState != IndentStateEqual {
// The following case ( current position is 'd' ), some variables becomes like here
// - prevIndentColumn: 1 of 'a'
// - indentNumBasedIndentState: IndentStateDown because d's indentNum(1) is less than c's indentNum(3).
// Therefore, s.prevIndentColumn(1) == s.column(1) is true, but we want to treat this as IndentStateDown.
// So, we look also current indentState value by the above prevIndentNum based logic, and determins finally indentState.
// ---
// a:
// b
// c
// d: e
// ^
s.indentState = IndentStateDown
} else {
s.indentState = IndentStateEqual
}
} else {
s.indentState = indentNumBasedIndentState
}
}
func (s *Scanner) updateIndent(ctx *Context, c rune) {
if s.isFirstCharAtLine && s.isNewLineChar(c) && ctx.isDocument() {
return
}
if s.isFirstCharAtLine && c == ' ' {
s.indentNum++
return
}
if !s.isFirstCharAtLine {
s.indentState = IndentStateKeep
return
}
s.updateIndentState(ctx)
s.isFirstCharAtLine = false
if s.isNeededKeepPreviousIndentNum(ctx, c) {
return
}
if s.indentState != IndentStateUp {
s.prevIndentColumn = 0
}
s.prevIndentNum = s.indentNum
s.prevIndentLevel = s.indentLevel
}
func (s *Scanner) isChangedToIndentStateDown() bool {
return s.indentState == IndentStateDown
}
func (s *Scanner) isChangedToIndentStateUp() bool {
return s.indentState == IndentStateUp
}
func (s *Scanner) isChangedToIndentStateEqual() bool {
return s.indentState == IndentStateEqual
}
func (s *Scanner) addBufferedTokenIfExists(ctx *Context) {
ctx.addToken(s.bufferedToken(ctx))
}
func (s *Scanner) breakLiteral(ctx *Context) {
s.docStartColumn = 0
ctx.breakLiteral()
}
func (s *Scanner) scanSingleQuote(ctx *Context) (tk *token.Token, pos int) {
ctx.addOriginBuf('\'')
srcpos := s.pos()
startIndex := ctx.idx + 1
src := ctx.src
size := len(src)
value := []rune{}
isFirstLineChar := false
isNewLine := false
for idx := startIndex; idx < size; idx++ {
if !isNewLine {
s.progressColumn(ctx, 1)
} else {
isNewLine = false
}
c := src[idx]
pos = idx + 1
ctx.addOriginBuf(c)
if s.isNewLineChar(c) {
value = append(value, ' ')
isFirstLineChar = true
isNewLine = true
s.progressLine(ctx)
continue
} else if c == ' ' && isFirstLineChar {
continue
} else if c != '\'' {
value = append(value, c)
isFirstLineChar = false
continue
}
if idx+1 < len(ctx.src) && ctx.src[idx+1] == '\'' {
// '' handle as ' character
value = append(value, c)
ctx.addOriginBuf(c)
idx++
continue
}
s.progressColumn(ctx, 1)
tk = token.SingleQuote(string(value), string(ctx.obuf), srcpos)
pos = idx - startIndex + 1
return
}
return
}
func hexToInt(b rune) int {
if b >= 'A' && b <= 'F' {
return int(b) - 'A' + 10
}
if b >= 'a' && b <= 'f' {
return int(b) - 'a' + 10
}
return int(b) - '0'
}
func hexRunesToInt(b []rune) int {
sum := 0
for i := 0; i < len(b); i++ {
sum += hexToInt(b[i]) << (uint(len(b)-i-1) * 4)
}
return sum
}
func (s *Scanner) scanDoubleQuote(ctx *Context) (tk *token.Token, pos int) {
ctx.addOriginBuf('"')
srcpos := s.pos()
startIndex := ctx.idx + 1
src := ctx.src
size := len(src)
value := []rune{}
isFirstLineChar := false
isNewLine := false
for idx := startIndex; idx < size; idx++ {
if !isNewLine {
s.progressColumn(ctx, 1)
} else {
isNewLine = false
}
c := src[idx]
pos = idx + 1
ctx.addOriginBuf(c)
if s.isNewLineChar(c) {
value = append(value, ' ')
isFirstLineChar = true
isNewLine = true
s.progressLine(ctx)
continue
} else if c == ' ' && isFirstLineChar {
continue
} else if c == '\\' {
isFirstLineChar = false
if idx+1 < size {
nextChar := src[idx+1]
switch nextChar {
case 'b':
ctx.addOriginBuf(nextChar)
value = append(value, '\b')
idx++
continue
case 'e':
ctx.addOriginBuf(nextChar)
value = append(value, '\x1B')
idx++
continue
case 'f':
ctx.addOriginBuf(nextChar)
value = append(value, '\f')
idx++
continue
case 'n':
ctx.addOriginBuf(nextChar)
value = append(value, '\n')
idx++
continue
case 'v':
ctx.addOriginBuf(nextChar)
value = append(value, '\v')
idx++
continue
case 'L': // LS (#x2028)
ctx.addOriginBuf(nextChar)
value = append(value, []rune{'\xE2', '\x80', '\xA8'}...)
idx++
continue
case 'N': // NEL (#x85)
ctx.addOriginBuf(nextChar)
value = append(value, []rune{'\xC2', '\x85'}...)
idx++
continue
case 'P': // PS (#x2029)
ctx.addOriginBuf(nextChar)
value = append(value, []rune{'\xE2', '\x80', '\xA9'}...)
idx++
continue
case '_': // #xA0
ctx.addOriginBuf(nextChar)
value = append(value, []rune{'\xC2', '\xA0'}...)
idx++
continue
case '"':
ctx.addOriginBuf(nextChar)
value = append(value, nextChar)
idx++
continue
case 'x':
if idx+3 >= size {
// TODO: need to return error
//err = xerrors.New("invalid escape character \\x")
return
}
codeNum := hexRunesToInt(src[idx+2 : idx+4])
value = append(value, rune(codeNum))
idx += 3
continue
case 'u':
if idx+5 >= size {
// TODO: need to return error
//err = xerrors.New("invalid escape character \\u")
return
}
codeNum := hexRunesToInt(src[idx+2 : idx+6])
value = append(value, rune(codeNum))
idx += 5
continue
case 'U':
if idx+9 >= size {
// TODO: need to return error
//err = xerrors.New("invalid escape character \\U")
return
}
codeNum := hexRunesToInt(src[idx+2 : idx+10])
value = append(value, rune(codeNum))
idx += 9
continue
case '\\':
ctx.addOriginBuf(nextChar)
idx++
}
}
value = append(value, c)
continue
} else if c != '"' {
value = append(value, c)
isFirstLineChar = false
continue
}
s.progressColumn(ctx, 1)
tk = token.DoubleQuote(string(value), string(ctx.obuf), srcpos)
pos = idx - startIndex + 1
return
}
return
}
func (s *Scanner) scanQuote(ctx *Context, ch rune) (tk *token.Token, pos int) {
if ch == '\'' {
return s.scanSingleQuote(ctx)
}
return s.scanDoubleQuote(ctx)
}
func (s *Scanner) isMergeKey(ctx *Context) bool {
if ctx.repeatNum('<') != 2 {
return false
}
src := ctx.src
size := len(src)
for idx := ctx.idx + 2; idx < size; idx++ {
c := src[idx]
if c == ' ' {
continue
}
if c != ':' {
return false
}
if idx+1 < size {
nc := src[idx+1]
if nc == ' ' || s.isNewLineChar(nc) {
return true
}
}
}
return false
}
func (s *Scanner) scanTag(ctx *Context) (tk *token.Token, pos int) {
ctx.addOriginBuf('!')
ctx.progress(1) // skip '!' character
for idx, c := range ctx.src[ctx.idx:] {
pos = idx + 1
ctx.addOriginBuf(c)
switch c {
case ' ', '\n', '\r':
value := ctx.source(ctx.idx-1, ctx.idx+idx)
tk = token.Tag(value, string(ctx.obuf), s.pos())
pos = len([]rune(value))
return
}
}
return
}
func (s *Scanner) scanComment(ctx *Context) (tk *token.Token, pos int) {
ctx.addOriginBuf('#')
ctx.progress(1) // skip '#' character
for idx, c := range ctx.src[ctx.idx:] {
pos = idx + 1
ctx.addOriginBuf(c)
switch c {
case '\n', '\r':
if ctx.previousChar() == '\\' {
continue
}
value := ctx.source(ctx.idx, ctx.idx+idx)
tk = token.Comment(value, string(ctx.obuf), s.pos())
pos = len([]rune(value)) + 1
return
}
}
return
}
func trimCommentFromLiteralOpt(text string) (string, error) {
idx := strings.Index(text, "#")
if idx < 0 {
return text, nil
}
if idx == 0 {
return "", xerrors.New("invalid literal header")
}
return text[:idx-1], nil
}
func (s *Scanner) scanLiteral(ctx *Context, c rune) {
ctx.addOriginBuf(c)
if ctx.isEOS() {
if ctx.isLiteral {
ctx.addBuf(c)
}
value := ctx.bufferedSrc()
ctx.addToken(token.String(string(value), string(ctx.obuf), s.pos()))
ctx.resetBuffer()
s.progressColumn(ctx, 1)
} else if s.isNewLineChar(c) {
if ctx.isLiteral {
ctx.addBuf(c)
} else {
ctx.addBuf(' ')
}
s.progressLine(ctx)
} else if s.isFirstCharAtLine && c == ' ' {
if 0 < s.docStartColumn && s.docStartColumn <= s.column {
ctx.addBuf(c)
}
s.progressColumn(ctx, 1)
} else {
if s.docStartColumn == 0 {
s.docStartColumn = s.column
}
ctx.addBuf(c)
s.progressColumn(ctx, 1)
}
}
func (s *Scanner) scanLiteralHeader(ctx *Context) (pos int, err error) {
header := ctx.currentChar()
ctx.addOriginBuf(header)
ctx.progress(1) // skip '|' or '>' character
for idx, c := range ctx.src[ctx.idx:] {
pos = idx
ctx.addOriginBuf(c)
switch c {
case '\n', '\r':
value := ctx.source(ctx.idx, ctx.idx+idx)
opt := strings.TrimRight(value, " ")
orgOptLen := len(opt)
opt, err = trimCommentFromLiteralOpt(opt)
if err != nil {
return
}
switch opt {
case "", "+", "-",
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9":
hasComment := len(opt) < orgOptLen
if header == '|' {
if hasComment {
commentLen := orgOptLen - len(opt)
headerPos := strings.Index(string(ctx.obuf), "|")
litBuf := ctx.obuf[:len(ctx.obuf)-commentLen-headerPos]
commentBuf := ctx.obuf[len(litBuf):]
ctx.addToken(token.Literal("|"+opt, string(litBuf), s.pos()))
s.column += len(litBuf)
s.offset += len(litBuf)
commentHeader := strings.Index(value, "#")
ctx.addToken(token.Comment(string(value[commentHeader+1:]), string(commentBuf), s.pos()))
} else {
ctx.addToken(token.Literal("|"+opt, string(ctx.obuf), s.pos()))
}
ctx.isLiteral = true
} else if header == '>' {
if hasComment {
commentLen := orgOptLen - len(opt)
headerPos := strings.Index(string(ctx.obuf), ">")
foldedBuf := ctx.obuf[:len(ctx.obuf)-commentLen-headerPos]
commentBuf := ctx.obuf[len(foldedBuf):]
ctx.addToken(token.Folded(">"+opt, string(foldedBuf), s.pos()))
s.column += len(foldedBuf)
s.offset += len(foldedBuf)
commentHeader := strings.Index(value, "#")
ctx.addToken(token.Comment(string(value[commentHeader+1:]), string(commentBuf), s.pos()))
} else {
ctx.addToken(token.Folded(">"+opt, string(ctx.obuf), s.pos()))
}
ctx.isFolded = true
}
s.indentState = IndentStateKeep
ctx.resetBuffer()
ctx.literalOpt = opt
return
}
break
}
}
err = xerrors.New("invalid literal header")
return
}
func (s *Scanner) scanNewLine(ctx *Context, c rune) {
if len(ctx.buf) > 0 && s.savedPos == nil {
s.savedPos = s.pos()
s.savedPos.Column -= len(ctx.bufferedSrc())
}
// if the following case, origin buffer has unnecessary two spaces.
// So, `removeRightSpaceFromOriginBuf` remove them, also fix column number too.
// ---
// a:[space][space]
// b: c
removedNum := ctx.removeRightSpaceFromBuf()
if removedNum > 0 {
s.column -= removedNum
s.offset -= removedNum
if s.savedPos != nil {
s.savedPos.Column -= removedNum
}
}
if ctx.isEOS() {
s.addBufferedTokenIfExists(ctx)
} else if s.isAnchor {
s.addBufferedTokenIfExists(ctx)
}
ctx.addBuf(' ')
ctx.addOriginBuf(c)
ctx.isSingleLine = false
s.progressLine(ctx)
}
func (s *Scanner) scan(ctx *Context) (pos int) {
for ctx.next() {
pos = ctx.nextPos()
c := ctx.currentChar()
s.updateIndent(ctx, c)
if ctx.isDocument() {
if s.isChangedToIndentStateEqual() ||
s.isChangedToIndentStateDown() {
s.addBufferedTokenIfExists(ctx)
s.breakLiteral(ctx)
} else {
s.scanLiteral(ctx, c)
continue
}
} else if s.isChangedToIndentStateDown() {
s.addBufferedTokenIfExists(ctx)
} else if s.isChangedToIndentStateEqual() {
// if first character is new line character, buffer expect to raw folded literal
if len(ctx.obuf) > 0 && s.newLineCount(ctx.obuf) <= 1 {
// doesn't raw folded literal
s.addBufferedTokenIfExists(ctx)
}
}
switch c {
case '{':
if !ctx.existsBuffer() {
ctx.addOriginBuf(c)
ctx.addToken(token.MappingStart(string(ctx.obuf), s.pos()))
s.startedFlowMapNum++
s.progressColumn(ctx, 1)
return
}
case '}':
if !ctx.existsBuffer() || s.startedFlowMapNum > 0 {
ctx.addToken(s.bufferedToken(ctx))
ctx.addOriginBuf(c)
ctx.addToken(token.MappingEnd(string(ctx.obuf), s.pos()))
s.startedFlowMapNum--
s.progressColumn(ctx, 1)
return
}
case '.':
if s.indentNum == 0 && s.column == 1 && ctx.repeatNum('.') == 3 {
ctx.addToken(token.DocumentEnd(string(ctx.obuf)+"...", s.pos()))
s.progressColumn(ctx, 3)
pos += 2
return
}
case '<':
if s.isMergeKey(ctx) {
s.prevIndentColumn = s.column
ctx.addToken(token.MergeKey(string(ctx.obuf)+"<<", s.pos()))
s.progressColumn(ctx, 1)
pos++
return
}
case '-':
if s.indentNum == 0 && s.column == 1 && ctx.repeatNum('-') == 3 {
s.addBufferedTokenIfExists(ctx)
ctx.addToken(token.DocumentHeader(string(ctx.obuf)+"---", s.pos()))
s.progressColumn(ctx, 3)
pos += 2
return
}
if ctx.existsBuffer() && s.isChangedToIndentStateUp() {
// raw folded
ctx.isRawFolded = true
ctx.addBuf(c)
ctx.addOriginBuf(c)
s.progressColumn(ctx, 1)
continue
}
if ctx.existsBuffer() {
// '-' is literal
ctx.addBuf(c)
ctx.addOriginBuf(c)
s.progressColumn(ctx, 1)
continue
}
nc := ctx.nextChar()
if nc == ' ' || s.isNewLineChar(nc) {
s.addBufferedTokenIfExists(ctx)
ctx.addOriginBuf(c)
tk := token.SequenceEntry(string(ctx.obuf), s.pos())
s.prevIndentColumn = tk.Position.Column
ctx.addToken(tk)
s.progressColumn(ctx, 1)
return
}
case '[':
if !ctx.existsBuffer() {
ctx.addOriginBuf(c)
ctx.addToken(token.SequenceStart(string(ctx.obuf), s.pos()))
s.startedFlowSequenceNum++
s.progressColumn(ctx, 1)
return
}
case ']':
if !ctx.existsBuffer() || s.startedFlowSequenceNum > 0 {
s.addBufferedTokenIfExists(ctx)
ctx.addOriginBuf(c)
ctx.addToken(token.SequenceEnd(string(ctx.obuf), s.pos()))
s.startedFlowSequenceNum--
s.progressColumn(ctx, 1)
return
}
case ',':
if s.startedFlowSequenceNum > 0 || s.startedFlowMapNum > 0 {
s.addBufferedTokenIfExists(ctx)
ctx.addOriginBuf(c)
ctx.addToken(token.CollectEntry(string(ctx.obuf), s.pos()))
s.progressColumn(ctx, 1)
return
}
case ':':
nc := ctx.nextChar()
if s.startedFlowMapNum > 0 || nc == ' ' || s.isNewLineChar(nc) || ctx.isNextEOS() {
// mapping value
tk := s.bufferedToken(ctx)
if tk != nil {
s.prevIndentColumn = tk.Position.Column
ctx.addToken(tk)
}
ctx.addToken(token.MappingValue(s.pos()))
s.progressColumn(ctx, 1)
return
}
case '|', '>':
if !ctx.existsBuffer() {
progress, err := s.scanLiteralHeader(ctx)
if err != nil {
// TODO: returns syntax error object
return
}
s.progressColumn(ctx, progress)
s.progressLine(ctx)
continue
}
case '!':
if !ctx.existsBuffer() {
token, progress := s.scanTag(ctx)
ctx.addToken(token)
s.progressColumn(ctx, progress)
if c := ctx.previousChar(); s.isNewLineChar(c) {
s.progressLine(ctx)
}
pos += progress
return
}
case '%':
if !ctx.existsBuffer() && s.indentNum == 0 {
ctx.addToken(token.Directive(string(ctx.obuf)+"%", s.pos()))
s.progressColumn(ctx, 1)
return
}
case '?':
nc := ctx.nextChar()
if !ctx.existsBuffer() && nc == ' ' {
ctx.addToken(token.MappingKey(s.pos()))
s.progressColumn(ctx, 1)
return
}
case '&':
if !ctx.existsBuffer() {
s.addBufferedTokenIfExists(ctx)
ctx.addOriginBuf(c)
ctx.addToken(token.Anchor(string(ctx.obuf), s.pos()))
s.progressColumn(ctx, 1)
s.isAnchor = true
return
}
case '*':
if !ctx.existsBuffer() {
s.addBufferedTokenIfExists(ctx)
ctx.addOriginBuf(c)
ctx.addToken(token.Alias(string(ctx.obuf), s.pos()))
s.progressColumn(ctx, 1)
return
}
case '#':
if !ctx.existsBuffer() || ctx.previousChar() == ' ' {
s.addBufferedTokenIfExists(ctx)
token, progress := s.scanComment(ctx)
ctx.addToken(token)
s.progressColumn(ctx, progress)
s.progressLine(ctx)
pos += progress
return
}
case '\'', '"':
if !ctx.existsBuffer() {
token, progress := s.scanQuote(ctx, c)
ctx.addToken(token)
pos += progress
return
}
case '\r', '\n':
// There is no problem that we ignore CR which followed by LF and normalize it to LF, because of following YAML1.2 spec.
// > Line breaks inside scalar content must be normalized by the YAML processor. Each such line break must be parsed into a single line feed character.
// > Outside scalar content, YAML allows any line break to be used to terminate lines.
// > -- https://yaml.org/spec/1.2/spec.html
if c == '\r' && ctx.nextChar() == '\n' {
ctx.addOriginBuf('\r')
ctx.progress(1)
c = '\n'
}
s.scanNewLine(ctx, c)
continue
case ' ':
if ctx.isSaveIndentMode() || (!s.isAnchor && !s.isFirstCharAtLine) {
ctx.addBuf(c)
ctx.addOriginBuf(c)
s.progressColumn(ctx, 1)
continue
}
if s.isFirstCharAtLine {
s.progressColumn(ctx, 1)
ctx.addOriginBuf(c)
continue
}
s.addBufferedTokenIfExists(ctx)
pos-- // to rescan white space at next scanning for adding white space to next buffer.
s.isAnchor = false
return
}
ctx.addBuf(c)
ctx.addOriginBuf(c)
s.progressColumn(ctx, 1)
}
s.addBufferedTokenIfExists(ctx)
return
}
// Init prepares the scanner s to tokenize the text src by setting the scanner at the beginning of src.
func (s *Scanner) Init(text string) {
src := []rune(text)
s.source = src
s.sourcePos = 0
s.sourceSize = len(src)
s.line = 1
s.column = 1
s.offset = 1
s.prevIndentLevel = 0
s.prevIndentNum = 0
s.prevIndentColumn = 0
s.indentLevel = 0
s.indentNum = 0
s.isFirstCharAtLine = true
}
// Scan scans the next token and returns the token collection. The source end is indicated by io.EOF.
func (s *Scanner) Scan() (token.Tokens, error) {
if s.sourcePos >= s.sourceSize {
return nil, io.EOF
}
ctx := newContext(s.source[s.sourcePos:])
defer ctx.release()
progress := s.scan(ctx)
s.sourcePos += progress
var tokens token.Tokens
tokens = append(tokens, ctx.tokens...)
return tokens, nil
}
golang-github-goccy-go-yaml-1.9.5/stdlib_quote.go 0000664 0000000 0000000 00000005354 14167531274 0021770 0 ustar 00root root 0000000 0000000 // Copied and trimmed down from https://github.com/golang/go/blob/e3769299cd3484e018e0e2a6e1b95c2b18ce4f41/src/strconv/quote.go
// We want to use the standard library's private "quoteWith" function rather than write our own so that we get robust unicode support.
// Every private function called by quoteWith was copied.
// There are 2 modifications to simplify the code:
// 1. The unicode.IsPrint function was substituted for the custom implementation of IsPrint
// 2. All code paths reachable only when ASCIIonly or grphicOnly are set to true were removed.
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package yaml
import (
"unicode"
"unicode/utf8"
)
const (
lowerhex = "0123456789abcdef"
)
func quoteWith(s string, quote byte) string {
return string(appendQuotedWith(make([]byte, 0, 3*len(s)/2), s, quote))
}
func appendQuotedWith(buf []byte, s string, quote byte) []byte {
// Often called with big strings, so preallocate. If there's quoting,
// this is conservative but still helps a lot.
if cap(buf)-len(buf) < len(s) {
nBuf := make([]byte, len(buf), len(buf)+1+len(s)+1)
copy(nBuf, buf)
buf = nBuf
}
buf = append(buf, quote)
for width := 0; len(s) > 0; s = s[width:] {
r := rune(s[0])
width = 1
if r >= utf8.RuneSelf {
r, width = utf8.DecodeRuneInString(s)
}
if width == 1 && r == utf8.RuneError {
buf = append(buf, `\x`...)
buf = append(buf, lowerhex[s[0]>>4])
buf = append(buf, lowerhex[s[0]&0xF])
continue
}
buf = appendEscapedRune(buf, r, quote)
}
buf = append(buf, quote)
return buf
}
func appendEscapedRune(buf []byte, r rune, quote byte) []byte {
var runeTmp [utf8.UTFMax]byte
if r == rune(quote) || r == '\\' { // always backslashed
buf = append(buf, '\\')
buf = append(buf, byte(r))
return buf
}
if unicode.IsPrint(r) {
n := utf8.EncodeRune(runeTmp[:], r)
buf = append(buf, runeTmp[:n]...)
return buf
}
switch r {
case '\a':
buf = append(buf, `\a`...)
case '\b':
buf = append(buf, `\b`...)
case '\f':
buf = append(buf, `\f`...)
case '\n':
buf = append(buf, `\n`...)
case '\r':
buf = append(buf, `\r`...)
case '\t':
buf = append(buf, `\t`...)
case '\v':
buf = append(buf, `\v`...)
default:
switch {
case r < ' ':
buf = append(buf, `\x`...)
buf = append(buf, lowerhex[byte(r)>>4])
buf = append(buf, lowerhex[byte(r)&0xF])
case r > utf8.MaxRune:
r = 0xFFFD
fallthrough
case r < 0x10000:
buf = append(buf, `\u`...)
for s := 12; s >= 0; s -= 4 {
buf = append(buf, lowerhex[r>>uint(s)&0xF])
}
default:
buf = append(buf, `\U`...)
for s := 28; s >= 0; s -= 4 {
buf = append(buf, lowerhex[r>>uint(s)&0xF])
}
}
}
return buf
}
golang-github-goccy-go-yaml-1.9.5/struct.go 0000664 0000000 0000000 00000005540 14167531274 0020613 0 ustar 00root root 0000000 0000000 package yaml
import (
"reflect"
"strings"
"golang.org/x/xerrors"
)
const (
// StructTagName tag keyword for Marshal/Unmarshal
StructTagName = "yaml"
)
// StructField information for each the field in structure
type StructField struct {
FieldName string
RenderName string
AnchorName string
AliasName string
IsAutoAnchor bool
IsAutoAlias bool
IsOmitEmpty bool
IsFlow bool
IsInline bool
}
func getTag(field reflect.StructField) string {
// If struct tag `yaml` exist, use that. If no `yaml`
// exists, but `json` does, use that and try the best to
// adhere to its rules
tag := field.Tag.Get(StructTagName)
if tag == "" {
tag = field.Tag.Get(`json`)
}
return tag
}
func structField(field reflect.StructField) *StructField {
tag := getTag(field)
fieldName := strings.ToLower(field.Name)
options := strings.Split(tag, ",")
if len(options) > 0 {
if options[0] != "" {
fieldName = options[0]
}
}
structField := &StructField{
FieldName: field.Name,
RenderName: fieldName,
}
if len(options) > 1 {
for _, opt := range options[1:] {
switch {
case opt == "omitempty":
structField.IsOmitEmpty = true
case opt == "flow":
structField.IsFlow = true
case opt == "inline":
structField.IsInline = true
case strings.HasPrefix(opt, "anchor"):
anchor := strings.Split(opt, "=")
if len(anchor) > 1 {
structField.AnchorName = anchor[1]
} else {
structField.IsAutoAnchor = true
}
case strings.HasPrefix(opt, "alias"):
alias := strings.Split(opt, "=")
if len(alias) > 1 {
structField.AliasName = alias[1]
} else {
structField.IsAutoAlias = true
}
default:
}
}
}
return structField
}
func isIgnoredStructField(field reflect.StructField) bool {
if field.PkgPath != "" && !field.Anonymous {
// private field
return true
}
tag := getTag(field)
if tag == "-" {
return true
}
return false
}
type StructFieldMap map[string]*StructField
func (m StructFieldMap) isIncludedRenderName(name string) bool {
for _, v := range m {
if v.RenderName == name {
return true
}
}
return false
}
func (m StructFieldMap) hasMergeProperty() bool {
for _, v := range m {
if v.IsOmitEmpty && v.IsInline && v.IsAutoAlias {
return true
}
}
return false
}
func structFieldMap(structType reflect.Type) (StructFieldMap, error) {
structFieldMap := StructFieldMap{}
renderNameMap := map[string]struct{}{}
for i := 0; i < structType.NumField(); i++ {
field := structType.Field(i)
if isIgnoredStructField(field) {
continue
}
structField := structField(field)
if _, exists := renderNameMap[structField.RenderName]; exists {
return nil, xerrors.Errorf("duplicated struct field name %s", structField.RenderName)
}
structFieldMap[structField.FieldName] = structField
renderNameMap[structField.RenderName] = struct{}{}
}
return structFieldMap, nil
}
golang-github-goccy-go-yaml-1.9.5/testdata/ 0000775 0000000 0000000 00000000000 14167531274 0020545 5 ustar 00root root 0000000 0000000 golang-github-goccy-go-yaml-1.9.5/testdata/anchor.yml 0000664 0000000 0000000 00000000030 14167531274 0022533 0 ustar 00root root 0000000 0000000 a: &a
b: 1
c: hello
golang-github-goccy-go-yaml-1.9.5/token/ 0000775 0000000 0000000 00000000000 14167531274 0020054 5 ustar 00root root 0000000 0000000 golang-github-goccy-go-yaml-1.9.5/token/token.go 0000664 0000000 0000000 00000061300 14167531274 0021523 0 ustar 00root root 0000000 0000000 package token
import (
"fmt"
"strings"
)
// Character type for character
type Character byte
const (
// SequenceEntryCharacter character for sequence entry
SequenceEntryCharacter Character = '-'
// MappingKeyCharacter character for mapping key
MappingKeyCharacter = '?'
// MappingValueCharacter character for mapping value
MappingValueCharacter = ':'
// CollectEntryCharacter character for collect entry
CollectEntryCharacter = ','
// SequenceStartCharacter character for sequence start
SequenceStartCharacter = '['
// SequenceEndCharacter character for sequence end
SequenceEndCharacter = ']'
// MappingStartCharacter character for mapping start
MappingStartCharacter = '{'
// MappingEndCharacter character for mapping end
MappingEndCharacter = '}'
// CommentCharacter character for comment
CommentCharacter = '#'
// AnchorCharacter character for anchor
AnchorCharacter = '&'
// AliasCharacter character for alias
AliasCharacter = '*'
// TagCharacter character for tag
TagCharacter = '!'
// LiteralCharacter character for literal
LiteralCharacter = '|'
// FoldedCharacter character for folded
FoldedCharacter = '>'
// SingleQuoteCharacter character for single quote
SingleQuoteCharacter = '\''
// DoubleQuoteCharacter character for double quote
DoubleQuoteCharacter = '"'
// DirectiveCharacter character for directive
DirectiveCharacter = '%'
// SpaceCharacter character for space
SpaceCharacter = ' '
// LineBreakCharacter character for line break
LineBreakCharacter = '\n'
)
// Type type identifier for token
type Type int
const (
// UnknownType reserve for invalid type
UnknownType Type = iota
// DocumentHeaderType type for DocumentHeader token
DocumentHeaderType
// DocumentEndType type for DocumentEnd token
DocumentEndType
// SequenceEntryType type for SequenceEntry token
SequenceEntryType
// MappingKeyType type for MappingKey token
MappingKeyType
// MappingValueType type for MappingValue token
MappingValueType
// MergeKeyType type for MergeKey token
MergeKeyType
// CollectEntryType type for CollectEntry token
CollectEntryType
// SequenceStartType type for SequenceStart token
SequenceStartType
// SequenceEndType type for SequenceEnd token
SequenceEndType
// MappingStartType type for MappingStart token
MappingStartType
// MappingEndType type for MappingEnd token
MappingEndType
// CommentType type for Comment token
CommentType
// AnchorType type for Anchor token
AnchorType
// AliasType type for Alias token
AliasType
// TagType type for Tag token
TagType
// LiteralType type for Literal token
LiteralType
// FoldedType type for Folded token
FoldedType
// SingleQuoteType type for SingleQuote token
SingleQuoteType
// DoubleQuoteType type for DoubleQuote token
DoubleQuoteType
// DirectiveType type for Directive token
DirectiveType
// SpaceType type for Space token
SpaceType
// NullType type for Null token
NullType
// InfinityType type for Infinity token
InfinityType
// NanType type for Nan token
NanType
// IntegerType type for Integer token
IntegerType
// BinaryIntegerType type for BinaryInteger token
BinaryIntegerType
// OctetIntegerType type for OctetInteger token
OctetIntegerType
// HexIntegerType type for HexInteger token
HexIntegerType
// FloatType type for Float token
FloatType
// StringType type for String token
StringType
// BoolType type for Bool token
BoolType
)
// String type identifier to text
func (t Type) String() string {
switch t {
case UnknownType:
return "Unknown"
case DocumentHeaderType:
return "DocumentHeader"
case DocumentEndType:
return "DocumentEnd"
case SequenceEntryType:
return "SequenceEntry"
case MappingKeyType:
return "MappingKey"
case MappingValueType:
return "MappingValue"
case MergeKeyType:
return "MergeKey"
case CollectEntryType:
return "CollectEntry"
case SequenceStartType:
return "SequenceStart"
case SequenceEndType:
return "SequenceEnd"
case MappingStartType:
return "MappingStart"
case MappingEndType:
return "MappingEnd"
case CommentType:
return "Comment"
case AnchorType:
return "Anchor"
case AliasType:
return "Alias"
case TagType:
return "Tag"
case LiteralType:
return "Literal"
case FoldedType:
return "Folded"
case SingleQuoteType:
return "SingleQuote"
case DoubleQuoteType:
return "DoubleQuote"
case DirectiveType:
return "Directive"
case SpaceType:
return "Space"
case StringType:
return "String"
case BoolType:
return "Bool"
case IntegerType:
return "Integer"
case BinaryIntegerType:
return "BinaryInteger"
case OctetIntegerType:
return "OctetInteger"
case HexIntegerType:
return "HexInteger"
case FloatType:
return "Float"
case NullType:
return "Null"
case InfinityType:
return "Infinity"
case NanType:
return "Nan"
}
return ""
}
// CharacterType type for character category
type CharacterType int
const (
// CharacterTypeIndicator type of indicator character
CharacterTypeIndicator CharacterType = iota
// CharacterTypeWhiteSpace type of white space character
CharacterTypeWhiteSpace
// CharacterTypeMiscellaneous type of miscellaneous character
CharacterTypeMiscellaneous
// CharacterTypeEscaped type of escaped character
CharacterTypeEscaped
)
// String character type identifier to text
func (c CharacterType) String() string {
switch c {
case CharacterTypeIndicator:
return "Indicator"
case CharacterTypeWhiteSpace:
return "WhiteSpcae"
case CharacterTypeMiscellaneous:
return "Miscellaneous"
case CharacterTypeEscaped:
return "Escaped"
}
return ""
}
// Indicator type for indicator
type Indicator int
const (
// NotIndicator not indicator
NotIndicator Indicator = iota
// BlockStructureIndicator indicator for block structure ( '-', '?', ':' )
BlockStructureIndicator
// FlowCollectionIndicator indicator for flow collection ( '[', ']', '{', '}', ',' )
FlowCollectionIndicator
// CommentIndicator indicator for comment ( '#' )
CommentIndicator
// NodePropertyIndicator indicator for node property ( '!', '&', '*' )
NodePropertyIndicator
// BlockScalarIndicator indicator for block scalar ( '|', '>' )
BlockScalarIndicator
// QuotedScalarIndicator indicator for quoted scalar ( ''', '"' )
QuotedScalarIndicator
// DirectiveIndicator indicator for directive ( '%' )
DirectiveIndicator
// InvalidUseOfReservedIndicator indicator for invalid use of reserved keyword ( '@', '`' )
InvalidUseOfReservedIndicator
)
// String indicator to text
func (i Indicator) String() string {
switch i {
case NotIndicator:
return "NotIndicator"
case BlockStructureIndicator:
return "BlockStructure"
case FlowCollectionIndicator:
return "FlowCollection"
case CommentIndicator:
return "Comment"
case NodePropertyIndicator:
return "NodeProperty"
case BlockScalarIndicator:
return "BlockScalar"
case QuotedScalarIndicator:
return "QuotedScalar"
case DirectiveIndicator:
return "Directive"
case InvalidUseOfReservedIndicator:
return "InvalidUseOfReserved"
}
return ""
}
var (
reservedNullKeywords = []string{
"null",
"Null",
"NULL",
"~",
}
reservedBoolKeywords = []string{
"true",
"True",
"TRUE",
"false",
"False",
"FALSE",
}
reservedInfKeywords = []string{
".inf",
".Inf",
".INF",
"-.inf",
"-.Inf",
"-.INF",
}
reservedNanKeywords = []string{
".nan",
".NaN",
".NAN",
}
reservedKeywordMap = map[string]func(string, string, *Position) *Token{}
)
func reservedKeywordToken(typ Type, value, org string, pos *Position) *Token {
return &Token{
Type: typ,
CharacterType: CharacterTypeMiscellaneous,
Indicator: NotIndicator,
Value: value,
Origin: org,
Position: pos,
}
}
func init() {
for _, keyword := range reservedNullKeywords {
reservedKeywordMap[keyword] = func(value, org string, pos *Position) *Token {
return reservedKeywordToken(NullType, value, org, pos)
}
}
for _, keyword := range reservedBoolKeywords {
reservedKeywordMap[keyword] = func(value, org string, pos *Position) *Token {
return reservedKeywordToken(BoolType, value, org, pos)
}
}
for _, keyword := range reservedInfKeywords {
reservedKeywordMap[keyword] = func(value, org string, pos *Position) *Token {
return reservedKeywordToken(InfinityType, value, org, pos)
}
}
for _, keyword := range reservedNanKeywords {
reservedKeywordMap[keyword] = func(value, org string, pos *Position) *Token {
return reservedKeywordToken(NanType, value, org, pos)
}
}
}
// ReservedTagKeyword type of reserved tag keyword
type ReservedTagKeyword string
const (
// IntegerTag `!!int` tag
IntegerTag ReservedTagKeyword = "!!int"
// FloatTag `!!float` tag
FloatTag ReservedTagKeyword = "!!float"
// NullTag `!!null` tag
NullTag ReservedTagKeyword = "!!null"
// SequenceTag `!!seq` tag
SequenceTag ReservedTagKeyword = "!!seq"
// MappingTag `!!map` tag
MappingTag ReservedTagKeyword = "!!map"
// StringTag `!!str` tag
StringTag ReservedTagKeyword = "!!str"
// BinaryTag `!!binary` tag
BinaryTag ReservedTagKeyword = "!!binary"
// OrderedMapTag `!!omap` tag
OrderedMapTag ReservedTagKeyword = "!!omap"
// SetTag `!!set` tag
SetTag ReservedTagKeyword = "!!set"
// TimestampTag `!!timestamp` tag
TimestampTag ReservedTagKeyword = "!!timestamp"
)
var (
// ReservedTagKeywordMap map for reserved tag keywords
ReservedTagKeywordMap = map[ReservedTagKeyword]func(string, string, *Position) *Token{
IntegerTag: func(value, org string, pos *Position) *Token {
return &Token{
Type: TagType,
CharacterType: CharacterTypeIndicator,
Indicator: NodePropertyIndicator,
Value: value,
Origin: org,
Position: pos,
}
},
FloatTag: func(value, org string, pos *Position) *Token {
return &Token{
Type: TagType,
CharacterType: CharacterTypeIndicator,
Indicator: NodePropertyIndicator,
Value: value,
Origin: org,
Position: pos,
}
},
NullTag: func(value, org string, pos *Position) *Token {
return &Token{
Type: TagType,
CharacterType: CharacterTypeIndicator,
Indicator: NodePropertyIndicator,
Value: value,
Origin: org,
Position: pos,
}
},
SequenceTag: func(value, org string, pos *Position) *Token {
return &Token{
Type: TagType,
CharacterType: CharacterTypeIndicator,
Indicator: NodePropertyIndicator,
Value: value,
Origin: org,
Position: pos,
}
},
MappingTag: func(value, org string, pos *Position) *Token {
return &Token{
Type: TagType,
CharacterType: CharacterTypeIndicator,
Indicator: NodePropertyIndicator,
Value: value,
Origin: org,
Position: pos,
}
},
StringTag: func(value, org string, pos *Position) *Token {
return &Token{
Type: TagType,
CharacterType: CharacterTypeIndicator,
Indicator: NodePropertyIndicator,
Value: value,
Origin: org,
Position: pos,
}
},
BinaryTag: func(value, org string, pos *Position) *Token {
return &Token{
Type: TagType,
CharacterType: CharacterTypeIndicator,
Indicator: NodePropertyIndicator,
Value: value,
Origin: org,
Position: pos,
}
},
OrderedMapTag: func(value, org string, pos *Position) *Token {
return &Token{
Type: TagType,
CharacterType: CharacterTypeIndicator,
Indicator: NodePropertyIndicator,
Value: value,
Origin: org,
Position: pos,
}
},
SetTag: func(value, org string, pos *Position) *Token {
return &Token{
Type: TagType,
CharacterType: CharacterTypeIndicator,
Indicator: NodePropertyIndicator,
Value: value,
Origin: org,
Position: pos,
}
},
TimestampTag: func(value, org string, pos *Position) *Token {
return &Token{
Type: TagType,
CharacterType: CharacterTypeIndicator,
Indicator: NodePropertyIndicator,
Value: value,
Origin: org,
Position: pos,
}
},
}
)
type numType int
const (
numTypeNone numType = iota
numTypeBinary
numTypeOctet
numTypeHex
numTypeFloat
)
type numStat struct {
isNum bool
typ numType
}
func getNumberStat(str string) *numStat {
stat := &numStat{}
if str == "" {
return stat
}
if str == "-" || str == "." || str == "+" || str == "_" {
return stat
}
if str[0] == '_' {
return stat
}
dotFound := false
isNegative := false
isExponent := false
if str[0] == '-' {
isNegative = true
}
for idx, c := range str {
switch c {
case 'x':
if (isNegative && idx == 2) || (!isNegative && idx == 1) {
continue
}
case 'o':
if (isNegative && idx == 2) || (!isNegative && idx == 1) {
continue
}
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
continue
case 'a', 'b', 'c', 'd', 'e', 'f', 'A', 'B', 'C', 'D', 'E', 'F':
if (len(str) > 2 && str[0] == '0' && str[1] == 'x') ||
(len(str) > 3 && isNegative && str[1] == '0' && str[2] == 'x') {
// hex number
continue
}
if c == 'b' && ((isNegative && idx == 2) || (!isNegative && idx == 1)) {
// binary number
continue
}
if (c == 'e' || c == 'E') && dotFound {
// exponent
isExponent = true
continue
}
case '.':
if dotFound {
// multiple dot
return stat
}
dotFound = true
continue
case '-':
if idx == 0 || isExponent {
continue
}
case '+':
if idx == 0 || isExponent {
continue
}
case '_':
continue
}
return stat
}
stat.isNum = true
switch {
case dotFound:
stat.typ = numTypeFloat
case strings.HasPrefix(str, "0b") || strings.HasPrefix(str, "-0b"):
stat.typ = numTypeBinary
case strings.HasPrefix(str, "0x") || strings.HasPrefix(str, "-0x"):
stat.typ = numTypeHex
case strings.HasPrefix(str, "0o") || strings.HasPrefix(str, "-0o"):
stat.typ = numTypeOctet
case (len(str) > 1 && str[0] == '0') || (len(str) > 1 && str[0] == '-' && str[1] == '0'):
stat.typ = numTypeOctet
}
return stat
}
func looksLikeTimeValue(value string) bool {
for i, c := range value {
switch c {
case ':', '1', '2', '3', '4', '5', '6', '7', '8', '9':
continue
case '0':
if i == 0 {
return false
}
continue
}
return false
}
return true
}
// IsNeedQuoted whether need quote for passed string or not
func IsNeedQuoted(value string) bool {
if value == "" {
return true
}
if _, exists := reservedKeywordMap[value]; exists {
return true
}
if stat := getNumberStat(value); stat.isNum {
return true
}
first := value[0]
switch first {
case '*', '&', '[', '{', '}', ']', ',', '!', '|', '>', '%', '\'', '"':
return true
}
last := value[len(value)-1]
switch last {
case ':':
return true
}
if looksLikeTimeValue(value) {
return true
}
for i, c := range value {
switch c {
case '#', '\\':
return true
case ':':
if i+1 < len(value) && value[i+1] == ' ' {
return true
}
}
}
return false
}
// LiteralBlockHeader detect literal block scalar header
func LiteralBlockHeader(value string) string {
lbc := DetectLineBreakCharacter(value)
switch {
case !strings.Contains(value, lbc):
return ""
case strings.HasSuffix(value, fmt.Sprintf("%s%s", lbc, lbc)):
return "|+"
case strings.HasSuffix(value, lbc):
return "|"
default:
return "|-"
}
}
// New create reserved keyword token or number token and other string token
func New(value string, org string, pos *Position) *Token {
fn := reservedKeywordMap[value]
if fn != nil {
return fn(value, org, pos)
}
if stat := getNumberStat(value); stat.isNum {
tk := &Token{
Type: IntegerType,
CharacterType: CharacterTypeMiscellaneous,
Indicator: NotIndicator,
Value: value,
Origin: org,
Position: pos,
}
switch stat.typ {
case numTypeFloat:
tk.Type = FloatType
case numTypeBinary:
tk.Type = BinaryIntegerType
case numTypeOctet:
tk.Type = OctetIntegerType
case numTypeHex:
tk.Type = HexIntegerType
}
return tk
}
return String(value, org, pos)
}
// Position type for position in YAML document
type Position struct {
Line int
Column int
Offset int
IndentNum int
IndentLevel int
}
// String position to text
func (p *Position) String() string {
return fmt.Sprintf("[level:%d,line:%d,column:%d,offset:%d]", p.IndentLevel, p.Line, p.Column, p.Offset)
}
// Token type for token
type Token struct {
Type Type
CharacterType CharacterType
Indicator Indicator
Value string
Origin string
Position *Position
Next *Token
Prev *Token
}
// PreviousType previous token type
func (t *Token) PreviousType() Type {
if t.Prev != nil {
return t.Prev.Type
}
return UnknownType
}
// NextType next token type
func (t *Token) NextType() Type {
if t.Next != nil {
return t.Next.Type
}
return UnknownType
}
// AddColumn append column number to current position of column
func (t *Token) AddColumn(col int) {
if t == nil {
return
}
t.Position.Column += col
}
// Clone copy token ( preserve Prev/Next reference )
func (t *Token) Clone() *Token {
if t == nil {
return nil
}
copied := *t
if t.Position != nil {
pos := *(t.Position)
copied.Position = &pos
}
return &copied
}
// Tokens type of token collection
type Tokens []*Token
func (t *Tokens) add(tk *Token) {
tokens := *t
if len(tokens) == 0 {
tokens = append(tokens, tk)
} else {
last := tokens[len(tokens)-1]
last.Next = tk
tk.Prev = last
tokens = append(tokens, tk)
}
*t = tokens
}
// Add append new some tokens
func (t *Tokens) Add(tks ...*Token) {
for _, tk := range tks {
t.add(tk)
}
}
// Dump dump all token structures for debugging
func (t Tokens) Dump() {
for _, tk := range t {
fmt.Printf("- %+v\n", tk)
}
}
// String create token for String
func String(value string, org string, pos *Position) *Token {
return &Token{
Type: StringType,
CharacterType: CharacterTypeMiscellaneous,
Indicator: NotIndicator,
Value: value,
Origin: org,
Position: pos,
}
}
// SequenceEntry create token for SequenceEntry
func SequenceEntry(org string, pos *Position) *Token {
return &Token{
Type: SequenceEntryType,
CharacterType: CharacterTypeIndicator,
Indicator: BlockStructureIndicator,
Value: string(SequenceEntryCharacter),
Origin: org,
Position: pos,
}
}
// MappingKey create token for MappingKey
func MappingKey(pos *Position) *Token {
return &Token{
Type: MappingKeyType,
CharacterType: CharacterTypeIndicator,
Indicator: BlockStructureIndicator,
Value: string(MappingKeyCharacter),
Origin: string(MappingKeyCharacter),
Position: pos,
}
}
// MappingValue create token for MappingValue
func MappingValue(pos *Position) *Token {
return &Token{
Type: MappingValueType,
CharacterType: CharacterTypeIndicator,
Indicator: BlockStructureIndicator,
Value: string(MappingValueCharacter),
Origin: string(MappingValueCharacter),
Position: pos,
}
}
// CollectEntry create token for CollectEntry
func CollectEntry(org string, pos *Position) *Token {
return &Token{
Type: CollectEntryType,
CharacterType: CharacterTypeIndicator,
Indicator: FlowCollectionIndicator,
Value: string(CollectEntryCharacter),
Origin: org,
Position: pos,
}
}
// SequenceStart create token for SequenceStart
func SequenceStart(org string, pos *Position) *Token {
return &Token{
Type: SequenceStartType,
CharacterType: CharacterTypeIndicator,
Indicator: FlowCollectionIndicator,
Value: string(SequenceStartCharacter),
Origin: org,
Position: pos,
}
}
// SequenceEnd create token for SequenceEnd
func SequenceEnd(org string, pos *Position) *Token {
return &Token{
Type: SequenceEndType,
CharacterType: CharacterTypeIndicator,
Indicator: FlowCollectionIndicator,
Value: string(SequenceEndCharacter),
Origin: org,
Position: pos,
}
}
// MappingStart create token for MappingStart
func MappingStart(org string, pos *Position) *Token {
return &Token{
Type: MappingStartType,
CharacterType: CharacterTypeIndicator,
Indicator: FlowCollectionIndicator,
Value: string(MappingStartCharacter),
Origin: org,
Position: pos,
}
}
// MappingEnd create token for MappingEnd
func MappingEnd(org string, pos *Position) *Token {
return &Token{
Type: MappingEndType,
CharacterType: CharacterTypeIndicator,
Indicator: FlowCollectionIndicator,
Value: string(MappingEndCharacter),
Origin: org,
Position: pos,
}
}
// Comment create token for Comment
func Comment(value string, org string, pos *Position) *Token {
return &Token{
Type: CommentType,
CharacterType: CharacterTypeIndicator,
Indicator: CommentIndicator,
Value: value,
Origin: org,
Position: pos,
}
}
// Anchor create token for Anchor
func Anchor(org string, pos *Position) *Token {
return &Token{
Type: AnchorType,
CharacterType: CharacterTypeIndicator,
Indicator: NodePropertyIndicator,
Value: string(AnchorCharacter),
Origin: org,
Position: pos,
}
}
// Alias create token for Alias
func Alias(org string, pos *Position) *Token {
return &Token{
Type: AliasType,
CharacterType: CharacterTypeIndicator,
Indicator: NodePropertyIndicator,
Value: string(AliasCharacter),
Origin: org,
Position: pos,
}
}
// Tag create token for Tag
func Tag(value string, org string, pos *Position) *Token {
fn := ReservedTagKeywordMap[ReservedTagKeyword(value)]
if fn != nil {
return fn(value, org, pos)
}
return &Token{
Type: TagType,
CharacterType: CharacterTypeIndicator,
Indicator: NodePropertyIndicator,
Value: value,
Origin: org,
Position: pos,
}
}
// Literal create token for Literal
func Literal(value string, org string, pos *Position) *Token {
return &Token{
Type: LiteralType,
CharacterType: CharacterTypeIndicator,
Indicator: BlockScalarIndicator,
Value: value,
Origin: org,
Position: pos,
}
}
// Folded create token for Folded
func Folded(value string, org string, pos *Position) *Token {
return &Token{
Type: FoldedType,
CharacterType: CharacterTypeIndicator,
Indicator: BlockScalarIndicator,
Value: value,
Origin: org,
Position: pos,
}
}
// SingleQuote create token for SingleQuote
func SingleQuote(value string, org string, pos *Position) *Token {
return &Token{
Type: SingleQuoteType,
CharacterType: CharacterTypeIndicator,
Indicator: QuotedScalarIndicator,
Value: value,
Origin: org,
Position: pos,
}
}
// DoubleQuote create token for DoubleQuote
func DoubleQuote(value string, org string, pos *Position) *Token {
return &Token{
Type: DoubleQuoteType,
CharacterType: CharacterTypeIndicator,
Indicator: QuotedScalarIndicator,
Value: value,
Origin: org,
Position: pos,
}
}
// Directive create token for Directive
func Directive(org string, pos *Position) *Token {
return &Token{
Type: DirectiveType,
CharacterType: CharacterTypeIndicator,
Indicator: DirectiveIndicator,
Value: string(DirectiveCharacter),
Origin: org,
Position: pos,
}
}
// Space create token for Space
func Space(pos *Position) *Token {
return &Token{
Type: SpaceType,
CharacterType: CharacterTypeWhiteSpace,
Indicator: NotIndicator,
Value: string(SpaceCharacter),
Origin: string(SpaceCharacter),
Position: pos,
}
}
// MergeKey create token for MergeKey
func MergeKey(org string, pos *Position) *Token {
return &Token{
Type: MergeKeyType,
CharacterType: CharacterTypeMiscellaneous,
Indicator: NotIndicator,
Value: "<<",
Origin: org,
Position: pos,
}
}
// DocumentHeader create token for DocumentHeader
func DocumentHeader(org string, pos *Position) *Token {
return &Token{
Type: DocumentHeaderType,
CharacterType: CharacterTypeMiscellaneous,
Indicator: NotIndicator,
Value: "---",
Origin: org,
Position: pos,
}
}
// DocumentEnd create token for DocumentEnd
func DocumentEnd(org string, pos *Position) *Token {
return &Token{
Type: DocumentEndType,
CharacterType: CharacterTypeMiscellaneous,
Indicator: NotIndicator,
Value: "...",
Origin: org,
Position: pos,
}
}
// DetectLineBreakCharacter detect line break character in only one inside scalar content scope.
func DetectLineBreakCharacter(src string) string {
nc := strings.Count(src, "\n")
rc := strings.Count(src, "\r")
rnc := strings.Count(src, "\r\n")
switch {
case nc == rnc && rc == rnc:
return "\r\n"
case rc > nc:
return "\r"
default:
return "\n"
}
}
golang-github-goccy-go-yaml-1.9.5/token/token_test.go 0000664 0000000 0000000 00000005571 14167531274 0022572 0 ustar 00root root 0000000 0000000 package token_test
import (
"testing"
"github.com/goccy/go-yaml/token"
)
func TestToken(t *testing.T) {
pos := &token.Position{}
tokens := token.Tokens{
token.SequenceEntry("-", pos),
token.MappingKey(pos),
token.MappingValue(pos),
token.CollectEntry(",", pos),
token.SequenceStart("[", pos),
token.SequenceEnd("]", pos),
token.MappingStart("{", pos),
token.MappingEnd("}", pos),
token.Comment("#", "#", pos),
token.Anchor("&", pos),
token.Alias("*", pos),
token.Literal("|", "|", pos),
token.Folded(">", ">", pos),
token.SingleQuote("'", "'", pos),
token.DoubleQuote(`"`, `"`, pos),
token.Directive("%", pos),
token.Space(pos),
token.MergeKey("<<", pos),
token.DocumentHeader("---", pos),
token.DocumentEnd("...", pos),
token.New("1", "1", pos),
token.New("3.14", "3.14", pos),
token.New("-0b101010", "-0b101010", pos),
token.New("0xA", "0xA", pos),
token.New("685.230_15e+03", "685.230_15e+03", pos),
token.New("02472256", "02472256", pos),
token.New("0o2472256", "0o2472256", pos),
token.New("", "", pos),
token.New("_1", "_1", pos),
token.New("1.1.1.1", "1.1.1.1", pos),
token.New("+", "+", pos),
token.New("-", "-", pos),
token.New("_", "_", pos),
token.New("~", "~", pos),
token.New("true", "true", pos),
token.New("false", "false", pos),
token.New(".nan", ".nan", pos),
token.New(".inf", ".inf", pos),
token.New("-.inf", "-.inf", pos),
token.New("null", "null", pos),
token.Tag("!!null", "!!null", pos),
token.Tag("!!map", "!!map", pos),
token.Tag("!!str", "!!str", pos),
token.Tag("!!seq", "!!seq", pos),
token.Tag("!!binary", "!!binary", pos),
token.Tag("!!omap", "!!omap", pos),
token.Tag("!!set", "!!set", pos),
token.Tag("!!int", "!!int", pos),
token.Tag("!!float", "!!float", pos),
token.Tag("!hoge", "!hoge", pos),
}
tokens.Dump()
tokens.Add(token.New("hoge", "hoge", pos))
if tokens[len(tokens)-1].PreviousType() != token.TagType {
t.Fatal("invalid previous token type")
}
if tokens[0].PreviousType() != token.UnknownType {
t.Fatal("invalid previous token type")
}
if tokens[len(tokens)-2].NextType() != token.StringType {
t.Fatal("invalid next token type")
}
if tokens[len(tokens)-1].NextType() != token.UnknownType {
t.Fatal("invalid next token type")
}
}
func TestIsNeedQuoted(t *testing.T) {
needQuotedTests := []string{
"",
"true",
"1.234",
"1:1",
"hoge # comment",
"\\0",
"#a b",
"*a b",
"&a b",
"{a b",
"}a b",
"[a b",
"]a b",
",a b",
"!a b",
"|a b",
">a b",
">a b",
"%a b",
`'a b`,
`"a b`,
"a:",
"a: b",
}
for i, test := range needQuotedTests {
if !token.IsNeedQuoted(test) {
t.Fatalf("%d: failed to quoted judge for %s", i, test)
}
}
notNeedQuotedTests := []string{
"Hello World",
}
for i, test := range notNeedQuotedTests {
if token.IsNeedQuoted(test) {
t.Fatalf("%d: failed to quoted judge for %s", i, test)
}
}
}
golang-github-goccy-go-yaml-1.9.5/validate.go 0000664 0000000 0000000 00000000626 14167531274 0021060 0 ustar 00root root 0000000 0000000 package yaml
// StructValidator need to implement Struct method only
// ( see https://pkg.go.dev/github.com/go-playground/validator/v10#Validate.Struct )
type StructValidator interface {
Struct(interface{}) error
}
// FieldError need to implement StructField method only
// ( see https://pkg.go.dev/github.com/go-playground/validator/v10#FieldError )
type FieldError interface {
StructField() string
}
golang-github-goccy-go-yaml-1.9.5/validate_test.go 0000664 0000000 0000000 00000011061 14167531274 0022112 0 ustar 00root root 0000000 0000000 package yaml_test
import (
"strings"
"testing"
"github.com/go-playground/validator/v10"
"github.com/goccy/go-yaml"
)
func TestStructValidator(t *testing.T) {
type Inner struct {
Required string `validate:"required"`
Lt10 int `validate:"lt=10"`
}
cases := []struct {
TestName string
YAMLContent string
ExpectedErr string
Instance interface{}
}{
{
TestName: "Test Simple Validation",
YAMLContent: `---
- name: john
age: 20
- name: tom
age: -1
- name: ken
age: 10`,
ExpectedErr: `[5:8] Key: 'Age' Error:Field validation for 'Age' failed on the 'gte' tag
2 | - name: john
3 | age: 20
4 | - name: tom
> 5 | age: -1
^
6 | - name: ken
7 | age: 10`,
Instance: &[]struct {
Name string `yaml:"name" validate:"required"`
Age int `yaml:"age" validate:"gte=0,lt=120"`
}{},
},
{
TestName: "Test Missing Required Field",
YAMLContent: `---
- name: john
age: 20
- age: 10`,
ExpectedErr: `[4:1] Key: 'Name' Error:Field validation for 'Name' failed on the 'required' tag
1 | ---
2 | - name: john
3 | age: 20
> 4 | - age: 10
^
`,
Instance: &[]struct {
Name string `yaml:"name" validate:"required"`
Age int `yaml:"age" validate:"gte=0,lt=120"`
}{},
},
{
TestName: "Test Nested Validation Missing Internal Required",
YAMLContent: `---
name: john
age: 10
addr:
number: seven`,
ExpectedErr: `[4:5] Key: 'State' Error:Field validation for 'State' failed on the 'required' tag
1 | ---
2 | name: john
3 | age: 10
> 4 | addr:
^
5 | number: seven`,
Instance: &struct {
Name string `yaml:"name" validate:"required"`
Age int `yaml:"age" validate:"gte=0,lt=120"`
Addr struct {
Number string `yaml:"number" validate:"required"`
State string `yaml:"state" validate:"required"`
} `yaml:"addr"`
}{},
},
{
TestName: "Test nested Validation with unknown field",
YAMLContent: `---
name: john
age: 20
addr:
number: seven
state: washington
error: error
`,
ExpectedErr: `[7:3] unknown field "error"
4 | addr:
5 | number: seven
6 | state: washington
> 7 | error: error
^
`,
Instance: &struct {
Name string `yaml:"name" validate:"required"`
Age int `yaml:"age" validate:"gte=0,lt=120"`
Addr *struct {
Number string `yaml:"number" validate:"required"`
State string `yaml:"state" validate:"required"`
} `yaml:"addr" validate:"required"`
}{},
},
{
TestName: "Test Validation with wrong field type",
YAMLContent: `---
name: myDocument
roles:
name: myRole
permissions:
- hello
- how
- are
- you
`,
ExpectedErr: `[4:7] mapping was used where sequence is expected
1 | ---
2 | name: myDocument
3 | roles:
> 4 | name: myRole
^
5 | permissions:
6 | - hello
7 | - how
8 | `,
Instance: &struct {
Name string `yaml:"name"`
Roles []struct {
Name string `yaml:"name"`
Permissions []string `yaml:"permissions"`
} `yaml:"roles"`
}{},
},
{
TestName: "Test inline validation missing required",
YAMLContent: `---
name: john
age: 20
`,
ExpectedErr: `Key: 'Inner.Required' Error:Field validation for 'Required' failed on the 'required' tag`,
Instance: &struct {
Name string `yaml:"name" validate:"required"`
Age int `yaml:"age" validate:"gte=0,lt=120"`
Inner `yaml:",inline"`
}{},
},
{
TestName: "Test inline validation field error",
YAMLContent: `---
name: john
age: 20
required: present
lt10: 20
`,
ExpectedErr: `[5:7] Key: 'Inner.Lt10' Error:Field validation for 'Lt10' failed on the 'lt' tag
2 | name: john
3 | age: 20
4 | required: present
> 5 | lt10: 20
^
`,
Instance: &struct {
Name string `yaml:"name" validate:"required"`
Age int `yaml:"age" validate:"gte=0,lt=120"`
Inner `yaml:",inline"`
}{},
},
}
for _, tc := range cases {
tc := tc // NOTE: https://github.com/golang/go/wiki/CommonMistakes#using-goroutines-on-loop-iterator-variables
t.Run(tc.TestName, func(t *testing.T) {
validate := validator.New()
dec := yaml.NewDecoder(
strings.NewReader(tc.YAMLContent),
yaml.Validator(validate),
yaml.Strict(),
)
err := dec.Decode(tc.Instance)
switch {
case tc.ExpectedErr != "" && err == nil:
t.Fatal("expected error")
case tc.ExpectedErr == "" && err != nil:
t.Fatalf("unexpected error: %v", err)
case tc.ExpectedErr != "" && tc.ExpectedErr != err.Error():
t.Fatalf("expected `%s` but actual `%s`", tc.ExpectedErr, err.Error())
}
})
}
}
golang-github-goccy-go-yaml-1.9.5/yaml.go 0000664 0000000 0000000 00000021442 14167531274 0020230 0 ustar 00root root 0000000 0000000 package yaml
import (
"bytes"
"context"
"io"
"github.com/goccy/go-yaml/ast"
"github.com/goccy/go-yaml/internal/errors"
"golang.org/x/xerrors"
)
// BytesMarshaler interface may be implemented by types to customize their
// behavior when being marshaled into a YAML document. The returned value
// is marshaled in place of the original value implementing Marshaler.
//
// If an error is returned by MarshalYAML, the marshaling procedure stops
// and returns with the provided error.
type BytesMarshaler interface {
MarshalYAML() ([]byte, error)
}
// BytesMarshalerContext interface use BytesMarshaler with context.Context.
type BytesMarshalerContext interface {
MarshalYAML(context.Context) ([]byte, error)
}
// InterfaceMarshaler interface has MarshalYAML compatible with github.com/go-yaml/yaml package.
type InterfaceMarshaler interface {
MarshalYAML() (interface{}, error)
}
// InterfaceMarshalerContext interface use InterfaceMarshaler with context.Context.
type InterfaceMarshalerContext interface {
MarshalYAML(context.Context) (interface{}, error)
}
// BytesUnmarshaler interface may be implemented by types to customize their
// behavior when being unmarshaled from a YAML document.
type BytesUnmarshaler interface {
UnmarshalYAML([]byte) error
}
// BytesUnmarshalerContext interface use BytesUnmarshaler with context.Context.
type BytesUnmarshalerContext interface {
UnmarshalYAML(context.Context, []byte) error
}
// InterfaceUnmarshaler interface has UnmarshalYAML compatible with github.com/go-yaml/yaml package.
type InterfaceUnmarshaler interface {
UnmarshalYAML(func(interface{}) error) error
}
// InterfaceUnmarshalerContext interface use InterfaceUnmarshaler with context.Context.
type InterfaceUnmarshalerContext interface {
UnmarshalYAML(context.Context, func(interface{}) error) error
}
// MapItem is an item in a MapSlice.
type MapItem struct {
Key, Value interface{}
}
// MapSlice encodes and decodes as a YAML map.
// The order of keys is preserved when encoding and decoding.
type MapSlice []MapItem
// ToMap convert to map[interface{}]interface{}.
func (s MapSlice) ToMap() map[interface{}]interface{} {
v := map[interface{}]interface{}{}
for _, item := range s {
v[item.Key] = item.Value
}
return v
}
// Marshal serializes the value provided into a YAML document. The structure
// of the generated document will reflect the structure of the value itself.
// Maps and pointers (to struct, string, int, etc) are accepted as the in value.
//
// Struct fields are only marshalled if they are exported (have an upper case
// first letter), and are marshalled using the field name lowercased as the
// default key. Custom keys may be defined via the "yaml" name in the field
// tag: the content preceding the first comma is used as the key, and the
// following comma-separated options are used to tweak the marshalling process.
// Conflicting names result in a runtime error.
//
// The field tag format accepted is:
//
// `(...) yaml:"[][,[,]]" (...)`
//
// The following flags are currently supported:
//
// omitempty Only include the field if it's not set to the zero
// value for the type or to empty slices or maps.
// Zero valued structs will be omitted if all their public
// fields are zero, unless they implement an IsZero
// method (see the IsZeroer interface type), in which
// case the field will be included if that method returns true.
//
// flow Marshal using a flow style (useful for structs,
// sequences and maps).
//
// inline Inline the field, which must be a struct or a map,
// causing all of its fields or keys to be processed as if
// they were part of the outer struct. For maps, keys must
// not conflict with the yaml keys of other struct fields.
//
// anchor Marshal with anchor. If want to define anchor name explicitly, use anchor=name style.
// Otherwise, if used 'anchor' name only, used the field name lowercased as the anchor name
//
// alias Marshal with alias. If want to define alias name explicitly, use alias=name style.
// Otherwise, If omitted alias name and the field type is pointer type,
// assigned anchor name automatically from same pointer address.
//
// In addition, if the key is "-", the field is ignored.
//
// For example:
//
// type T struct {
// F int `yaml:"a,omitempty"`
// B int
// }
// yaml.Marshal(&T{B: 2}) // Returns "b: 2\n"
// yaml.Marshal(&T{F: 1}) // Returns "a: 1\nb: 0\n"
//
func Marshal(v interface{}) ([]byte, error) {
return MarshalWithOptions(v)
}
// MarshalWithOptions serializes the value provided into a YAML document with EncodeOptions.
func MarshalWithOptions(v interface{}, opts ...EncodeOption) ([]byte, error) {
return MarshalContext(context.Background(), v, opts...)
}
// MarshalContext serializes the value provided into a YAML document with context.Context and EncodeOptions.
func MarshalContext(ctx context.Context, v interface{}, opts ...EncodeOption) ([]byte, error) {
var buf bytes.Buffer
if err := NewEncoder(&buf, opts...).EncodeContext(ctx, v); err != nil {
return nil, errors.Wrapf(err, "failed to marshal")
}
return buf.Bytes(), nil
}
// ValueToNode convert from value to ast.Node.
func ValueToNode(v interface{}, opts ...EncodeOption) (ast.Node, error) {
var buf bytes.Buffer
node, err := NewEncoder(&buf, opts...).EncodeToNode(v)
if err != nil {
return nil, errors.Wrapf(err, "failed to convert value to node")
}
return node, nil
}
// Unmarshal decodes the first document found within the in byte slice
// and assigns decoded values into the out value.
//
// Struct fields are only unmarshalled if they are exported (have an
// upper case first letter), and are unmarshalled using the field name
// lowercased as the default key. Custom keys may be defined via the
// "yaml" name in the field tag: the content preceding the first comma
// is used as the key, and the following comma-separated options are
// used to tweak the marshalling process (see Marshal).
// Conflicting names result in a runtime error.
//
// For example:
//
// type T struct {
// F int `yaml:"a,omitempty"`
// B int
// }
// var t T
// yaml.Unmarshal([]byte("a: 1\nb: 2"), &t)
//
// See the documentation of Marshal for the format of tags and a list of
// supported tag options.
//
func Unmarshal(data []byte, v interface{}) error {
return UnmarshalWithOptions(data, v)
}
// UnmarshalWithOptions decodes with DecodeOptions the first document found within the in byte slice
// and assigns decoded values into the out value.
func UnmarshalWithOptions(data []byte, v interface{}, opts ...DecodeOption) error {
return UnmarshalContext(context.Background(), data, v, opts...)
}
// UnmarshalContext decodes with context.Context and DecodeOptions.
func UnmarshalContext(ctx context.Context, data []byte, v interface{}, opts ...DecodeOption) error {
dec := NewDecoder(bytes.NewBuffer(data), opts...)
if err := dec.DecodeContext(ctx, v); err != nil {
if err == io.EOF {
return nil
}
return errors.Wrapf(err, "failed to unmarshal")
}
return nil
}
// NodeToValue converts node to the value pointed to by v.
func NodeToValue(node ast.Node, v interface{}, opts ...DecodeOption) error {
var buf bytes.Buffer
if err := NewDecoder(&buf, opts...).DecodeFromNode(node, v); err != nil {
return errors.Wrapf(err, "failed to convert node to value")
}
return nil
}
// FormatError is a utility function that takes advantage of the metadata
// stored in the errors returned by this package's parser.
//
// If the second argument `colored` is true, the error message is colorized.
// If the third argument `inclSource` is true, the error message will
// contain snippets of the YAML source that was used.
func FormatError(e error, colored, inclSource bool) string {
var pp errors.PrettyPrinter
if xerrors.As(e, &pp) {
var buf bytes.Buffer
pp.PrettyPrint(&errors.Sink{&buf}, colored, inclSource)
return buf.String()
}
return e.Error()
}
// YAMLToJSON convert YAML bytes to JSON.
func YAMLToJSON(bytes []byte) ([]byte, error) {
var v interface{}
if err := UnmarshalWithOptions(bytes, &v, UseOrderedMap()); err != nil {
return nil, errors.Wrapf(err, "failed to unmarshal")
}
out, err := MarshalWithOptions(v, JSON())
if err != nil {
return nil, errors.Wrapf(err, "failed to marshal with json option")
}
return out, nil
}
// JSONToYAML convert JSON bytes to YAML.
func JSONToYAML(bytes []byte) ([]byte, error) {
var v interface{}
if err := UnmarshalWithOptions(bytes, &v, UseOrderedMap()); err != nil {
return nil, errors.Wrapf(err, "failed to unmarshal from json bytes")
}
out, err := Marshal(v)
if err != nil {
return nil, errors.Wrapf(err, "failed to marshal")
}
return out, nil
}
golang-github-goccy-go-yaml-1.9.5/yaml_test.go 0000664 0000000 0000000 00000024471 14167531274 0021274 0 ustar 00root root 0000000 0000000 package yaml_test
import (
"bytes"
"reflect"
"testing"
"github.com/goccy/go-yaml"
"github.com/goccy/go-yaml/ast"
"golang.org/x/xerrors"
)
func TestMarshal(t *testing.T) {
var v struct {
A int
B string
}
v.A = 1
v.B = "hello"
bytes, err := yaml.Marshal(v)
if err != nil {
t.Fatalf("%+v", err)
}
if string(bytes) != "a: 1\nb: hello\n" {
t.Fatal("failed to marshal")
}
}
func TestUnmarshal(t *testing.T) {
yml := `
%YAML 1.2
---
a: 1
b: c
`
var v struct {
A int
B string
}
if err := yaml.Unmarshal([]byte(yml), &v); err != nil {
t.Fatalf("%+v", err)
}
}
type marshalTest struct{}
func (t *marshalTest) MarshalYAML() ([]byte, error) {
return yaml.Marshal(yaml.MapSlice{
{
"a", 1,
},
{
"b", "hello",
},
{
"c", true,
},
{
"d", map[string]string{"x": "y"},
},
})
}
type marshalTest2 struct{}
func (t *marshalTest2) MarshalYAML() (interface{}, error) {
return yaml.MapSlice{
{
"a", 2,
},
{
"b", "world",
},
{
"c", true,
},
}, nil
}
func TestMarshalYAML(t *testing.T) {
var v struct {
A *marshalTest
B *marshalTest2
}
v.A = &marshalTest{}
v.B = &marshalTest2{}
bytes, err := yaml.Marshal(v)
if err != nil {
t.Fatalf("failed to Marshal: %+v", err)
}
expect := `
a:
a: 1
b: hello
c: true
d:
x: y
b:
a: 2
b: world
c: true
`
actual := "\n" + string(bytes)
if expect != actual {
t.Fatalf("failed to MarshalYAML expect:[%s], actual:[%s]", expect, actual)
}
}
type unmarshalTest struct {
a int
b string
c bool
}
func (t *unmarshalTest) UnmarshalYAML(b []byte) error {
if t.a != 0 {
return xerrors.New("unexpected field value to a")
}
if t.b != "" {
return xerrors.New("unexpected field value to b")
}
if t.c {
return xerrors.New("unexpected field value to c")
}
var v struct {
A int
B string
C bool
}
if err := yaml.Unmarshal(b, &v); err != nil {
return err
}
t.a = v.A
t.b = v.B
t.c = v.C
return nil
}
type unmarshalTest2 struct {
a int
b string
c bool
}
func (t *unmarshalTest2) UnmarshalYAML(unmarshal func(interface{}) error) error {
var v struct {
A int
B string
C bool
}
if t.a != 0 {
return xerrors.New("unexpected field value to a")
}
if t.b != "" {
return xerrors.New("unexpected field value to b")
}
if t.c {
return xerrors.New("unexpected field value to c")
}
if err := unmarshal(&v); err != nil {
return err
}
t.a = v.A
t.b = v.B
t.c = v.C
return nil
}
func TestUnmarshalYAML(t *testing.T) {
yml := `
a:
a: 1
b: hello
c: true
b:
a: 2
b: world
c: true
`
var v struct {
A *unmarshalTest
B *unmarshalTest2
}
if err := yaml.Unmarshal([]byte(yml), &v); err != nil {
t.Fatalf("failed to Unmarshal: %+v", err)
}
if v.A == nil {
t.Fatal("failed to UnmarshalYAML")
}
if v.A.a != 1 {
t.Fatal("failed to UnmarshalYAML")
}
if v.A.b != "hello" {
t.Fatal("failed to UnmarshalYAML")
}
if !v.A.c {
t.Fatal("failed to UnmarshalYAML")
}
if v.B == nil {
t.Fatal("failed to UnmarshalYAML")
}
if v.B.a != 2 {
t.Fatal("failed to UnmarshalYAML")
}
if v.B.b != "world" {
t.Fatal("failed to UnmarshalYAML")
}
if !v.B.c {
t.Fatal("failed to UnmarshalYAML")
}
}
type ObjectMap map[string]*Object
type ObjectDecl struct {
Name string `yaml:"-"`
*Object `yaml:",inline,anchor"`
}
func (m ObjectMap) MarshalYAML() (interface{}, error) {
newMap := map[string]*ObjectDecl{}
for k, v := range m {
newMap[k] = &ObjectDecl{Name: k, Object: v}
}
return newMap, nil
}
type rootObject struct {
Single ObjectMap `yaml:"single"`
Collection map[string][]*Object `yaml:"collection"`
}
type Object struct {
*Object `yaml:",omitempty,inline,alias"`
MapValue map[string]interface{} `yaml:",omitempty,inline"`
}
func TestInlineAnchorAndAlias(t *testing.T) {
yml := `---
single:
default: &default
id: 1
name: john
user_1: &user_1
id: 1
name: ken
user_2: &user_2
<<: *default
id: 2
collection:
defaults:
- *default
- <<: *default
- <<: *default
id: 2
users:
- <<: *user_1
- <<: *user_2
- <<: *user_1
id: 3
- <<: *user_1
id: 4
- <<: *user_1
id: 5
`
var v rootObject
if err := yaml.Unmarshal([]byte(yml), &v); err != nil {
t.Fatal(err)
}
opt := yaml.MarshalAnchor(func(anchor *ast.AnchorNode, value interface{}) error {
if o, ok := value.(*ObjectDecl); ok {
return anchor.SetName(o.Name)
}
return nil
})
var buf bytes.Buffer
if err := yaml.NewEncoder(&buf, opt).Encode(v); err != nil {
t.Fatalf("%+v", err)
}
actual := "---\n" + buf.String()
if yml != actual {
t.Fatalf("failed to marshal: expected:[%s] actual:[%s]", yml, actual)
}
}
func TestMapSlice_Map(t *testing.T) {
yml := `
a: b
c: d
`
var v yaml.MapSlice
if err := yaml.Unmarshal([]byte(yml), &v); err != nil {
t.Fatal(err)
}
m := v.ToMap()
if len(m) != 2 {
t.Fatal("failed to convert MapSlice to map")
}
if m["a"] != "b" {
t.Fatal("failed to convert MapSlice to map")
}
if m["c"] != "d" {
t.Fatal("failed to convert MapSlice to map")
}
}
func TestMarshalWithModifiedAnchorAlias(t *testing.T) {
yml := `
a: &a 1
b: *a
`
var v struct {
A *int `yaml:"a,anchor"`
B *int `yaml:"b,alias"`
}
if err := yaml.Unmarshal([]byte(yml), &v); err != nil {
t.Fatal(err)
}
node, err := yaml.ValueToNode(v)
if err != nil {
t.Fatal(err)
}
anchors := ast.Filter(ast.AnchorType, node)
if len(anchors) != 1 {
t.Fatal("failed to filter node")
}
anchor := anchors[0].(*ast.AnchorNode)
if err := anchor.SetName("b"); err != nil {
t.Fatal(err)
}
aliases := ast.Filter(ast.AliasType, node)
if len(anchors) != 1 {
t.Fatal("failed to filter node")
}
alias := aliases[0].(*ast.AliasNode)
if err := alias.SetName("b"); err != nil {
t.Fatal(err)
}
expected := `
a: &b 1
b: *b`
actual := "\n" + node.String()
if expected != actual {
t.Fatalf("failed to marshal: expected:[%q] but got [%q]", expected, actual)
}
}
func Test_YAMLToJSON(t *testing.T) {
yml := `
foo:
bar:
- a
- b
- c
a: 1
`
actual, err := yaml.YAMLToJSON([]byte(yml))
if err != nil {
t.Fatal(err)
}
expected := `{"foo": {"bar": ["a", "b", "c"]}, "a": 1}`
if expected+"\n" != string(actual) {
t.Fatalf("failed to convert yaml to json: expected [%q] but got [%q]", expected, actual)
}
}
func Test_JSONToYAML(t *testing.T) {
json := `{"foo": {"bar": ["a", "b", "c"]}, "a": 1}`
expected := `
foo:
bar:
- a
- b
- c
a: 1
`
actual, err := yaml.JSONToYAML([]byte(json))
if err != nil {
t.Fatal(err)
}
if expected != "\n"+string(actual) {
t.Fatalf("failed to convert json to yaml: expected [%q] but got [%q]", expected, actual)
}
}
func Test_WithCommentOption(t *testing.T) {
v := struct {
Foo string `yaml:"foo"`
Bar map[string]interface{} `yaml:"bar"`
Baz struct {
X int `yaml:"x"`
} `yaml:"baz"`
}{
Foo: "aaa",
Bar: map[string]interface{}{"bbb": "ccc"},
Baz: struct {
X int `yaml:"x"`
}{X: 10},
}
t.Run("line comment", func(t *testing.T) {
b, err := yaml.MarshalWithOptions(v, yaml.WithComment(
yaml.CommentMap{
"$.foo": yaml.LineComment("foo comment"),
"$.bar": yaml.LineComment("bar comment"),
"$.bar.bbb": yaml.LineComment("bbb comment"),
"$.baz.x": yaml.LineComment("x comment"),
},
))
if err != nil {
t.Fatal(err)
}
expected := `
foo: aaa #foo comment
bar: #bar comment
bbb: ccc #bbb comment
baz:
x: 10 #x comment
`
actual := "\n" + string(b)
if expected != actual {
t.Fatalf("expected:%s but got %s", expected, actual)
}
})
t.Run("head comment", func(t *testing.T) {
b, err := yaml.MarshalWithOptions(v, yaml.WithComment(
yaml.CommentMap{
"$.foo": yaml.HeadComment(
"foo comment",
"foo comment2",
),
"$.bar": yaml.HeadComment(
"bar comment",
"bar comment2",
),
"$.bar.bbb": yaml.HeadComment(
"bbb comment",
"bbb comment2",
),
"$.baz.x": yaml.HeadComment(
"x comment",
"x comment2",
),
},
))
if err != nil {
t.Fatal(err)
}
expected := `
#foo comment
#foo comment2
foo: aaa
#bar comment
#bar comment2
bar:
#bbb comment
#bbb comment2
bbb: ccc
baz:
#x comment
#x comment2
x: 10
`
actual := "\n" + string(b)
if expected != actual {
t.Fatalf("expected:%s but got %s", expected, actual)
}
})
}
func Test_CommentToMapOption(t *testing.T) {
t.Run("line comment", func(t *testing.T) {
yml := `
foo: aaa #foo comment
bar: #bar comment
bbb: ccc #bbb comment
baz:
x: 10 #x comment
`
var (
v interface{}
cm = yaml.CommentMap{}
)
if err := yaml.UnmarshalWithOptions([]byte(yml), &v, yaml.CommentToMap(cm)); err != nil {
t.Fatal(err)
}
expected := []struct {
path string
comment *yaml.Comment
}{
{
path: "$.foo",
comment: yaml.LineComment("foo comment"),
},
{
path: "$.bar",
comment: yaml.LineComment("bar comment"),
},
{
path: "$.bar.bbb",
comment: yaml.LineComment("bbb comment"),
},
{
path: "$.baz.x",
comment: yaml.LineComment("x comment"),
},
}
for _, exp := range expected {
comment := cm[exp.path]
if comment == nil {
t.Fatalf("failed to get path %s", exp.path)
}
if !reflect.DeepEqual(exp.comment, comment) {
t.Fatalf("failed to get comment. expected:[%+v] but got:[%+v]", exp.comment, comment)
}
}
})
t.Run("head comment", func(t *testing.T) {
yml := `
#foo comment
#foo comment2
foo: aaa
#bar comment
#bar comment2
bar:
#bbb comment
#bbb comment2
bbb: ccc
baz:
#x comment
#x comment2
x: 10
`
var (
v interface{}
cm = yaml.CommentMap{}
)
if err := yaml.UnmarshalWithOptions([]byte(yml), &v, yaml.CommentToMap(cm)); err != nil {
t.Fatal(err)
}
expected := []struct {
path string
comment *yaml.Comment
}{
{
path: "$.foo",
comment: yaml.HeadComment(
"foo comment",
"foo comment2",
),
},
{
path: "$.bar",
comment: yaml.HeadComment(
"bar comment",
"bar comment2",
),
},
{
path: "$.bar.bbb",
comment: yaml.HeadComment(
"bbb comment",
"bbb comment2",
),
},
{
path: "$.baz.x",
comment: yaml.HeadComment(
"x comment",
"x comment2",
),
},
}
for _, exp := range expected {
comment := cm[exp.path]
if comment == nil {
t.Fatalf("failed to get path %s", exp.path)
}
if !reflect.DeepEqual(exp.comment, comment) {
t.Fatalf("failed to get comment. expected:[%+v] but got:[%+v]", exp.comment, comment)
}
}
})
}