pax_global_header00006660000000000000000000000064141675312740014524gustar00rootroot0000000000000052 comment=e4f32a26722ce33ac79a7b4ad88c26fd0b9c2995 golang-github-goccy-go-yaml-1.9.5/000077500000000000000000000000001416753127400167345ustar00rootroot00000000000000golang-github-goccy-go-yaml-1.9.5/.codecov.yml000066400000000000000000000006341416753127400211620ustar00rootroot00000000000000codecov: 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/000077500000000000000000000000001416753127400202745ustar00rootroot00000000000000golang-github-goccy-go-yaml-1.9.5/.github/FUNDING.yml000066400000000000000000000000201416753127400221010ustar00rootroot00000000000000github: [goccy] golang-github-goccy-go-yaml-1.9.5/.github/workflows/000077500000000000000000000000001416753127400223315ustar00rootroot00000000000000golang-github-goccy-go-yaml-1.9.5/.github/workflows/go.yml000066400000000000000000000023641416753127400234660ustar00rootroot00000000000000name: 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.md000066400000000000000000000061001416753127400205420ustar00rootroot00000000000000## 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/LICENSE000066400000000000000000000020601416753127400177370ustar00rootroot00000000000000MIT 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/Makefile000066400000000000000000000003521416753127400203740ustar00rootroot00000000000000.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.md000066400000000000000000000213451416753127400202200ustar00rootroot00000000000000# YAML support for the Go language [![PkgGoDev](https://pkg.go.dev/badge/github.com/goccy/go-yaml)](https://pkg.go.dev/github.com/goccy/go-yaml) ![Go](https://github.com/goccy/go-yaml/workflows/Go/badge.svg) [![codecov](https://codecov.io/gh/goccy/go-yaml/branch/master/graph/badge.svg)](https://codecov.io/gh/goccy/go-yaml) [![Go Report Card](https://goreportcard.com/badge/github.com/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 ycat ### 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/000077500000000000000000000000001416753127400175235ustar00rootroot00000000000000golang-github-goccy-go-yaml-1.9.5/ast/ast.go000066400000000000000000001436531416753127400206550ustar00rootroot00000000000000package 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.go000066400000000000000000000003471416753127400217040ustar00rootroot00000000000000package 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/000077500000000000000000000000001416753127400210515ustar00rootroot00000000000000golang-github-goccy-go-yaml-1.9.5/benchmarks/benchmark_test.go000066400000000000000000000016571416753127400244020ustar00rootroot00000000000000package 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.mod000066400000000000000000000003341416753127400221570ustar00rootroot00000000000000module 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.sum000066400000000000000000000070441416753127400222110ustar00rootroot00000000000000github.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/000077500000000000000000000000001416753127400174775ustar00rootroot00000000000000golang-github-goccy-go-yaml-1.9.5/cmd/ycat/000077500000000000000000000000001416753127400204375ustar00rootroot00000000000000golang-github-goccy-go-yaml-1.9.5/cmd/ycat/ycat.go000066400000000000000000000034661416753127400217370ustar00rootroot00000000000000package 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.go000066400000000000000000001236501416753127400205150ustar00rootroot00000000000000package 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.go000066400000000000000000001520071416753127400215520ustar00rootroot00000000000000package 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.go000066400000000000000000000511411416753127400205220ustar00rootroot00000000000000package 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.go000066400000000000000000000576621416753127400215770ustar00rootroot00000000000000package 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.go000066400000000000000000000034201416753127400204130ustar00rootroot00000000000000package 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.mod000066400000000000000000000003451416753127400200440ustar00rootroot00000000000000module 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.sum000066400000000000000000000074421416753127400200760ustar00rootroot00000000000000github.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/000077500000000000000000000000001416753127400205505ustar00rootroot00000000000000golang-github-goccy-go-yaml-1.9.5/internal/errors/000077500000000000000000000000001416753127400220645ustar00rootroot00000000000000golang-github-goccy-go-yaml-1.9.5/internal/errors/error.go000066400000000000000000000110721416753127400235450ustar00rootroot00000000000000package 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/000077500000000000000000000000001416753127400200535ustar00rootroot00000000000000golang-github-goccy-go-yaml-1.9.5/lexer/lexer.go000066400000000000000000000005641416753127400215260ustar00rootroot00000000000000package 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.go000066400000000000000000000177311416753127400225710ustar00rootroot00000000000000package 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.go000066400000000000000000000135761416753127400206070ustar00rootroot00000000000000package 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/000077500000000000000000000000001416753127400202305ustar00rootroot00000000000000golang-github-goccy-go-yaml-1.9.5/parser/context.go000066400000000000000000000070351416753127400222500ustar00rootroot00000000000000package 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.go000066400000000000000000000444571416753127400220710ustar00rootroot00000000000000package 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.go000066400000000000000000000243751416753127400231250ustar00rootroot00000000000000package 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/000077500000000000000000000000001416753127400220415ustar00rootroot00000000000000golang-github-goccy-go-yaml-1.9.5/parser/testdata/cr.yml000066400000000000000000000000151416753127400231640ustar00rootroot00000000000000a: "a" b: 1 golang-github-goccy-go-yaml-1.9.5/parser/testdata/crlf.yml000066400000000000000000000000201416753127400235020ustar00rootroot00000000000000a: "a" b: 1 golang-github-goccy-go-yaml-1.9.5/parser/testdata/lf.yml000066400000000000000000000000151416753127400231610ustar00rootroot00000000000000a: "a" b: 1 golang-github-goccy-go-yaml-1.9.5/path.go000066400000000000000000000513421416753127400202240ustar00rootroot00000000000000package 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.go000066400000000000000000000310101416753127400212510ustar00rootroot00000000000000package 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/000077500000000000000000000000001416753127400204175ustar00rootroot00000000000000golang-github-goccy-go-yaml-1.9.5/printer/printer.go000066400000000000000000000176171416753127400224450ustar00rootroot00000000000000package 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.go000066400000000000000000000101361416753127400234710ustar00rootroot00000000000000package 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/000077500000000000000000000000001416753127400203655ustar00rootroot00000000000000golang-github-goccy-go-yaml-1.9.5/scanner/context.go000066400000000000000000000067651416753127400224160ustar00rootroot00000000000000package 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.go000066400000000000000000000523121416753127400223500ustar00rootroot00000000000000package 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.go000066400000000000000000000053541416753127400217700ustar00rootroot00000000000000// 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.go000066400000000000000000000055401416753127400206130ustar00rootroot00000000000000package 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/000077500000000000000000000000001416753127400205455ustar00rootroot00000000000000golang-github-goccy-go-yaml-1.9.5/testdata/anchor.yml000066400000000000000000000000301416753127400225330ustar00rootroot00000000000000a: &a b: 1 c: hello golang-github-goccy-go-yaml-1.9.5/token/000077500000000000000000000000001416753127400200545ustar00rootroot00000000000000golang-github-goccy-go-yaml-1.9.5/token/token.go000066400000000000000000000613001416753127400215230ustar00rootroot00000000000000package 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.go000066400000000000000000000055711416753127400225720ustar00rootroot00000000000000package 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.go000066400000000000000000000006261416753127400210600ustar00rootroot00000000000000package 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.go000066400000000000000000000110611416753127400221120ustar00rootroot00000000000000package 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.go000066400000000000000000000214421416753127400202300ustar00rootroot00000000000000package 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.go000066400000000000000000000244711416753127400212740ustar00rootroot00000000000000package 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) } } }) }