pax_global_header 0000666 0000000 0000000 00000000064 15015766371 0014525 g ustar 00root root 0000000 0000000 52 comment=52dacb84f31d55f1d4f0916d48707456a7c90ecb
goccy-go-yaml-52dacb8/ 0000775 0000000 0000000 00000000000 15015766371 0014666 5 ustar 00root root 0000000 0000000 goccy-go-yaml-52dacb8/.codecov.yml 0000664 0000000 0000000 00000000634 15015766371 0017114 0 ustar 00root root 0000000 0000000 codecov:
require_ci_to_pass: yes
coverage:
precision: 2
round: down
range: "70...100"
status:
project:
default:
target: 75%
threshold: 2%
patch: off
changes: no
parsers:
gcov:
branch_detection:
conditional: yes
loop: yes
method: no
macro: no
comment:
layout: "header,diff"
behavior: default
require_changes: no
ignore:
- ast
goccy-go-yaml-52dacb8/.github/ 0000775 0000000 0000000 00000000000 15015766371 0016226 5 ustar 00root root 0000000 0000000 goccy-go-yaml-52dacb8/.github/FUNDING.yml 0000664 0000000 0000000 00000000020 15015766371 0020033 0 ustar 00root root 0000000 0000000 github: [goccy]
goccy-go-yaml-52dacb8/.github/ISSUE_TEMPLATE/ 0000775 0000000 0000000 00000000000 15015766371 0020411 5 ustar 00root root 0000000 0000000 goccy-go-yaml-52dacb8/.github/ISSUE_TEMPLATE/bug_report.md 0000664 0000000 0000000 00000001212 15015766371 0023077 0 ustar 00root root 0000000 0000000 ---
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Please provide a minimum yaml content that can be reproduced.
We are more than happy to use [Go Playground](https://go.dev/play)
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Version Variables**
- Go version: [e.g. 1.21 ]
- go-yaml's Version: [e.g. v1.11.1 ]
**Additional context**
Add any other context about the problem here.
goccy-go-yaml-52dacb8/.github/ISSUE_TEMPLATE/feature_request.md 0000664 0000000 0000000 00000001140 15015766371 0024132 0 ustar 00root root 0000000 0000000 ---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: feature request
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.
goccy-go-yaml-52dacb8/.github/pull_request_template.md 0000664 0000000 0000000 00000000261 15015766371 0023166 0 ustar 00root root 0000000 0000000 Before submitting your PR, please confirm the following.
- [ ] Describe the purpose for which you created this PR.
- [ ] Create test code that corresponds to the modification goccy-go-yaml-52dacb8/.github/workflows/ 0000775 0000000 0000000 00000000000 15015766371 0020263 5 ustar 00root root 0000000 0000000 goccy-go-yaml-52dacb8/.github/workflows/go.yml 0000664 0000000 0000000 00000005435 15015766371 0021422 0 ustar 00root root 0000000 0000000 name: Go
on:
push:
branches:
- master
pull_request:
jobs:
lint:
name: lint
runs-on: ubuntu-latest
steps:
- name: checkout
uses: actions/checkout@v4
- name: setup Go
uses: actions/setup-go@v4
with:
go-version: "1.24"
- name: run linters
run: |
make lint
race-test:
name: Test with -race
strategy:
matrix:
os: [ "ubuntu-latest", "macos-latest", "windows-latest" ]
go-version: [ "1.21", "1.22", "1.23", "1.24" ]
runs-on: ${{ matrix.os }}
steps:
- name: checkout
uses: actions/checkout@v4
- name: setup Go ${{ matrix.go-version }}
uses: actions/setup-go@v4
with:
go-version: ${{ matrix.go-version }}
- name: test
run: |
make test
i386-test:
name: Test in i386
strategy:
matrix:
os: [ "ubuntu-latest", "windows-latest" ]
go-version: [ "1.21", "1.22", "1.23", "1.24" ]
runs-on: ${{ matrix.os }}
env:
GOARCH: "386"
steps:
- name: checkout
uses: actions/checkout@v4
- name: setup Go ${{ matrix.go-version }}
uses: actions/setup-go@v4
with:
go-version: ${{ matrix.go-version }}
- name: test
run: |
make simple-test
fuzz:
name: Fuzzing Test
runs-on: ubuntu-latest
steps:
- name: checkout
uses: actions/checkout@v4
- name: setup Go
uses: actions/setup-go@v4
with:
go-version: "1.24"
- name: run
run: |
make fuzz
ycat:
name: ycat
runs-on: ubuntu-latest
steps:
- name: checkout
uses: actions/checkout@v4
- name: setup Go
uses: actions/setup-go@v4
with:
go-version: "1.24"
- name: build
run: |
make ycat/build
- name: run
run: |
./bin/ycat .github/workflows/go.yml
page:
name: page
runs-on: ubuntu-latest
steps:
- name: checkout
uses: actions/checkout@v4
- name: setup Node
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
cache-dependency-path: docs/playground/package-lock.json
- name: install dependencies
run: make -C ./docs/playground deps
- name: build
run: make -C ./docs/playground build
coverage:
name: Coverage
runs-on: ubuntu-latest
steps:
- name: checkout
uses: actions/checkout@v4
- name: setup Go
uses: actions/setup-go@v4
with:
go-version: "1.24"
- name: measure coverage
run: |
make cover
- uses: codecov/codecov-action@v4
with:
fail_ci_if_error: true
verbose: true
token: ${{ secrets.CODECOV_TOKEN }}
goccy-go-yaml-52dacb8/.github/workflows/pages.yaml 0000664 0000000 0000000 00000001677 15015766371 0022261 0 ustar 00root root 0000000 0000000 name: Deploy static content to Pages
on:
push:
tags:
- v*
workflow_dispatch:
permissions:
contents: read
pages: write
id-token: write
concurrency:
group: 'pages'
cancel-in-progress: true
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: checkout
uses: actions/checkout@v4
- name: setup Node
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
cache-dependency-path: docs/playground/package-lock.json
- name: install dependencies
run: make -C ./docs/playground deps
- name: build
run: make -C ./docs/playground build
- name: setup Pages
uses: actions/configure-pages@v4
- name: upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: './docs/playground/dist'
- name: deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
goccy-go-yaml-52dacb8/.gitignore 0000664 0000000 0000000 00000000026 15015766371 0016654 0 ustar 00root root 0000000 0000000 bin/
.idea/
cover.out
goccy-go-yaml-52dacb8/.golangci.yml 0000664 0000000 0000000 00000002107 15015766371 0017252 0 ustar 00root root 0000000 0000000 version: "2"
linters:
default: none
enable:
- errcheck
- govet
- ineffassign
- misspell
- perfsprint
- staticcheck
- unused
settings:
errcheck:
without_tests: true
govet:
disable:
- tests
misspell:
locale: US
perfsprint:
int-conversion: false
err-error: false
errorf: true
sprintf1: false
strconcat: false
staticcheck:
checks:
- -ST1000
- -ST1005
- all
exclusions:
generated: lax
presets:
- comments
- common-false-positives
- legacy
- std-error-handling
rules:
- linters:
- staticcheck
path: _test\.go
paths:
- third_party$
- builtin$
- examples$
formatters:
enable:
- gci
- gofmt
settings:
gci:
sections:
- standard
- default
- prefix(github.com/goccy/go-yaml)
- blank
- dot
gofmt:
simplify: true
exclusions:
generated: lax
paths:
- third_party$
- builtin$
- examples$
goccy-go-yaml-52dacb8/CHANGELOG.md 0000664 0000000 0000000 00000012221 15015766371 0016475 0 ustar 00root root 0000000 0000000 # 1.11.2 - 2023-09-15
### Fix bugs
- Fix quoted comments ( #370 )
- Fix handle of space at start or last ( #376 )
- Fix sequence with comment ( #390 )
# 1.11.1 - 2023-09-14
### Fix bugs
- Handle `\r` in a double-quoted string the same as `\n` ( #372 )
- Replace loop with n.Values = append(n.Values, target.Values...) ( #380 )
- Skip encoding an inline field if it is null ( #386 )
- Fix comment parsing with null value ( #388 )
# 1.11.0 - 2023-04-03
### Features
- Supports dynamically switch encode and decode processing for a given type
# 1.10.1 - 2023-03-28
### Features
- Quote YAML 1.1 bools at encoding time for compatibility with other legacy parsers
- Add support of 32-bit architecture
### Fix bugs
- Don't trim all space characters in block style sequence
- Support strings starting with `@`
# 1.10.0 - 2023-03-01
### Fix bugs
Reversible conversion of comments was not working in various cases, which has been corrected.
**Breaking Change** exists in the comment map interface. However, if you are dealing with CommentMap directly, there is no problem.
# 1.9.8 - 2022-12-19
### Fix feature
- Append new line at the end of file ( #329 )
### Fix bugs
- Fix custom marshaler ( #333, #334 )
- Fix behavior when struct fields conflicted( #335 )
- Fix position calculation for literal, folded and raw folded strings ( #330 )
# 1.9.7 - 2022-12-03
### Fix bugs
- Fix handling of quoted map key ( #328 )
- Fix resusing process of scanning context ( #322 )
## v1.9.6 - 2022-10-26
### New Features
- Introduce MapKeyNode interface to limit node types for map key ( #312 )
### Fix bugs
- Quote strings with special characters in flow mode ( #270 )
- typeError implements PrettyPrinter interface ( #280 )
- Fix incorrect const type ( #284 )
- Fix large literals type inference on 32 bits ( #293 )
- Fix UTF-8 characters ( #294 )
- Fix decoding of unknown aliases ( #317 )
- Fix stream encoder for insert a separator between each encoded document ( #318 )
### Update
- Update golang.org/x/sys ( #289 )
- Update Go version in CI ( #295 )
- Add test cases for missing keys to struct literals ( #300 )
## 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
goccy-go-yaml-52dacb8/LICENSE 0000664 0000000 0000000 00000002060 15015766371 0015671 0 ustar 00root root 0000000 0000000 MIT License
Copyright (c) 2019 Masaaki Goshima
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
goccy-go-yaml-52dacb8/Makefile 0000664 0000000 0000000 00000002600 15015766371 0016324 0 ustar 00root root 0000000 0000000 ## Location to install dependencies to
LOCALBIN ?= $(shell pwd)/bin
TESTMOD := testdata/go_test.mod
$(LOCALBIN):
mkdir -p $(LOCALBIN)
.PHONY: test
test:
go test -v -race ./...
go test -v -race ./testdata -modfile=$(TESTMOD)
.PHONY: simple-test
simple-test:
go test -v ./...
go test -v ./testdata -modfile=$(TESTMOD)
.PHONY: fuzz
fuzz:
go test -fuzz=Fuzz -fuzztime 60s
.PHONY: cover
cover:
go test -coverpkg=.,./ast,./lexer,./parser,./printer,./scanner,./token -coverprofile=cover.out -modfile=$(TESTMOD) ./... ./testdata
.PHONY: cover-html
cover-html: cover
go tool cover -html=cover.out
.PHONY: ycat/build
ycat/build: $(LOCALBIN)
cd ./cmd/ycat && go build -o $(LOCALBIN)/ycat .
.PHONY: lint
lint: golangci-lint ## Run golangci-lint
@$(GOLANGCI_LINT) run
.PHONY: fmt
fmt: golangci-lint ## Ensure consistent code style
@go mod tidy
@go fmt ./...
@$(GOLANGCI_LINT) run --fix
## Tool Binaries
GOLANGCI_LINT ?= $(LOCALBIN)/golangci-lint
## Tool Versions
GOLANGCI_VERSION := 2.1.2
.PHONY: golangci-lint
.PHONY: $(GOLANGCI_LINT)
golangci-lint: $(GOLANGCI_LINT) ## Download golangci-lint locally if necessary.
$(GOLANGCI_LINT): $(LOCALBIN)
@test -s $(LOCALBIN)/golangci-lint && $(LOCALBIN)/golangci-lint version --short | grep -q $(GOLANGCI_VERSION) || \
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(LOCALBIN) v$(GOLANGCI_VERSION)
goccy-go-yaml-52dacb8/README.md 0000664 0000000 0000000 00000032334 15015766371 0016152 0 ustar 00root root 0000000 0000000 # YAML support for the Go language
[](https://pkg.go.dev/github.com/goccy/go-yaml)

[](https://codecov.io/gh/goccy/go-yaml)
[](https://goreportcard.com/report/github.com/goccy/go-yaml)
## This library has **NO** relation to the go-yaml/yaml library
> [!IMPORTANT]
> This library is developed from scratch to replace [`go-yaml/yaml`](https://github.com/go-yaml/yaml).
> If you're looking for a better YAML library, this one should be helpful.
# 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 believe that a new YAML library is necessary for the following reasons:
- Not actively maintained
- `go-yaml/yaml` has ported the libyaml written in C to Go, so the source code is not written in Go style
- There is a lot of content that cannot be parsed
- YAML is often used for configuration, and it is common to include validation along with it. However, the errors in `go-yaml/yaml` are not intuitive, and it is difficult to provide meaningful validation errors
- When creating tools that use YAML, there are cases where reversible transformation of YAML is required. However, to perform reversible transformations of content that includes Comments or Anchors/Aliases, manipulating the AST is the only option
- Non-intuitive [Marshaler](https://pkg.go.dev/gopkg.in/yaml.v3#Marshaler) / [Unmarshaler](https://pkg.go.dev/gopkg.in/yaml.v3#Unmarshaler)
By the way, libraries such as [ghodss/yaml](https://github.com/ghodss/yaml) and [sigs.k8s.io/yaml](https://github.com/kubernetes-sigs/yaml) also depend on go-yaml/yaml, so if you are using these libraries, the same issues apply: they cannot parse things that go-yaml/yaml cannot parse, and they inherit many of the problems that go-yaml/yaml has.
# Features
- No dependencies
- A better parser than `go-yaml/yaml`.
- [Support recursive processing](https://github.com/apple/device-management/blob/release/docs/schema.yaml)
- Higher coverage in the [YAML Test Suite](https://github.com/yaml/yaml-test-suite?tab=readme-ov-file)
- YAML Test Suite consists of 402 cases in total, of which `gopkg.in/yaml.v3` passes `295`. In addition to passing all those test cases, `goccy/go-yaml` successfully passes nearly 60 additional test cases ( 2024/12/15 )
- The test code is [here](https://github.com/goccy/go-yaml/blob/master/yaml_test_suite_test.go#L77)
- Ease and sustainability of maintenance
- The main maintainer is [@goccy](https://github.com/goccy), but we are also building a system to develop as a team with trusted developers
- Since it is written from scratch, the code is easy to read for Gophers
- An API structure that allows the use of not only `Encoder`/`Decoder` but also `Tokenizer` and `Parser` functionalities.
- [lexer.Tokenize](https://pkg.go.dev/github.com/goccy/go-yaml@v1.15.4/lexer#Tokenize)
- [parser.Parse](https://pkg.go.dev/github.com/goccy/go-yaml@v1.15.4/parser#Parse)
- Filtering, replacing, and merging YAML content using YAML Path
- Reversible transformation without using the AST for YAML that includes Anchors, Aliases, and Comments
- Customize the Marshal/Unmarshal behavior for primitive types and third-party library types ([RegisterCustomMarshaler](https://pkg.go.dev/github.com/goccy/go-yaml#RegisterCustomMarshaler), [RegisterCustomUnmarshaler](https://pkg.go.dev/github.com/goccy/go-yaml#RegisterCustomUnmarshaler))
- Respects `encoding/json` behavior
- 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.
- [json.Marshaler](https://pkg.go.dev/encoding/json#Marshaler) style [marshaler](https://pkg.go.dev/github.com/goccy/go-yaml#BytesMarshaler)
- [json.Unmarshaler](https://pkg.go.dev/encoding/json#Unmarshaler) style [unmarshaler](https://pkg.go.dev/github.com/goccy/go-yaml#BytesUnmarshaler)
- Options for using `MarshalJSON` and `UnmarshalJSON` ([UseJSONMarshaler](https://pkg.go.dev/github.com/goccy/go-yaml#UseJSONMarshaler), [UseJSONUnmarshaler](https://pkg.go.dev/github.com/goccy/go-yaml#UseJSONUnmarshaler))
- Pretty format for error notifications
- Smart validation processing combined with [go-playground/validator](https://github.com/go-playground/validator)
- [example test code is here](https://github.com/goccy/go-yaml/blob/45889c98b0a0967240eb595a1bd6896e2f575106/testdata/validate_test.go#L12)
- Allow referencing elements declared in another file via anchors
# Users
The repositories that use goccy/go-yaml are listed here.
- https://github.com/goccy/go-yaml/wiki/Users
The source data is [here](https://github.com/goccy/go-yaml/network/dependents).
It is already being used in many repositories. Now it's your turn 😄
# Playground
The Playground visualizes how go-yaml processes YAML text. Use it to assist with your debugging or issue reporting.
https://goccy.github.io/go-yaml
# Installation
```sh
go get 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`, you can define it as a struct tag.
If the value specified for an anchor is a pointer type and the same address as the pointer is found, the value is automatically set to alias.
If an explicit alias name is specified, an error is raised if its value is different from the value specified in the anchor.
```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 the value specified for an anchor is a pointer type and the same address as the pointer is found, the value is automatically set to alias.
```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"`
D *T `yaml:"d"`
}
v.A = &T{I: 1, S: "hello"}
v.B = &T{I: 2, S: "world"}
v.C = v.A // C has same pointer address to A
v.D = v.B // D has same pointer address to B
bytes, err := yaml.Marshal(v)
if err != nil {
//...
}
fmt.Println(string(bytes))
/*
a: &a
i: 1
s: hello
b: &b
i: 2
s: world
c: *a
d: *b
*/
```
### 3.3 MergeKey and Alias
Merge key and alias ( `<<: *alias` ) can be used by embedding a structure with the `inline,alias` tag.
```go
type Person struct {
*Person `yaml:",omitempty,inline,alias"` // embed Person type for default value
Name string `yaml:",omitempty"`
Age int `yaml:",omitempty"`
}
defaultPerson := &Person{
Name: "John Smith",
Age: 20,
}
people := []*Person{
{
Person: defaultPerson, // assign default value
Name: "Ken", // override Name property
Age: 10, // override Age property
},
{
Person: defaultPerson, // assign default value only
},
}
var doc struct {
Default *Person `yaml:"default,anchor"`
People []*Person `yaml:"people"`
}
doc.Default = defaultPerson
doc.People = people
bytes, err := yaml.Marshal(doc)
if err != nil {
//...
}
fmt.Println(string(bytes))
/*
default: &default
name: John Smith
age: 20
people:
- <<: *default
name: Ken
age: 10
- <<: *default
*/
```
## 4. Pretty Formatted Errors
Error values produced during parsing have two extra features over regular
error values.
First, by default, they contain extra information on the location of the error
from the source YAML document, to make it easier to find the error location.
Second, the error messages can optionally be colorized.
If you would like to control exactly how the output looks like, consider
using `yaml.FormatError`, which accepts two boolean values to
control turning these features on or off.
## 5. Use YAMLPath
```go
yml := `
store:
book:
- author: john
price: 10
- author: ken
price: 12
bicycle:
color: red
price: 19.95
`
path, err := yaml.PathString("$.store.book[*].author")
if err != nil {
//...
}
var authors []string
if err := path.Read(strings.NewReader(yml), &authors); err != nil {
//...
}
fmt.Println(authors)
// [john ken]
```
### 5.1 Print customized error with YAML source code
```go
package main
import (
"fmt"
"github.com/goccy/go-yaml"
)
func main() {
yml := `
a: 1
b: "hello"
`
var v struct {
A int
B string
}
if err := yaml.Unmarshal([]byte(yml), &v); err != nil {
panic(err)
}
if v.A != 2 {
// output error with YAML source
path, err := yaml.PathString("$.a")
if err != nil {
panic(err)
}
source, err := path.AnnotateSource([]byte(yml), true)
if err != nil {
panic(err)
}
fmt.Printf("a value expected 2 but actual %d:\n%s\n", v.A, string(source))
}
}
```
output result is the following:
# Tools
## ycat
print yaml file with color
### Installation
```sh
git clone https://github.com/goccy/go-yaml.git
cd go-yaml/cmd/ycat && go install .
```
# For Developers
> [!NOTE]
> In this project, we manage such test code under the `testdata` directory to avoid adding dependencies on libraries that are only needed for testing to the top `go.mod` file. Therefore, if you want to add test cases that use 3rd party libraries, please add the test code to the `testdata` directory.
# 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
goccy-go-yaml-52dacb8/ast/ 0000775 0000000 0000000 00000000000 15015766371 0015455 5 ustar 00root root 0000000 0000000 goccy-go-yaml-52dacb8/ast/ast.go 0000664 0000000 0000000 00000155013 15015766371 0016600 0 ustar 00root root 0000000 0000000 package ast
import (
"errors"
"fmt"
"io"
"math"
"strconv"
"strings"
"github.com/goccy/go-yaml/token"
)
var (
ErrInvalidTokenType = errors.New("invalid token type")
ErrInvalidAnchorName = errors.New("invalid anchor name")
ErrInvalidAliasName = errors.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
// SequenceEntryType type identifier for sequence entry node
SequenceEntryType
// 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 SequenceEntryType:
return "SequenceEntry"
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 SequenceEntryType:
return "value"
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()
}
// MapKeyNode type for map key node
type MapKeyNode interface {
Node
IsMergeKey() bool
// String node to text without comment
stringWithoutComment() string
}
// ScalarNode type for scalar node
type ScalarNode interface {
MapKeyNode
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 []byte(s[readLen : readLen+size]) {
p[idx] = byte(b)
}
node.addReadLen(size)
return size, nil
}
func checkLineBreak(t *token.Token) bool {
if t.Prev != nil {
lbc := "\n"
prev := t.Prev
var adjustment int
// if the previous type is sequence entry use the previous type for that
if prev.Type == token.SequenceEntryType {
// as well as switching to previous type count any new lines in origin to account for:
// -
// b: c
adjustment = strings.Count(strings.TrimRight(t.Origin, lbc), lbc)
if prev.Prev != nil {
prev = prev.Prev
}
}
lineDiff := t.Position.Line - prev.Position.Line - 1
if lineDiff > 0 {
if prev.Type == token.StringType {
// Remove any line breaks included in multiline string
adjustment += strings.Count(strings.TrimRight(strings.TrimSpace(prev.Origin), lbc), lbc)
}
// Due to the way that comment parsing works its assumed that when a null value does not have new line in origin
// it was squashed therefore difference is ignored.
// foo:
// bar:
// # comment
// baz: 1
// becomes
// foo:
// bar: null # comment
//
// baz: 1
if prev.Type == token.NullType || prev.Type == token.ImplicitNullType {
return strings.Count(prev.Origin, lbc) > 0
}
if lineDiff-adjustment > 0 {
return true
}
}
}
return false
}
// Null create node for null value
func Null(tk *token.Token) *NullNode {
return &NullNode{
BaseNode: &BaseNode{},
Token: tk,
}
}
// Bool create node for boolean value
func Bool(tk *token.Token) *BoolNode {
b, _ := strconv.ParseBool(tk.Value)
return &BoolNode{
BaseNode: &BaseNode{},
Token: tk,
Value: b,
}
}
// Integer create node for integer value
func Integer(tk *token.Token) *IntegerNode {
var v any
if num := token.ToNumber(tk.Value); num != nil {
v = num.Value
}
return &IntegerNode{
BaseNode: &BaseNode{},
Token: tk,
Value: v,
}
}
// Float create node for float value
func Float(tk *token.Token) *FloatNode {
var v float64
if num := token.ToNumber(tk.Value); num != nil && num.Type == token.NumberTypeFloat {
value, ok := num.Value.(float64)
if ok {
v = value
}
}
return &FloatNode{
BaseNode: &BaseNode{},
Token: tk,
Value: v,
}
}
// 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 MapKeyNode, 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())
}
if len(docs) > 0 {
return strings.Join(docs, "\n") + "\n"
} else {
return ""
}
}
// 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)
}
if d.Body != nil {
doc = append(doc, d.Body.String())
}
if d.End != nil {
doc = append(doc, d.End.Value)
}
return strings.Join(doc, "\n")
}
// MarshalYAML encodes to a YAML text
func (d *DocumentNode) MarshalYAML() ([]byte, error) {
return []byte(d.String()), nil
}
// 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.Token.Type == token.ImplicitNullType {
if n.Comment != nil {
return n.Comment.String()
}
return ""
}
if n.Comment != nil {
return addCommentString("null", n.Comment)
}
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
}
// IsMergeKey returns whether it is a MergeKey node.
func (n *NullNode) IsMergeKey() bool {
return false
}
// 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
}
// IsMergeKey returns whether it is a MergeKey node.
func (n *IntegerNode) IsMergeKey() bool {
return false
}
// 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
}
// IsMergeKey returns whether it is a MergeKey node.
func (n *FloatNode) IsMergeKey() bool {
return false
}
// 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
}
// IsMergeKey returns whether it is a MergeKey node.
func (n *StringNode) IsMergeKey() bool {
return false
}
// 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)
indent := strings.Repeat(" ", n.Token.Position.IndentNum)
values := []string{}
for _, v := range strings.Split(n.Value, lbc) {
values = append(values, fmt.Sprintf("%s%s%s", space, indent, v))
}
block := strings.TrimSuffix(strings.TrimSuffix(strings.Join(values, lbc), fmt.Sprintf("%s%s%s", lbc, indent, space)), fmt.Sprintf("%s%s", indent, 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)
indent := strings.Repeat(" ", n.Token.Position.IndentNum)
values := []string{}
for _, v := range strings.Split(n.Value, lbc) {
values = append(values, fmt.Sprintf("%s%s%s", space, indent, v))
}
block := strings.TrimSuffix(strings.TrimSuffix(strings.Join(values, lbc), fmt.Sprintf("%s%s%s", lbc, indent, 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
}
// IsMergeKey returns whether it is a MergeKey node.
func (n *LiteralNode) IsMergeKey() bool {
return false
}
// 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.stringWithoutComment()
}
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
}
// IsMergeKey returns whether it is a MergeKey node.
func (n *MergeKeyNode) IsMergeKey() bool {
return true
}
// 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
}
// IsMergeKey returns whether it is a MergeKey node.
func (n *BoolNode) IsMergeKey() bool {
return false
}
// 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
}
// IsMergeKey returns whether it is a MergeKey node.
func (n *InfinityNode) IsMergeKey() bool {
return false
}
// 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
}
// IsMergeKey returns whether it is a MergeKey node.
func (n *NanNode) IsMergeKey() bool {
return false
}
// 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() MapKeyNode {
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
}
// KeyValue returns the MappingValueNode of the iterator's current map node entry.
func (m *MapNodeIter) KeyValue() *MappingValueNode {
return m.values[m.idx]
}
// MappingNode type of mapping node
type MappingNode struct {
*BaseNode
Start *token.Token
End *token.Token
IsFlowStyle bool
Values []*MappingValueNode
FootComment *CommentGroupNode
}
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)
}
// 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 n.stringWithoutComment()
}
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
}
// IsMergeKey returns whether it is a MergeKey node.
func (n *MappingKeyNode) IsMergeKey() bool {
if n.Value == nil {
return false
}
key, ok := n.Value.(MapKeyNode)
if !ok {
return false
}
return key.IsMergeKey()
}
// MappingValueNode type of mapping value
type MappingValueNode struct {
*BaseNode
Start *token.Token // delimiter token ':'.
CollectEntry *token.Token // collect entry token ','.
Key MapKeyNode
Value Node
FootComment *CommentGroupNode
IsFlowStyle bool
}
// 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) {
n.IsFlowStyle = isFlow
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 {
var text string
if n.Comment != nil {
text = fmt.Sprintf(
"%s\n%s",
n.Comment.StringWithSpace(n.Key.GetToken().Position.Column-1),
n.toString(),
)
} else {
text = n.toString()
}
if n.FootComment != nil {
text += fmt.Sprintf("\n%s", n.FootComment.StringWithSpace(n.Key.GetToken().Position.Column-1))
}
return text
}
func (n *MappingValueNode) toString() string {
space := strings.Repeat(" ", n.Key.GetToken().Position.Column-1)
if checkLineBreak(n.Key.GetToken()) {
space = fmt.Sprintf("%s%s", "\n", space)
}
keyIndentLevel := n.Key.GetToken().Position.IndentLevel
valueIndentLevel := n.Value.GetToken().Position.IndentLevel
keyComment := n.Key.GetComment()
if _, ok := n.Value.(ScalarNode); ok {
value := n.Value.String()
if value == "" {
// implicit null value.
return fmt.Sprintf("%s%s:", space, n.Key.String())
}
return fmt.Sprintf("%s%s: %s", space, n.Key.String(), value)
} else if keyIndentLevel < valueIndentLevel && !n.IsFlowStyle {
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())
} else if _, ok := n.Value.(*TagNode); 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())
}
// 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
ValueHeadComments []*CommentGroupNode
Entries []*SequenceEntryNode
FootComment *CommentGroupNode
}
// Replace replace value node.
func (n *SequenceNode) Replace(idx int, value Node) error {
if len(n.Values) <= idx {
return fmt.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)
n.Values = append(n.Values, target.Values...)
if len(target.ValueHeadComments) == 0 {
n.ValueHeadComments = append(n.ValueHeadComments, make([]*CommentGroupNode, len(target.Values))...)
return
}
n.ValueHeadComments = append(n.ValueHeadComments, target.ValueHeadComments...)
}
// 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 {
if value == nil {
continue
}
valueStr := value.String()
newLinePrefix := ""
if strings.HasPrefix(valueStr, "\n") {
valueStr = valueStr[1:]
newLinePrefix = "\n"
}
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.
prefix := space + " "
for i := 1; i < len(splittedValues); i++ {
splittedValues[i] = strings.TrimPrefix(splittedValues[i], prefix)
}
}
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.ValueHeadComments) == len(n.Values) && n.ValueHeadComments[idx] != nil {
values = append(values, fmt.Sprintf("%s%s", newLinePrefix, n.ValueHeadComments[idx].StringWithSpace(n.Start.Position.Column-1)))
newLinePrefix = ""
}
values = append(values, fmt.Sprintf("%s%s- %s", newLinePrefix, space, newValue))
}
if n.FootComment != nil {
values = append(values, n.FootComment.StringWithSpace(n.Start.Position.Column-1))
}
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()
}
// 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
}
// SequenceEntryNode is the sequence entry.
type SequenceEntryNode struct {
*BaseNode
HeadComment *CommentGroupNode // head comment.
LineComment *CommentGroupNode // line comment e.g.) - # comment.
Start *token.Token // entry token.
Value Node // value node.
}
// String node to text
func (n *SequenceEntryNode) String() string {
return "" // TODO
}
// GetToken returns token instance
func (n *SequenceEntryNode) GetToken() *token.Token {
return n.Start
}
// Type returns type of node
func (n *SequenceEntryNode) Type() NodeType {
return SequenceEntryType
}
// AddColumn add column number to child nodes recursively
func (n *SequenceEntryNode) AddColumn(col int) {
n.Start.AddColumn(col)
}
// SetComment set line comment.
func (n *SequenceEntryNode) SetComment(cm *CommentGroupNode) error {
n.LineComment = cm
return nil
}
// Comment returns comment token instance
func (n *SequenceEntryNode) GetComment() *CommentGroupNode {
return n.LineComment
}
// MarshalYAML
func (n *SequenceEntryNode) MarshalYAML() ([]byte, error) {
return []byte(n.String()), nil
}
func (n *SequenceEntryNode) Read(p []byte) (int, error) {
return readNode(p, n)
}
// SequenceEntry creates SequenceEntryNode instance.
func SequenceEntry(start *token.Token, value Node, headComment *CommentGroupNode) *SequenceEntryNode {
return &SequenceEntryNode{
BaseNode: &BaseNode{},
HeadComment: headComment,
Start: start,
Value: value,
}
}
// SequenceMergeValue creates SequenceMergeValueNode instance.
func SequenceMergeValue(values ...MapNode) *SequenceMergeValueNode {
return &SequenceMergeValueNode{
values: values,
}
}
// SequenceMergeValueNode is used to convert the Sequence node specified for the merge key into a MapNode format.
type SequenceMergeValueNode struct {
values []MapNode
}
// MapRange returns MapNodeIter instance.
func (n *SequenceMergeValueNode) MapRange() *MapNodeIter {
ret := &MapNodeIter{idx: startRangeIndex}
for _, value := range n.values {
iter := value.MapRange()
ret.values = append(ret.values, iter.values...)
}
return ret
}
// AnchorNode type of anchor node
type AnchorNode struct {
*BaseNode
Start *token.Token
Name Node
Value Node
}
func (n *AnchorNode) stringWithoutComment() string {
return n.Value.String()
}
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
}
func (n *AnchorNode) GetValue() any {
return n.Value.GetToken().Value
}
// 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 {
anchor := "&" + n.Name.String()
value := n.Value.String()
if s, ok := n.Value.(*SequenceNode); ok && !s.IsFlowStyle {
return fmt.Sprintf("%s\n%s", anchor, value)
} else if m, ok := n.Value.(*MappingNode); ok && !m.IsFlowStyle {
return fmt.Sprintf("%s\n%s", anchor, value)
}
if value == "" {
// implicit null value.
return anchor
}
return fmt.Sprintf("%s %s", anchor, value)
}
// MarshalYAML encodes to a YAML text
func (n *AnchorNode) MarshalYAML() ([]byte, error) {
return []byte(n.String()), nil
}
// IsMergeKey returns whether it is a MergeKey node.
func (n *AnchorNode) IsMergeKey() bool {
if n.Value == nil {
return false
}
key, ok := n.Value.(MapKeyNode)
if !ok {
return false
}
return key.IsMergeKey()
}
// AliasNode type of alias node
type AliasNode struct {
*BaseNode
Start *token.Token
Value Node
}
func (n *AliasNode) stringWithoutComment() string {
return n.Value.String()
}
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
}
func (n *AliasNode) GetValue() any {
return n.Value.GetToken().Value
}
// 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())
}
// MarshalYAML encodes to a YAML text
func (n *AliasNode) MarshalYAML() ([]byte, error) {
return []byte(n.String()), nil
}
// IsMergeKey returns whether it is a MergeKey node.
func (n *AliasNode) IsMergeKey() bool {
return false
}
// DirectiveNode type of directive node
type DirectiveNode struct {
*BaseNode
// Start is '%' token.
Start *token.Token
// Name is directive name e.g.) "YAML" or "TAG".
Name Node
// Values is directive values e.g.) "1.2" or "!!" and "tag:clarkevans.com,2002:app/".
Values []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.Name != nil {
n.Name.AddColumn(col)
}
for _, value := range n.Values {
value.AddColumn(col)
}
}
// String directive to text
func (n *DirectiveNode) String() string {
values := make([]string, 0, len(n.Values))
for _, val := range n.Values {
values = append(values, val.String())
}
return strings.Join(append([]string{"%" + n.Name.String()}, values...), " ")
}
// 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
Directive *DirectiveNode
Start *token.Token
Value Node
}
func (n *TagNode) GetValue() any {
scalar, ok := n.Value.(ScalarNode)
if !ok {
return nil
}
return scalar.GetValue()
}
func (n *TagNode) stringWithoutComment() string {
return n.Value.String()
}
// 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 {
value := n.Value.String()
if s, ok := n.Value.(*SequenceNode); ok && !s.IsFlowStyle {
return fmt.Sprintf("%s\n%s", n.Start.Value, value)
} else if m, ok := n.Value.(*MappingNode); ok && !m.IsFlowStyle {
return fmt.Sprintf("%s\n%s", n.Start.Value, value)
}
return fmt.Sprintf("%s %s", n.Start.Value, value)
}
// MarshalYAML encodes to a YAML text
func (n *TagNode) MarshalYAML() ([]byte, error) {
return []byte(n.String()), nil
}
// IsMergeKey returns whether it is a MergeKey node.
func (n *TagNode) IsMergeKey() bool {
if n.Value == nil {
return false
}
key, ok := n.Value.(MapKeyNode)
if !ok {
return false
}
return key.IsMergeKey()
}
func (n *TagNode) ArrayRange() *ArrayNodeIter {
arr, ok := n.Value.(ArrayNode)
if !ok {
return nil
}
return arr.ArrayRange()
}
// 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)
}
// 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 {
values := []string{}
space := strings.Repeat(" ", col)
for _, comment := range n.Comments {
space := space
if checkLineBreak(comment.Token) {
space = fmt.Sprintf("%s%s", "\n", space)
}
values = append(values, space+comment.String())
}
return strings.Join(values, "\n")
}
// 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.Name)
for _, value := range n.Values {
Walk(v, 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:
if found := f.walk(node, n.Name); found != nil {
return found
}
for _, value := range n.Values {
if found := f.walk(node, value); found != nil {
return found
}
}
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
}
goccy-go-yaml-52dacb8/ast/ast_test.go 0000664 0000000 0000000 00000001531 15015766371 0017632 0 ustar 00root root 0000000 0000000 package ast
import (
"testing"
"github.com/goccy/go-yaml/token"
)
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)
}
}
func TestReadNode(t *testing.T) {
t.Run("utf-8", func(t *testing.T) {
value := "éɛทᛞ⠻チ▓🦄"
node := &StringNode{
BaseNode: &BaseNode{},
Token: &token.Token{},
Value: value,
}
expectedSize := len(value)
gotBuffer := make([]byte, expectedSize)
expectedBuffer := []byte(value)
gotSize, _ := readNode(gotBuffer, node)
if gotSize != expectedSize {
t.Fatalf("expected size:%d\ngot:%d", expectedSize, gotSize)
}
if string(gotBuffer) != string(expectedBuffer) {
t.Fatalf("expected buffer:%s\ngot:%s", expectedBuffer, gotBuffer)
}
})
}
goccy-go-yaml-52dacb8/benchmarks/ 0000775 0000000 0000000 00000000000 15015766371 0017003 5 ustar 00root root 0000000 0000000 goccy-go-yaml-52dacb8/benchmarks/benchmark_test.go 0000664 0000000 0000000 00000001657 15015766371 0022334 0 ustar 00root root 0000000 0000000 package benchmarks
import (
"testing"
"github.com/goccy/go-yaml"
goyaml2 "gopkg.in/yaml.v2"
goyaml3 "gopkg.in/yaml.v3"
)
func Benchmark(b *testing.B) {
const src = `---
id: 1
message: Hello, World
verified: true
elements:
- one
- 0.02
- null
- -inf
`
type T struct {
ID int `yaml:"id"`
Message string `yaml:"message"`
Verified bool `yaml:"verified,omitempty"`
}
b.Run("gopkg.in/yaml.v2", func(b *testing.B) {
var t T
for i := 0; i < b.N; i++ {
if err := goyaml2.Unmarshal([]byte(src), &t); err != nil {
b.Fatal(err)
}
}
})
b.Run("gopkg.in/yaml.v3", func(b *testing.B) {
var t T
for i := 0; i < b.N; i++ {
if err := goyaml3.Unmarshal([]byte(src), &t); err != nil {
b.Fatal(err)
}
}
})
b.Run("github.com/goccy/go-yaml", func(b *testing.B) {
var t T
for i := 0; i < b.N; i++ {
if err := yaml.Unmarshal([]byte(src), &t); err != nil {
b.Fatal(err)
}
}
})
}
goccy-go-yaml-52dacb8/benchmarks/go.mod 0000664 0000000 0000000 00000000332 15015766371 0020107 0 ustar 00root root 0000000 0000000 module github.com/goccy/go-yaml/benchmarks
go 1.21.0
replace github.com/goccy/go-yaml => ..
require (
github.com/goccy/go-yaml v0.0.0-00010101000000-000000000000
gopkg.in/yaml.v2 v2.4.0
gopkg.in/yaml.v3 v3.0.1
)
goccy-go-yaml-52dacb8/benchmarks/go.sum 0000664 0000000 0000000 00000000777 15015766371 0020151 0 ustar 00root root 0000000 0000000 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.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
goccy-go-yaml-52dacb8/cmd/ 0000775 0000000 0000000 00000000000 15015766371 0015431 5 ustar 00root root 0000000 0000000 goccy-go-yaml-52dacb8/cmd/ycat/ 0000775 0000000 0000000 00000000000 15015766371 0016371 5 ustar 00root root 0000000 0000000 goccy-go-yaml-52dacb8/cmd/ycat/go.mod 0000664 0000000 0000000 00000000524 15015766371 0017500 0 ustar 00root root 0000000 0000000 module github.com/goccy/go-yaml/cmd/ycat
go 1.21.0
require (
github.com/fatih/color v1.18.0
github.com/goccy/go-yaml v0.0.0-00010101000000-000000000000
github.com/mattn/go-colorable v0.1.13
)
require (
github.com/mattn/go-isatty v0.0.20 // indirect
golang.org/x/sys v0.26.0 // indirect
)
replace github.com/goccy/go-yaml => ../../
goccy-go-yaml-52dacb8/cmd/ycat/go.sum 0000664 0000000 0000000 00000004743 15015766371 0017534 0 ustar 00root root 0000000 0000000 github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.22.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27HYW8P9FDk5PbgA=
github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
goccy-go-yaml-52dacb8/cmd/ycat/ycat.go 0000664 0000000 0000000 00000003674 15015766371 0017672 0 ustar 00root root 0000000 0000000 package main
import (
"errors"
"fmt"
"os"
"github.com/fatih/color"
"github.com/mattn/go-colorable"
"github.com/goccy/go-yaml"
"github.com/goccy/go-yaml/lexer"
"github.com/goccy/go-yaml/printer"
)
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 := os.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),
}
}
p.Comment = func() *printer.Property {
return &printer.Property{
Prefix: format(color.FgHiBlack),
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))
}
}
goccy-go-yaml-52dacb8/context.go 0000664 0000000 0000000 00000001337 15015766371 0016705 0 ustar 00root root 0000000 0000000 package yaml
import "context"
type (
ctxMergeKey struct{}
ctxAnchorKey struct{}
)
func withMerge(ctx context.Context) context.Context {
return context.WithValue(ctx, ctxMergeKey{}, true)
}
func isMerge(ctx context.Context) bool {
v, ok := ctx.Value(ctxMergeKey{}).(bool)
if !ok {
return false
}
return v
}
func withAnchor(ctx context.Context, name string) context.Context {
anchorMap := getAnchorMap(ctx)
if anchorMap == nil {
anchorMap = make(map[string]struct{})
}
anchorMap[name] = struct{}{}
return context.WithValue(ctx, ctxAnchorKey{}, anchorMap)
}
func getAnchorMap(ctx context.Context) map[string]struct{} {
v, ok := ctx.Value(ctxAnchorKey{}).(map[string]struct{})
if !ok {
return nil
}
return v
}
goccy-go-yaml-52dacb8/decode.go 0000664 0000000 0000000 00000143403 15015766371 0016445 0 ustar 00root root 0000000 0000000 package yaml
import (
"bytes"
"context"
"encoding"
"encoding/base64"
"fmt"
"io"
"maps"
"math"
"os"
"path/filepath"
"reflect"
"sort"
"strconv"
"strings"
"time"
"github.com/goccy/go-yaml/ast"
"github.com/goccy/go-yaml/internal/errors"
"github.com/goccy/go-yaml/internal/format"
"github.com/goccy/go-yaml/parser"
"github.com/goccy/go-yaml/token"
)
// 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
customUnmarshalerMap map[reflect.Type]func(context.Context, interface{}, []byte) error
commentMaps []CommentMap
toCommentMap CommentMap
opts []DecodeOption
referenceFiles []string
referenceDirs []string
isRecursiveDir bool
isResolvedReference bool
validator StructValidator
disallowUnknownField bool
allowDuplicateMapKey bool
useOrderedMap bool
useJSONUnmarshaler bool
parsedFile *ast.File
streamIndex int
decodeDepth 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{},
customUnmarshalerMap: map[reflect.Type]func(context.Context, interface{}, []byte) error{},
opts: opts,
referenceReaders: []io.Reader{},
referenceFiles: []string{},
referenceDirs: []string{},
isRecursiveDir: false,
isResolvedReference: false,
disallowUnknownField: false,
allowDuplicateMapKey: false,
useOrderedMap: false,
}
}
const maxDecodeDepth = 10000
func (d *Decoder) stepIn() {
d.decodeDepth++
}
func (d *Decoder) stepOut() {
d.decodeDepth--
}
func (d *Decoder) isExceededMaxDepth() bool {
return d.decodeDepth > maxDecodeDepth
}
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) mapKeyNodeToString(ctx context.Context, node ast.MapKeyNode) (string, error) {
key, err := d.nodeToValue(ctx, node)
if err != nil {
return "", err
}
if key == nil {
return "null", nil
}
if k, ok := key.(string); ok {
return k, nil
}
return fmt.Sprint(key), nil
}
func (d *Decoder) setToMapValue(ctx context.Context, node ast.Node, m map[string]interface{}) error {
d.stepIn()
defer d.stepOut()
if d.isExceededMaxDepth() {
return ErrExceededMaxDepth
}
d.setPathToCommentMap(node)
switch n := node.(type) {
case *ast.MappingValueNode:
if n.Key.IsMergeKey() {
value, err := d.getMapNode(n.Value, true)
if err != nil {
return err
}
iter := value.MapRange()
for iter.Next() {
if err := d.setToMapValue(ctx, iter.KeyValue(), m); err != nil {
return err
}
}
} else {
key, err := d.mapKeyNodeToString(ctx, n.Key)
if err != nil {
return err
}
v, err := d.nodeToValue(ctx, n.Value)
if err != nil {
return err
}
m[key] = v
}
case *ast.MappingNode:
for _, value := range n.Values {
if err := d.setToMapValue(ctx, value, m); err != nil {
return err
}
}
case *ast.AnchorNode:
anchorName := n.Name.GetToken().Value
d.anchorNodeMap[anchorName] = n.Value
}
return nil
}
func (d *Decoder) setToOrderedMapValue(ctx context.Context, node ast.Node, m *MapSlice) error {
d.stepIn()
defer d.stepOut()
if d.isExceededMaxDepth() {
return ErrExceededMaxDepth
}
d.setPathToCommentMap(node)
switch n := node.(type) {
case *ast.MappingValueNode:
if n.Key.IsMergeKey() {
value, err := d.getMapNode(n.Value, true)
if err != nil {
return err
}
iter := value.MapRange()
for iter.Next() {
if err := d.setToOrderedMapValue(ctx, iter.KeyValue(), m); err != nil {
return err
}
}
} else {
key, err := d.mapKeyNodeToString(ctx, n.Key)
if err != nil {
return err
}
value, err := d.nodeToValue(ctx, n.Value)
if err != nil {
return err
}
*m = append(*m, MapItem{Key: key, Value: value})
}
case *ast.MappingNode:
for _, value := range n.Values {
if err := d.setToOrderedMapValue(ctx, value, m); err != nil {
return err
}
}
}
return nil
}
func (d *Decoder) setPathToCommentMap(node ast.Node) {
if node == nil {
return
}
if d.toCommentMap == nil {
return
}
d.addHeadOrLineCommentToMap(node)
d.addFootCommentToMap(node)
}
func (d *Decoder) addHeadOrLineCommentToMap(node ast.Node) {
sequence, ok := node.(*ast.SequenceNode)
if ok {
d.addSequenceNodeCommentToMap(sequence)
return
}
commentGroup := node.GetComment()
if commentGroup == nil {
return
}
texts := []string{}
targetLine := node.GetToken().Position.Line
minCommentLine := math.MaxInt
for _, comment := range commentGroup.Comments {
if minCommentLine > comment.Token.Position.Line {
minCommentLine = comment.Token.Position.Line
}
texts = append(texts, comment.Token.Value)
}
if len(texts) == 0 {
return
}
commentPath := node.GetPath()
if minCommentLine < targetLine {
switch n := node.(type) {
case *ast.MappingNode:
if len(n.Values) != 0 {
commentPath = n.Values[0].Key.GetPath()
}
case *ast.MappingValueNode:
commentPath = n.Key.GetPath()
}
d.addCommentToMap(commentPath, HeadComment(texts...))
} else {
d.addCommentToMap(commentPath, LineComment(texts[0]))
}
}
func (d *Decoder) addSequenceNodeCommentToMap(node *ast.SequenceNode) {
if len(node.ValueHeadComments) != 0 {
for idx, headComment := range node.ValueHeadComments {
if headComment == nil {
continue
}
texts := make([]string, 0, len(headComment.Comments))
for _, comment := range headComment.Comments {
texts = append(texts, comment.Token.Value)
}
if len(texts) != 0 {
d.addCommentToMap(node.Values[idx].GetPath(), HeadComment(texts...))
}
}
}
firstElemHeadComment := node.GetComment()
if firstElemHeadComment != nil {
texts := make([]string, 0, len(firstElemHeadComment.Comments))
for _, comment := range firstElemHeadComment.Comments {
texts = append(texts, comment.Token.Value)
}
if len(texts) != 0 {
d.addCommentToMap(node.Values[0].GetPath(), HeadComment(texts...))
}
}
}
func (d *Decoder) addFootCommentToMap(node ast.Node) {
var (
footComment *ast.CommentGroupNode
footCommentPath = node.GetPath()
)
switch n := node.(type) {
case *ast.SequenceNode:
footComment = n.FootComment
if n.FootComment != nil {
footCommentPath = n.FootComment.GetPath()
}
case *ast.MappingNode:
footComment = n.FootComment
if n.FootComment != nil {
footCommentPath = n.FootComment.GetPath()
}
case *ast.MappingValueNode:
footComment = n.FootComment
if n.FootComment != nil {
footCommentPath = n.FootComment.GetPath()
}
}
if footComment == nil {
return
}
var texts []string
for _, comment := range footComment.Comments {
texts = append(texts, comment.Token.Value)
}
if len(texts) != 0 {
d.addCommentToMap(footCommentPath, FootComment(texts...))
}
}
func (d *Decoder) addCommentToMap(path string, comment *Comment) {
for _, c := range d.toCommentMap[path] {
if c.Position == comment.Position {
// already added same comment
return
}
}
d.toCommentMap[path] = append(d.toCommentMap[path], comment)
sort.Slice(d.toCommentMap[path], func(i, j int) bool {
return d.toCommentMap[path][i].Position < d.toCommentMap[path][j].Position
})
}
func (d *Decoder) nodeToValue(ctx context.Context, node ast.Node) (any, error) {
d.stepIn()
defer d.stepOut()
if d.isExceededMaxDepth() {
return nil, ErrExceededMaxDepth
}
d.setPathToCommentMap(node)
switch n := node.(type) {
case *ast.NullNode:
return nil, nil
case *ast.StringNode:
return n.GetValue(), nil
case *ast.IntegerNode:
return n.GetValue(), nil
case *ast.FloatNode:
return n.GetValue(), nil
case *ast.BoolNode:
return n.GetValue(), nil
case *ast.InfinityNode:
return n.GetValue(), nil
case *ast.NanNode:
return n.GetValue(), nil
case *ast.TagNode:
if n.Directive != nil {
v, err := d.nodeToValue(ctx, n.Value)
if err != nil {
return nil, err
}
if v == nil {
return "", nil
}
return fmt.Sprint(v), nil
}
switch token.ReservedTagKeyword(n.Start.Value) {
case token.TimestampTag:
t, _ := d.castToTime(ctx, n.Value)
return t, nil
case token.IntegerTag:
v, err := d.nodeToValue(ctx, n.Value)
if err != nil {
return nil, err
}
i, _ := strconv.Atoi(fmt.Sprint(v))
return i, nil
case token.FloatTag:
v, err := d.nodeToValue(ctx, n.Value)
if err != nil {
return nil, err
}
return d.castToFloat(v), nil
case token.NullTag:
return nil, nil
case token.BinaryTag:
v, err := d.nodeToValue(ctx, n.Value)
if err != nil {
return nil, err
}
str, ok := v.(string)
if !ok {
return nil, errors.ErrSyntax(
fmt.Sprintf("cannot convert %q to string", fmt.Sprint(v)),
n.Value.GetToken(),
)
}
b, _ := base64.StdEncoding.DecodeString(str)
return b, nil
case token.BooleanTag:
v, err := d.nodeToValue(ctx, n.Value)
if err != nil {
return nil, err
}
str := strings.ToLower(fmt.Sprint(v))
b, err := strconv.ParseBool(str)
if err == nil {
return b, nil
}
switch str {
case "yes":
return true, nil
case "no":
return false, nil
}
return nil, errors.ErrSyntax(fmt.Sprintf("cannot convert %q to boolean", fmt.Sprint(v)), n.Value.GetToken())
case token.StringTag:
v, err := d.nodeToValue(ctx, n.Value)
if err != nil {
return nil, err
}
if v == nil {
return "", nil
}
return fmt.Sprint(v), nil
case token.MappingTag:
return d.nodeToValue(ctx, n.Value)
default:
return d.nodeToValue(ctx, n.Value)
}
case *ast.AnchorNode:
anchorName := n.Name.GetToken().Value
// To handle the case where alias is processed recursively, the result of alias can be set to nil in advance.
d.anchorNodeMap[anchorName] = nil
anchorValue, err := d.nodeToValue(withAnchor(ctx, anchorName), n.Value)
if err != nil {
delete(d.anchorNodeMap, anchorName)
return nil, err
}
d.anchorNodeMap[anchorName] = n.Value
d.anchorValueMap[anchorName] = reflect.ValueOf(anchorValue)
return anchorValue, nil
case *ast.AliasNode:
text := n.Value.String()
if _, exists := getAnchorMap(ctx)[text]; exists {
// self recursion.
return nil, nil
}
if v, exists := d.anchorValueMap[text]; exists {
if !v.IsValid() {
return nil, nil
}
return v.Interface(), nil
}
aliasName := n.Value.GetToken().Value
return nil, errors.ErrSyntax(fmt.Sprintf("could not find alias %q", aliasName), n.Value.GetToken())
case *ast.LiteralNode:
return n.Value.GetValue(), nil
case *ast.MappingKeyNode:
return d.nodeToValue(ctx, n.Value)
case *ast.MappingValueNode:
if n.Key.IsMergeKey() {
value, err := d.getMapNode(n.Value, true)
if err != nil {
return nil, err
}
iter := value.MapRange()
if d.useOrderedMap {
m := MapSlice{}
for iter.Next() {
if err := d.setToOrderedMapValue(ctx, iter.KeyValue(), &m); err != nil {
return nil, err
}
}
return m, nil
}
m := make(map[string]any)
for iter.Next() {
if err := d.setToMapValue(ctx, iter.KeyValue(), m); err != nil {
return nil, err
}
}
return m, nil
}
key, err := d.mapKeyNodeToString(ctx, n.Key)
if err != nil {
return nil, err
}
if d.useOrderedMap {
v, err := d.nodeToValue(ctx, n.Value)
if err != nil {
return nil, err
}
return MapSlice{{Key: key, Value: v}}, nil
}
v, err := d.nodeToValue(ctx, n.Value)
if err != nil {
return nil, err
}
return map[string]interface{}{key: v}, nil
case *ast.MappingNode:
if d.useOrderedMap {
m := make(MapSlice, 0, len(n.Values))
for _, value := range n.Values {
if err := d.setToOrderedMapValue(ctx, value, &m); err != nil {
return nil, err
}
}
return m, nil
}
m := make(map[string]interface{}, len(n.Values))
for _, value := range n.Values {
if err := d.setToMapValue(ctx, value, m); err != nil {
return nil, err
}
}
return m, nil
case *ast.SequenceNode:
v := make([]interface{}, 0, len(n.Values))
for _, value := range n.Values {
vv, err := d.nodeToValue(ctx, value)
if err != nil {
return nil, err
}
v = append(v, vv)
}
return v, nil
}
return nil, nil
}
func (d *Decoder) getMapNode(node ast.Node, isMerge bool) (ast.MapNode, error) {
d.stepIn()
defer d.stepOut()
if d.isExceededMaxDepth() {
return nil, ErrExceededMaxDepth
}
switch n := node.(type) {
case ast.MapNode:
return n, nil
case *ast.AnchorNode:
anchorName := n.Name.GetToken().Value
d.anchorNodeMap[anchorName] = n.Value
return d.getMapNode(n.Value, isMerge)
case *ast.AliasNode:
aliasName := n.Value.GetToken().Value
node := d.anchorNodeMap[aliasName]
if node == nil {
return nil, fmt.Errorf("cannot find anchor by alias name %s", aliasName)
}
return d.getMapNode(node, isMerge)
case *ast.SequenceNode:
if !isMerge {
return nil, errors.ErrUnexpectedNodeType(node.Type(), ast.MappingType, node.GetToken())
}
var mapNodes []ast.MapNode
for _, value := range n.Values {
mapNode, err := d.getMapNode(value, false)
if err != nil {
return nil, err
}
mapNodes = append(mapNodes, mapNode)
}
return ast.SequenceMergeValue(mapNodes...), nil
}
return nil, errors.ErrUnexpectedNodeType(node.Type(), ast.MappingType, node.GetToken())
}
func (d *Decoder) getArrayNode(node ast.Node) (ast.ArrayNode, error) {
d.stepIn()
defer d.stepOut()
if d.isExceededMaxDepth() {
return nil, ErrExceededMaxDepth
}
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, errors.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, fmt.Errorf("cannot find anchor by alias name %s", aliasName)
}
arrayNode, ok := node.(ast.ArrayNode)
if ok {
return arrayNode, nil
}
return nil, errors.ErrUnexpectedNodeType(node.Type(), ast.SequenceType, node.GetToken())
}
arrayNode, ok := node.(ast.ArrayNode)
if !ok {
return nil, errors.ErrUnexpectedNodeType(node.Type(), ast.SequenceType, node.GetToken())
}
return arrayNode, nil
}
func (d *Decoder) convertValue(v reflect.Value, typ reflect.Type, src ast.Node) (reflect.Value, error) {
if typ.Kind() != reflect.String {
if !v.Type().ConvertibleTo(typ) {
// Special case for "strings -> floats" aka scientific notation
// If the destination type is a float and the source type is a string, check if we can
// use strconv.ParseFloat to convert the string to a float.
if (typ.Kind() == reflect.Float32 || typ.Kind() == reflect.Float64) &&
v.Type().Kind() == reflect.String {
if f, err := strconv.ParseFloat(v.String(), 64); err == nil {
if typ.Kind() == reflect.Float32 {
return reflect.ValueOf(float32(f)), nil
} else if typ.Kind() == reflect.Float64 {
return reflect.ValueOf(f), nil
}
// else, fall through to the error below
}
}
return reflect.Zero(typ), errors.ErrTypeMismatch(typ, v.Type(), src.GetToken())
}
return v.Convert(typ), nil
}
// cast value to string
var strVal string
switch v.Type().Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
strVal = strconv.FormatInt(v.Int(), 10)
case reflect.Float32, reflect.Float64:
strVal = fmt.Sprint(v.Float())
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
strVal = strconv.FormatUint(v.Uint(), 10)
case reflect.Bool:
strVal = strconv.FormatBool(v.Bool())
default:
if !v.Type().ConvertibleTo(typ) {
return reflect.Zero(typ), errors.ErrTypeMismatch(typ, v.Type(), src.GetToken())
}
return v.Convert(typ), nil
}
val := reflect.ValueOf(strVal)
if val.Type() != typ {
// Handle named types, e.g., `type MyString string`
val = val.Convert(typ)
}
return val, nil
}
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 err
}
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) unmarshalableDocument(node ast.Node) ([]byte, error) {
doc := format.FormatNodeWithResolvedAlias(node, d.anchorNodeMap)
return []byte(doc), nil
}
func (d *Decoder) unmarshalableText(node ast.Node) ([]byte, bool) {
doc := format.FormatNodeWithResolvedAlias(node, d.anchorNodeMap)
var v string
if err := Unmarshal([]byte(doc), &v); err != nil {
return nil, false
}
return []byte(v), true
}
type jsonUnmarshaler interface {
UnmarshalJSON([]byte) error
}
func (d *Decoder) existsTypeInCustomUnmarshalerMap(t reflect.Type) bool {
if _, exists := d.customUnmarshalerMap[t]; exists {
return true
}
globalCustomUnmarshalerMu.Lock()
defer globalCustomUnmarshalerMu.Unlock()
if _, exists := globalCustomUnmarshalerMap[t]; exists {
return true
}
return false
}
func (d *Decoder) unmarshalerFromCustomUnmarshalerMap(t reflect.Type) (func(context.Context, interface{}, []byte) error, bool) {
if unmarshaler, exists := d.customUnmarshalerMap[t]; exists {
return unmarshaler, exists
}
globalCustomUnmarshalerMu.Lock()
defer globalCustomUnmarshalerMu.Unlock()
if unmarshaler, exists := globalCustomUnmarshalerMap[t]; exists {
return unmarshaler, exists
}
return nil, false
}
func (d *Decoder) canDecodeByUnmarshaler(dst reflect.Value) bool {
ptrValue := dst.Addr()
if d.existsTypeInCustomUnmarshalerMap(ptrValue.Type()) {
return true
}
iface := ptrValue.Interface()
switch iface.(type) {
case BytesUnmarshalerContext,
BytesUnmarshaler,
InterfaceUnmarshalerContext,
InterfaceUnmarshaler,
NodeUnmarshaler,
NodeUnmarshalerContext,
*time.Time,
*time.Duration,
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 {
ptrValue := dst.Addr()
if unmarshaler, exists := d.unmarshalerFromCustomUnmarshalerMap(ptrValue.Type()); exists {
b, err := d.unmarshalableDocument(src)
if err != nil {
return err
}
if err := unmarshaler(ctx, ptrValue.Interface(), b); err != nil {
return err
}
return nil
}
iface := ptrValue.Interface()
if unmarshaler, ok := iface.(BytesUnmarshalerContext); ok {
b, err := d.unmarshalableDocument(src)
if err != nil {
return err
}
if err := unmarshaler.UnmarshalYAML(ctx, b); err != nil {
return err
}
return nil
}
if unmarshaler, ok := iface.(BytesUnmarshaler); ok {
b, err := d.unmarshalableDocument(src)
if err != nil {
return err
}
if err := unmarshaler.UnmarshalYAML(b); err != nil {
return err
}
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 ErrDecodeRequiredPointerType
}
if err := d.decodeValue(ctx, rv.Elem(), src); err != nil {
return err
}
return nil
}); err != nil {
return err
}
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 ErrDecodeRequiredPointerType
}
if err := d.decodeValue(ctx, rv.Elem(), src); err != nil {
return err
}
return nil
}); err != nil {
return err
}
return nil
}
if unmarshaler, ok := iface.(NodeUnmarshaler); ok {
if err := unmarshaler.UnmarshalYAML(src); err != nil {
return err
}
return nil
}
if unmarshaler, ok := iface.(NodeUnmarshalerContext); ok {
if err := unmarshaler.UnmarshalYAML(ctx, src); err != nil {
return err
}
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 err
}
return nil
}
}
if d.useJSONUnmarshaler {
if unmarshaler, ok := iface.(jsonUnmarshaler); ok {
b, err := d.unmarshalableDocument(src)
if err != nil {
return err
}
jsonBytes, err := YAMLToJSON(b)
if err != nil {
return err
}
jsonBytes = bytes.TrimRight(jsonBytes, "\n")
if err := unmarshaler.UnmarshalJSON(jsonBytes); err != nil {
return err
}
return nil
}
}
return errors.New("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 {
d.stepIn()
defer d.stepOut()
if d.isExceededMaxDepth() {
return ErrExceededMaxDepth
}
if !dst.IsValid() {
return nil
}
if src.Type() == ast.AnchorType {
anchor, _ := src.(*ast.AnchorNode)
anchorName := anchor.Name.GetToken().Value
if err := d.decodeValue(withAnchor(ctx, anchorName), dst, anchor.Value); err != nil {
return err
}
d.anchorValueMap[anchorName] = dst
return nil
}
if d.canDecodeByUnmarshaler(dst) {
if err := d.decodeByUnmarshaler(ctx, dst, src); err != nil {
return err
}
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 err
}
castedValue, err := d.castToAssignableValue(v, dst.Type(), src)
if err != nil {
return err
}
dst.Set(castedValue)
case reflect.Interface:
if dst.Type() == astNodeType {
dst.Set(reflect.ValueOf(src))
return nil
}
srcVal, err := d.nodeToValue(ctx, src)
if err != nil {
return err
}
v := reflect.ValueOf(srcVal)
if v.IsValid() {
dst.Set(v)
} else {
dst.Set(reflect.Zero(valueType))
}
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, err := d.nodeToValue(ctx, src)
if err != nil {
return err
}
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
}
case string: // handle scientific notation
if i, err := strconv.ParseFloat(vv, 64); err == nil {
if 0 <= i && i <= math.MaxUint64 && !dst.OverflowInt(int64(i)) {
dst.SetInt(int64(i))
return nil
}
} else { // couldn't be parsed as float
return errors.ErrTypeMismatch(valueType, reflect.TypeOf(v), src.GetToken())
}
default:
return errors.ErrTypeMismatch(valueType, reflect.TypeOf(v), src.GetToken())
}
return errors.ErrOverflow(valueType, fmt.Sprint(v), src.GetToken())
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
v, err := d.nodeToValue(ctx, src)
if err != nil {
return err
}
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
}
case string: // handle scientific notation
if i, err := strconv.ParseFloat(vv, 64); err == nil {
if 0 <= i && i <= math.MaxUint64 && !dst.OverflowUint(uint64(i)) {
dst.SetUint(uint64(i))
return nil
}
} else { // couldn't be parsed as float
return errors.ErrTypeMismatch(valueType, reflect.TypeOf(v), src.GetToken())
}
default:
return errors.ErrTypeMismatch(valueType, reflect.TypeOf(v), src.GetToken())
}
return errors.ErrOverflow(valueType, fmt.Sprint(v), src.GetToken())
}
srcVal, err := d.nodeToValue(ctx, src)
if err != nil {
return err
}
v := reflect.ValueOf(srcVal)
if v.IsValid() {
convertedValue, err := d.convertValue(v, dst.Type(), src)
if err != nil {
return err
}
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, src ast.Node) (reflect.Value, error) {
if target.Kind() != reflect.Ptr {
if !value.Type().AssignableTo(target) {
return reflect.Value{}, errors.ErrTypeMismatch(target, value.Type(), src.GetToken())
}
return value, nil
}
const maxAddrCount = 5
for i := 0; i < maxAddrCount; i++ {
if value.Type().AssignableTo(target) {
break
}
if !value.CanAddr() {
break
}
value = value.Addr()
}
if !value.Type().AssignableTo(target) {
return reflect.Value{}, errors.ErrTypeMismatch(target, value.Type(), src.GetToken())
}
return value, nil
}
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
value := d.anchorValueMap[aliasName]
if value.IsValid() {
v, err := d.castToAssignableValue(value, typ, node)
if err == nil {
return v, nil
}
}
anchor, exists := d.anchorNodeMap[aliasName]
if exists {
node = anchor
}
}
var newValue reflect.Value
if node.Type() == ast.NullType {
newValue = reflect.New(typ).Elem()
} else {
newValue = d.createDecodableValue(typ)
}
for defaultVal.Kind() == reflect.Ptr {
defaultVal = defaultVal.Elem()
}
if defaultVal.IsValid() && defaultVal.Type().AssignableTo(newValue.Type()) {
newValue.Set(defaultVal)
}
if node.Type() != ast.NullType {
if err := d.decodeValue(ctx, newValue, node); err != nil {
return reflect.Value{}, err
}
}
return d.castToAssignableValue(newValue, typ, node)
}
func (d *Decoder) keyToNodeMap(ctx context.Context, node ast.Node, ignoreMergeKey bool, getKeyOrValueNode func(*ast.MapNodeIter) ast.Node) (map[string]ast.Node, error) {
d.stepIn()
defer d.stepOut()
if d.isExceededMaxDepth() {
return nil, ErrExceededMaxDepth
}
mapNode, err := d.getMapNode(node, false)
if err != nil {
return nil, err
}
keyMap := map[string]struct{}{}
keyToNodeMap := map[string]ast.Node{}
mapIter := mapNode.MapRange()
for mapIter.Next() {
keyNode := mapIter.Key()
if keyNode.IsMergeKey() {
if ignoreMergeKey {
continue
}
mergeMap, err := d.keyToNodeMap(ctx, mapIter.Value(), ignoreMergeKey, getKeyOrValueNode)
if err != nil {
return nil, err
}
for k, v := range mergeMap {
if err := d.validateDuplicateKey(keyMap, k, v); err != nil {
return nil, err
}
keyToNodeMap[k] = v
}
} else {
keyVal, err := d.nodeToValue(ctx, keyNode)
if err != nil {
return nil, err
}
key, ok := keyVal.(string)
if !ok {
return nil, err
}
if err := d.validateDuplicateKey(keyMap, key, keyNode); err != nil {
return nil, err
}
keyToNodeMap[key] = getKeyOrValueNode(mapIter)
}
}
return keyToNodeMap, nil
}
func (d *Decoder) keyToKeyNodeMap(ctx context.Context, node ast.Node, ignoreMergeKey bool) (map[string]ast.Node, error) {
m, err := d.keyToNodeMap(ctx, node, ignoreMergeKey, func(nodeMap *ast.MapNodeIter) ast.Node { return nodeMap.Key() })
if err != nil {
return nil, err
}
return m, nil
}
func (d *Decoder) keyToValueNodeMap(ctx context.Context, node ast.Node, ignoreMergeKey bool) (map[string]ast.Node, error) {
m, err := d.keyToNodeMap(ctx, node, ignoreMergeKey, func(nodeMap *ast.MapNodeIter) ast.Node { return nodeMap.Value() })
if err != nil {
return nil, err
}
return m, nil
}
func (d *Decoder) setDefaultValueIfConflicted(v reflect.Value, fieldMap StructFieldMap) error {
for v.Type().Kind() == reflect.Ptr {
v = v.Elem()
}
typ := v.Type()
if typ.Kind() != reflect.Struct {
return nil
}
embeddedStructFieldMap, err := structFieldMap(typ)
if err != nil {
return err
}
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(ctx context.Context, src ast.Node) (time.Time, error) {
if src == nil {
return time.Time{}, nil
}
v, err := d.nodeToValue(ctx, src)
if err != nil {
return time.Time{}, err
}
if t, ok := v.(time.Time); ok {
return t, nil
}
s, ok := v.(string)
if !ok {
return time.Time{}, errors.ErrTypeMismatch(reflect.TypeOf(time.Time{}), reflect.TypeOf(v), src.GetToken())
}
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(ctx, src)
if err != nil {
return err
}
dst.Set(reflect.ValueOf(t))
return nil
}
func (d *Decoder) castToDuration(ctx context.Context, src ast.Node) (time.Duration, error) {
if src == nil {
return 0, nil
}
v, err := d.nodeToValue(ctx, src)
if err != nil {
return 0, err
}
if t, ok := v.(time.Duration); ok {
return t, nil
}
s, ok := v.(string)
if !ok {
return 0, errors.ErrTypeMismatch(reflect.TypeOf(time.Duration(0)), reflect.TypeOf(v), src.GetToken())
}
t, err := time.ParseDuration(s)
if err != nil {
return 0, err
}
return t, nil
}
func (d *Decoder) decodeDuration(ctx context.Context, dst reflect.Value, src ast.Node) error {
t, err := d.castToDuration(ctx, src)
if err != nil {
return err
}
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, true)
if err != nil {
return ""
}
mapIter := mapNode.MapRange()
for mapIter.Next() {
key := mapIter.Key()
value := mapIter.Value()
if key.IsMergeKey() && 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
}
d.stepIn()
defer d.stepOut()
if d.isExceededMaxDepth() {
return ErrExceededMaxDepth
}
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 err
}
ignoreMergeKey := structFieldMap.hasMergeProperty()
keyToNodeMap, err := d.keyToValueNodeMap(ctx, src, ignoreMergeKey)
if err != nil {
return err
}
var unknownFields map[string]ast.Node
if d.disallowUnknownField {
unknownFields, err = d.keyToKeyNodeMap(ctx, src, ignoreMergeKey)
if err != nil {
return err
}
}
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() {
value, err := d.castToAssignableValue(newFieldValue, fieldValue.Type(), d.anchorNodeMap[aliasName])
if err != nil {
return err
}
fieldValue.Set(value)
}
}
continue
}
if !fieldValue.CanSet() {
return fmt.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 err
}
}
if err != nil {
if foundErr != nil {
continue
}
var te *errors.TypeError
if errors.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(newFieldValue)
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 *errors.TypeError
if errors.As(err, &te) {
fieldName := fmt.Sprintf("%s.%s", structType.Name(), field.Name)
te.StructFieldName = &fieldName
foundErr = te
} else {
foundErr = err
}
continue
}
fieldValue.Set(newFieldValue)
}
if foundErr != nil {
return foundErr
}
// 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 errors.ErrUnknownField(fmt.Sprintf(`unknown field "%s"`, key), node.GetToken())
}
}
if d.validator != nil {
if err := d.validator.Struct(dst.Addr().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),
d.getParentMapTokenIfExistsForValidationError(node.Type(), 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
}
// getParentMapTokenIfExists if the NodeType is a container type such as MappingType or SequenceType,
// it is necessary to return the parent MapNode's colon token to represent the entire container.
func (d *Decoder) getParentMapTokenIfExistsForValidationError(typ ast.NodeType, tk *token.Token) *token.Token {
if tk == nil {
return nil
}
if typ == ast.MappingType {
// map:
// key: value
// ^ current token ( colon )
if tk.Prev == nil {
return tk
}
key := tk.Prev
if key.Prev == nil {
return tk
}
return key.Prev
}
if typ == ast.SequenceType {
// map:
// - value
// ^ current token ( sequence entry )
if tk.Prev == nil {
return tk
}
return tk.Prev
}
return tk
}
func (d *Decoder) decodeArray(ctx context.Context, dst reflect.Value, src ast.Node) error {
d.stepIn()
defer d.stepOut()
if d.isExceededMaxDepth() {
return ErrExceededMaxDepth
}
arrayNode, err := d.getArrayNode(src)
if err != nil {
return err
}
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
}
arrayValue.Index(idx).Set(dstValue)
}
idx++
}
dst.Set(arrayValue)
if foundErr != nil {
return foundErr
}
return nil
}
func (d *Decoder) decodeSlice(ctx context.Context, dst reflect.Value, src ast.Node) error {
d.stepIn()
defer d.stepOut()
if d.isExceededMaxDepth() {
return ErrExceededMaxDepth
}
arrayNode, err := d.getArrayNode(src)
if err != nil {
return err
}
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, dstValue)
}
dst.Set(sliceValue)
if foundErr != nil {
return foundErr
}
return nil
}
func (d *Decoder) decodeMapItem(ctx context.Context, dst *MapItem, src ast.Node) error {
d.stepIn()
defer d.stepOut()
if d.isExceededMaxDepth() {
return ErrExceededMaxDepth
}
mapNode, err := d.getMapNode(src, isMerge(ctx))
if err != nil {
return err
}
mapIter := mapNode.MapRange()
if !mapIter.Next() {
return nil
}
key := mapIter.Key()
value := mapIter.Value()
if key.IsMergeKey() {
if err := d.decodeMapItem(withMerge(ctx), dst, value); err != nil {
return err
}
return nil
}
k, err := d.nodeToValue(ctx, key)
if err != nil {
return err
}
v, err := d.nodeToValue(ctx, value)
if err != nil {
return err
}
*dst = MapItem{Key: k, Value: v}
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.allowDuplicateMapKey {
if _, exists := keyMap[k]; exists {
return errors.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 {
d.stepIn()
defer d.stepOut()
if d.isExceededMaxDepth() {
return ErrExceededMaxDepth
}
mapNode, err := d.getMapNode(src, isMerge(ctx))
if err != nil {
return err
}
mapSlice := MapSlice{}
mapIter := mapNode.MapRange()
keyMap := map[string]struct{}{}
for mapIter.Next() {
key := mapIter.Key()
value := mapIter.Value()
if key.IsMergeKey() {
var m MapSlice
if err := d.decodeMapSlice(withMerge(ctx), &m, value); err != nil {
return err
}
for _, v := range m {
if err := d.validateDuplicateKey(keyMap, v.Key, value); err != nil {
return err
}
mapSlice = append(mapSlice, v)
}
continue
}
k, err := d.nodeToValue(ctx, key)
if err != nil {
return err
}
if err := d.validateDuplicateKey(keyMap, k, key); err != nil {
return err
}
v, err := d.nodeToValue(ctx, value)
if err != nil {
return err
}
mapSlice = append(mapSlice, MapItem{Key: k, Value: v})
}
*dst = mapSlice
return nil
}
func (d *Decoder) decodeMap(ctx context.Context, dst reflect.Value, src ast.Node) error {
d.stepIn()
defer d.stepOut()
if d.isExceededMaxDepth() {
return ErrExceededMaxDepth
}
mapNode, err := d.getMapNode(src, isMerge(ctx))
if err != nil {
return err
}
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.IsMergeKey() {
if err := d.decodeMap(withMerge(ctx), dst, value); err != nil {
return err
}
iter := dst.MapRange()
for iter.Next() {
if err := d.validateDuplicateKey(keyMap, iter.Key(), value); err != nil {
return err
}
mapValue.SetMapIndex(iter.Key(), iter.Value())
}
continue
}
k := d.createDecodableValue(keyType)
if d.canDecodeByUnmarshaler(k) {
if err := d.decodeByUnmarshaler(ctx, k, key); err != nil {
return err
}
} else {
keyVal, err := d.nodeToValue(ctx, key)
if err != nil {
return err
}
k = reflect.ValueOf(keyVal)
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 err
}
}
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), dstValue)
continue
}
if keyType.Kind() != k.Kind() {
return errors.ErrSyntax(
fmt.Sprintf("cannot convert %q type to %q type", k.Kind(), keyType.Kind()),
key.GetToken(),
)
}
mapValue.SetMapIndex(k, dstValue)
}
dst.Set(mapValue)
if foundErr != nil {
return foundErr
}
return nil
}
func (d *Decoder) fileToReader(file string) (io.Reader, error) {
reader, err := os.Open(file)
if err != nil {
return nil, err
}
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, err
}
readers := []io.Reader{}
for _, match := range matches {
if !d.isYAMLFile(match) {
continue
}
reader, err := d.fileToReader(match)
if err != nil {
return nil, err
}
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, _ error) error {
if !d.isYAMLFile(path) {
return nil
}
reader, readerErr := d.fileToReader(path)
if readerErr != nil {
return readerErr
}
readers = append(readers, reader)
return nil
}); err != nil {
return nil, err
}
return readers, nil
}
func (d *Decoder) resolveReference(ctx context.Context) error {
for _, opt := range d.opts {
if err := opt(d); err != nil {
return err
}
}
for _, file := range d.referenceFiles {
reader, err := d.fileToReader(file)
if err != nil {
return err
}
d.referenceReaders = append(d.referenceReaders, reader)
}
for _, dir := range d.referenceDirs {
if !d.isRecursiveDir {
readers, err := d.readersUnderDir(dir)
if err != nil {
return err
}
d.referenceReaders = append(d.referenceReaders, readers...)
} else {
readers, err := d.readersUnderDirRecursive(dir)
if err != nil {
return err
}
d.referenceReaders = append(d.referenceReaders, readers...)
}
}
for _, reader := range d.referenceReaders {
bytes, err := io.ReadAll(reader)
if err != nil {
return err
}
// assign new anchor definition to anchorMap
if _, err := d.parse(ctx, bytes); err != nil {
return err
}
}
d.isResolvedReference = true
return nil
}
func (d *Decoder) parse(ctx context.Context, bytes []byte) (*ast.File, error) {
var parseMode parser.Mode
if d.toCommentMap != nil {
parseMode = parser.ParseComments
}
var opts []parser.Option
if d.allowDuplicateMapKey {
opts = append(opts, parser.AllowDuplicateMapKey())
}
f, err := parser.ParseBytes(bytes, parseMode, opts...)
if err != nil {
return nil, err
}
normalizedFile := &ast.File{}
for _, doc := range f.Docs {
// try to decode ast.Node to value and map anchor value to anchorMap
v, err := d.nodeToValue(ctx, doc.Body)
if err != nil {
return nil, err
}
if v != nil || (doc.Body != nil && doc.Body.Type() == ast.NullType) {
normalizedFile.Docs = append(normalizedFile.Docs, doc)
cm := CommentMap{}
maps.Copy(cm, d.toCommentMap)
d.commentMaps = append(d.commentMaps, cm)
}
for k := range d.toCommentMap {
delete(d.toCommentMap, k)
}
}
return normalizedFile, nil
}
func (d *Decoder) isInitialized() bool {
return d.parsedFile != nil
}
func (d *Decoder) decodeInit(ctx context.Context) error {
if !d.isResolvedReference {
if err := d.resolveReference(ctx); err != nil {
return err
}
}
var buf bytes.Buffer
if _, err := io.Copy(&buf, d.reader); err != nil {
return err
}
file, err := d.parse(ctx, buf.Bytes())
if err != nil {
return err
}
d.parsedFile = file
return nil
}
func (d *Decoder) decode(ctx context.Context, v reflect.Value) error {
d.decodeDepth = 0
d.anchorValueMap = make(map[string]reflect.Value)
if len(d.parsedFile.Docs) == 0 {
// empty document.
dst := v.Elem()
if dst.IsValid() {
dst.Set(reflect.Zero(dst.Type()))
}
}
if len(d.parsedFile.Docs) <= d.streamIndex {
return io.EOF
}
body := d.parsedFile.Docs[d.streamIndex].Body
if body == nil {
return nil
}
if len(d.commentMaps) > d.streamIndex {
maps.Copy(d.toCommentMap, d.commentMaps[d.streamIndex])
}
if err := d.decodeValue(ctx, v.Elem(), body); err != nil {
return err
}
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.IsValid() || rv.Type().Kind() != reflect.Ptr {
return ErrDecodeRequiredPointerType
}
if d.isInitialized() {
if err := d.decode(ctx, rv); err != nil {
return err
}
return nil
}
if err := d.decodeInit(ctx); err != nil {
return err
}
if err := d.decode(ctx, rv); err != nil {
return err
}
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 ErrDecodeRequiredPointerType
}
if !d.isInitialized() {
if err := d.decodeInit(ctx); err != nil {
return err
}
}
// resolve references to the anchor on the same file
if _, err := d.nodeToValue(ctx, node); err != nil {
return err
}
if err := d.decodeValue(ctx, rv.Elem(), node); err != nil {
return err
}
return nil
}
goccy-go-yaml-52dacb8/decode_test.go 0000664 0000000 0000000 00000232010 15015766371 0017475 0 ustar 00root root 0000000 0000000 package yaml_test
import (
"bytes"
"context"
"fmt"
"io"
"log"
"math"
"net"
"reflect"
"strconv"
"strings"
"testing"
"time"
"github.com/goccy/go-yaml"
"github.com/goccy/go-yaml/ast"
"github.com/goccy/go-yaml/internal/errors"
"github.com/goccy/go-yaml/parser"
)
type Child struct {
B int
C int `yaml:"-"`
}
type TestString string
func TestDecoder(t *testing.T) {
tests := []struct {
source string
value interface{}
eof bool
}{
{
source: "v: hi\n",
value: map[string]string{"v": "hi"},
},
{
source: "v: hi\n",
value: map[string]TestString{"v": "hi"},
},
{
source: "v: \"true\"\n",
value: map[string]string{"v": "true"},
},
{
source: "v: \"false\"\n",
value: map[string]string{"v": "false"},
},
{
source: "v: true\n",
value: map[string]interface{}{"v": true},
},
{
source: "v: true\n",
value: map[string]string{"v": "true"},
},
{
source: "v: 10\n",
value: map[string]string{"v": "10"},
},
{
source: "v: 10\n",
value: map[string]TestString{"v": "10"},
},
{
source: "v: -10\n",
value: map[string]string{"v": "-10"},
},
{
source: "v: 1.234\n",
value: map[string]string{"v": "1.234"},
},
{
source: "v: \" foo\"\n",
value: map[string]string{"v": " foo"},
},
{
source: "v: \"foo \"\n",
value: map[string]string{"v": "foo "},
},
{
source: "v: \" foo \"\n",
value: map[string]string{"v": " foo "},
},
{
source: "v: false\n",
value: map[string]bool{"v": false},
},
{
source: "v: 10\n",
value: map[string]int{"v": 10},
},
{
source: "v: 10",
value: map[string]interface{}{"v": 10},
},
{
source: "v: 0b10",
value: map[string]interface{}{"v": 2},
},
{
source: "v: -0b101010",
value: map[string]interface{}{"v": -42},
},
{
source: "v: -0b1000000000000000000000000000000000000000000000000000000000000000",
value: map[string]interface{}{"v": int64(-9223372036854775808)},
},
{
source: "v: 0xA",
value: map[string]interface{}{"v": 10},
},
{
source: "v: .1",
value: map[string]interface{}{"v": 0.1},
},
{
source: "v: -.1",
value: map[string]interface{}{"v": -0.1},
},
{
source: "v: -10\n",
value: map[string]int{"v": -10},
},
{
source: "v: 4294967296\n",
value: map[string]int64{"v": int64(4294967296)},
},
{
source: "v: 0.1\n",
value: map[string]interface{}{"v": 0.1},
},
{
source: "v: 0.99\n",
value: map[string]float32{"v": 0.99},
},
{
source: "v: -0.1\n",
value: map[string]float64{"v": -0.1},
},
{
source: "v: 6.8523e+5",
value: map[string]interface{}{"v": 6.8523e+5},
},
{
source: "v: 685.230_15e+03",
value: map[string]interface{}{"v": 685.23015e+03},
},
{
source: "v: 685_230.15",
value: map[string]interface{}{"v": 685230.15},
},
{
source: "v: 685_230.15",
value: map[string]float64{"v": 685230.15},
},
{
source: "v: 685230",
value: map[string]interface{}{"v": 685230},
},
{
source: "v: +685_230",
value: map[string]interface{}{"v": 685230},
},
{
source: "v: 02472256",
value: map[string]interface{}{"v": 685230},
},
{
source: "v: 0x_0A_74_AE",
value: map[string]interface{}{"v": 685230},
},
{
source: "v: 0b1010_0111_0100_1010_1110",
value: map[string]interface{}{"v": 685230},
},
{
source: "v: +685_230",
value: map[string]int{"v": 685230},
},
// Bools from spec
{
source: "v: True",
value: map[string]interface{}{"v": true},
},
{
source: "v: TRUE",
value: map[string]interface{}{"v": true},
},
{
source: "v: False",
value: map[string]interface{}{"v": false},
},
{
source: "v: FALSE",
value: map[string]interface{}{"v": false},
},
{
source: "v: y",
value: map[string]interface{}{"v": "y"}, // y or yes or Yes is string
},
{
source: "v: NO",
value: map[string]interface{}{"v": "NO"}, // no or No or NO is string
},
{
source: "v: on",
value: map[string]interface{}{"v": "on"}, // on is string
},
// Some cross type conversions
{
source: "v: 42",
value: map[string]uint{"v": 42},
},
{
source: "v: 4294967296",
value: map[string]uint64{"v": uint64(4294967296)},
},
// int
{
source: "v: 2147483647",
value: map[string]int{"v": math.MaxInt32},
},
{
source: "v: -2147483648",
value: map[string]int{"v": math.MinInt32},
},
// int64
{
source: "v: 9223372036854775807",
value: map[string]int64{"v": math.MaxInt64},
},
{
source: "v: 0b111111111111111111111111111111111111111111111111111111111111111",
value: map[string]int64{"v": math.MaxInt64},
},
{
source: "v: -9223372036854775808",
value: map[string]int64{"v": math.MinInt64},
},
{
source: "v: -0b111111111111111111111111111111111111111111111111111111111111111",
value: map[string]int64{"v": -math.MaxInt64},
},
// uint
{
source: "v: 0",
value: map[string]uint{"v": 0},
},
{
source: "v: 4294967295",
value: map[string]uint{"v": math.MaxUint32},
},
{
source: "v: 1e3",
value: map[string]uint{"v": 1000},
},
// uint64
{
source: "v: 0",
value: map[string]uint{"v": 0},
},
{
source: "v: 18446744073709551615",
value: map[string]uint64{"v": math.MaxUint64},
},
{
source: "v: 0b1111111111111111111111111111111111111111111111111111111111111111",
value: map[string]uint64{"v": math.MaxUint64},
},
{
source: "v: 9223372036854775807",
value: map[string]uint64{"v": math.MaxInt64},
},
{
source: "v: 1e3",
value: map[string]uint64{"v": 1000},
},
// float32
{
source: "v: 3.40282346638528859811704183484516925440e+38",
value: map[string]float32{"v": math.MaxFloat32},
},
{
source: "v: 1.401298464324817070923729583289916131280e-45",
value: map[string]float32{"v": math.SmallestNonzeroFloat32},
},
{
source: "v: 18446744073709551615",
value: map[string]float32{"v": float32(math.MaxUint64)},
},
{
source: "v: 18446744073709551616",
value: map[string]float32{"v": float32(math.MaxUint64 + 1)},
},
{
source: "v: 1e-06",
value: map[string]float32{"v": 1e-6},
},
// float64
{
source: "v: 1.797693134862315708145274237317043567981e+308",
value: map[string]float64{"v": math.MaxFloat64},
},
{
source: "v: 4.940656458412465441765687928682213723651e-324",
value: map[string]float64{"v": math.SmallestNonzeroFloat64},
},
{
source: "v: 18446744073709551615",
value: map[string]float64{"v": float64(math.MaxUint64)},
},
{
source: "v: 18446744073709551616",
value: map[string]float64{"v": float64(math.MaxUint64 + 1)},
},
{
source: "v: 1e-06",
value: map[string]float64{"v": 1e-06},
},
// Timestamps
{
// Date only.
source: "v: 2015-01-01\n",
value: map[string]time.Time{"v": time.Date(2015, 1, 1, 0, 0, 0, 0, time.UTC)},
},
{
// RFC3339
source: "v: 2015-02-24T18:19:39.12Z\n",
value: map[string]time.Time{"v": time.Date(2015, 2, 24, 18, 19, 39, .12e9, time.UTC)},
},
{
// RFC3339 with short dates.
source: "v: 2015-2-3T3:4:5Z",
value: map[string]time.Time{"v": time.Date(2015, 2, 3, 3, 4, 5, 0, time.UTC)},
},
{
// ISO8601 lower case t
source: "v: 2015-02-24t18:19:39Z\n",
value: map[string]time.Time{"v": time.Date(2015, 2, 24, 18, 19, 39, 0, time.UTC)},
},
{
// space separate, no time zone
source: "v: 2015-02-24 18:19:39\n",
value: map[string]time.Time{"v": time.Date(2015, 2, 24, 18, 19, 39, 0, time.UTC)},
},
{
source: "v: 60s\n",
value: map[string]time.Duration{"v": time.Minute},
},
{
source: "v: -0.5h\n",
value: map[string]time.Duration{"v": -30 * time.Minute},
},
// Single Quoted values.
{
source: `'1': '2'`,
value: map[interface{}]interface{}{"1": `2`},
},
{
source: `'1': '"2"'`,
value: map[interface{}]interface{}{"1": `"2"`},
},
{
source: `'1': ''''`,
value: map[interface{}]interface{}{"1": `'`},
},
{
source: `'1': '''2'''`,
value: map[interface{}]interface{}{"1": `'2'`},
},
{
source: `'1': 'B''z'`,
value: map[interface{}]interface{}{"1": `B'z`},
},
{
source: `'1': '\'`,
value: map[interface{}]interface{}{"1": `\`},
},
{
source: `'1': '\\'`,
value: map[interface{}]interface{}{"1": `\\`},
},
{
source: `'1': '\"2\"'`,
value: map[interface{}]interface{}{"1": `\"2\"`},
},
{
source: `'1': '\\"2\\"'`,
value: map[interface{}]interface{}{"1": `\\"2\\"`},
},
{
source: "'1': ' 1\n 2\n 3'",
value: map[interface{}]interface{}{"1": " 1 2 3"},
},
{
source: "'1': '\n 2\n 3'",
value: map[interface{}]interface{}{"1": " 2 3"},
},
// Double Quoted values.
{
source: `"1": "2"`,
value: map[interface{}]interface{}{"1": `2`},
},
{
source: `"1": "\"2\""`,
value: map[interface{}]interface{}{"1": `"2"`},
},
{
source: `"1": "\""`,
value: map[interface{}]interface{}{"1": `"`},
},
{
source: `"1": "X\"z"`,
value: map[interface{}]interface{}{"1": `X"z`},
},
{
source: `"1": "\\"`,
value: map[interface{}]interface{}{"1": `\`},
},
{
source: `"1": "\\\\"`,
value: map[interface{}]interface{}{"1": `\\`},
},
{
source: `"1": "\\\"2\\\""`,
value: map[interface{}]interface{}{"1": `\"2\"`},
},
{
source: "'1': \" 1\n 2\n 3\"",
value: map[interface{}]interface{}{"1": " 1 2 3"},
},
{
source: "'1': \"\n 2\n 3\"",
value: map[interface{}]interface{}{"1": " 2 3"},
},
{
source: `"1": "a\x2Fb"`,
value: map[interface{}]interface{}{"1": `a/b`},
},
{
source: `"1": "a\u002Fb"`,
value: map[interface{}]interface{}{"1": `a/b`},
},
{
source: `"1": "a\x2Fb\u002Fc\U0000002Fd"`,
value: map[interface{}]interface{}{"1": `a/b/c/d`},
},
{
source: "'1': \"2\\n3\"",
value: map[interface{}]interface{}{"1": "2\n3"},
},
{
source: "'1': \"2\\r\\n3\"",
value: map[interface{}]interface{}{"1": "2\r\n3"},
},
{
source: "'1': \"a\\\nb\\\nc\"",
value: map[interface{}]interface{}{"1": "abc"},
},
{
source: "'1': \"a\\\r\nb\\\r\nc\"",
value: map[interface{}]interface{}{"1": "abc"},
},
{
source: "'1': \"a\\\rb\\\rc\"",
value: map[interface{}]interface{}{"1": "abc"},
},
{
source: "a: -b_c",
value: map[string]interface{}{"a": "-b_c"},
},
{
source: "a: +b_c",
value: map[string]interface{}{"a": "+b_c"},
},
{
source: "a: 50cent_of_dollar",
value: map[string]interface{}{"a": "50cent_of_dollar"},
},
// Nulls
{
source: "null",
value: (*struct{})(nil),
},
{
source: "~",
value: (*struct{})(nil),
},
{
source: "v:",
value: map[string]interface{}{"v": nil},
},
{
source: "v: ~",
value: map[string]interface{}{"v": nil},
},
{
source: "~: null key",
value: map[interface{}]string{nil: "null key"},
},
{
source: "v:",
value: map[string]*bool{"v": nil},
},
{
source: "v: null",
value: map[string]*string{"v": nil},
},
{
source: "v: null",
value: map[string]string{"v": ""},
},
{
source: "v: null",
value: map[string]interface{}{"v": nil},
},
{
source: "v: Null",
value: map[string]interface{}{"v": nil},
},
{
source: "v: NULL",
value: map[string]interface{}{"v": nil},
},
{
source: "v: ~",
value: map[string]*string{"v": nil},
},
{
source: "v: ~",
value: map[string]string{"v": ""},
},
{
source: "v: .inf\n",
value: map[string]interface{}{"v": math.Inf(0)},
},
{
source: "v: .Inf\n",
value: map[string]interface{}{"v": math.Inf(0)},
},
{
source: "v: .INF\n",
value: map[string]interface{}{"v": math.Inf(0)},
},
{
source: "v: -.inf\n",
value: map[string]interface{}{"v": math.Inf(-1)},
},
{
source: "v: -.Inf\n",
value: map[string]interface{}{"v": math.Inf(-1)},
},
{
source: "v: -.INF\n",
value: map[string]interface{}{"v": math.Inf(-1)},
},
{
source: "v: .nan\n",
value: map[string]interface{}{"v": math.NaN()},
},
{
source: "v: .NaN\n",
value: map[string]interface{}{"v": math.NaN()},
},
{
source: "v: .NAN\n",
value: map[string]interface{}{"v": math.NaN()},
},
// Explicit tags.
{
source: "v: !!float '1.1'",
value: map[string]interface{}{"v": 1.1},
},
{
source: "v: !!float 0",
value: map[string]interface{}{"v": float64(0)},
},
{
source: "v: !!float -1",
value: map[string]interface{}{"v": float64(-1)},
},
{
source: "v: !!null ''",
value: map[string]interface{}{"v": nil},
},
{
source: "v: !!timestamp \"2015-01-01\"",
value: map[string]time.Time{"v": time.Date(2015, 1, 1, 0, 0, 0, 0, time.UTC)},
},
{
source: "v: !!timestamp 2015-01-01",
value: map[string]time.Time{"v": time.Date(2015, 1, 1, 0, 0, 0, 0, time.UTC)},
},
{
source: "v: !!bool yes",
value: map[string]bool{"v": true},
},
{
source: "v: !!bool False",
value: map[string]bool{"v": false},
},
{
source: `
!!merge <<: { a: 1, b: 2 }
c: 3
`,
value: map[string]any{"a": 1, "b": 2, "c": 3},
},
// merge
{
source: `
a: &a
foo: 1
b: &b
bar: 2
merge:
<<: [*a, *b]
`,
value: map[string]map[string]any{
"a": {"foo": 1},
"b": {"bar": 2},
"merge": {"foo": 1, "bar": 2},
},
},
{
source: `
a: &a
foo: 1
b: &b
bar: 2
merge:
<<: [*a, *b]
`,
value: map[string]yaml.MapSlice{
"a": {{Key: "foo", Value: 1}},
"b": {{Key: "bar", Value: 2}},
"merge": {{Key: "foo", Value: 1}, {Key: "bar", Value: 2}},
},
},
// Flow sequence
{
source: "v: [A,B]",
value: map[string]interface{}{"v": []interface{}{"A", "B"}},
},
{
source: "v: [A,B,C,]",
value: map[string][]string{"v": {"A", "B", "C"}},
},
{
source: "v: [A,1,C]",
value: map[string][]string{"v": {"A", "1", "C"}},
},
{
source: "v: [A,1,C]",
value: map[string]interface{}{"v": []interface{}{"A", 1, "C"}},
},
{
source: "v: [a: b, c: d]",
value: map[string]any{"v": []any{
map[string]any{"a": "b"},
map[string]any{"c": "d"},
}},
},
{
source: "v: [{a: b}, {c: d, e: f}]",
value: map[string]any{"v": []any{
map[string]any{"a": "b"},
map[string]any{
"c": "d",
"e": "f",
},
}},
},
// Block sequence
{
source: "v:\n - A\n - B",
value: map[string]interface{}{"v": []interface{}{"A", "B"}},
},
{
source: "v:\n - A\n - B\n - C",
value: map[string][]string{"v": {"A", "B", "C"}},
},
{
source: "v:\n - A\n - 1\n - C",
value: map[string][]string{"v": {"A", "1", "C"}},
},
{
source: "v:\n - A\n - 1\n - C",
value: map[string]interface{}{"v": []interface{}{"A", 1, "C"}},
},
// Map inside interface with no type hints.
{
source: "a: {b: c}",
value: map[interface{}]interface{}{"a": map[interface{}]interface{}{"b": "c"}},
},
{
source: "v: \"\"\n",
value: map[string]string{"v": ""},
},
{
source: "v:\n- A\n- B\n",
value: map[string][]string{"v": {"A", "B"}},
},
{
source: "a: '-'\n",
value: map[string]string{"a": "-"},
},
{
source: "123\n",
value: 123,
},
{
source: "hello: world\n",
value: map[string]string{"hello": "world"},
},
{
source: "hello: world\r\n",
value: map[string]string{"hello": "world"},
},
{
source: "hello: world\rGo: Gopher",
value: map[string]string{"hello": "world", "Go": "Gopher"},
},
// Structs and type conversions.
{
source: "hello: world",
value: struct{ Hello string }{"world"},
},
{
source: "a: {b: c}",
value: struct{ A struct{ B string } }{struct{ B string }{"c"}},
},
{
source: "a: {b: c}",
value: struct{ A map[string]string }{map[string]string{"b": "c"}},
},
{
source: "a:",
value: struct{ A map[string]string }{},
},
{
source: "a: 1",
value: struct{ A int }{1},
},
{
source: "a: 1",
value: struct{ A float64 }{1},
},
{
source: "a: 1.0",
value: struct{ A int }{1},
},
{
source: "a: 1.0",
value: struct{ A uint }{1},
},
{
source: "a: [1, 2]",
value: struct{ A []int }{[]int{1, 2}},
},
{
source: "a: [1, 2]",
value: struct{ A [2]int }{[2]int{1, 2}},
},
{
source: "a: 1",
value: struct{ B int }{0},
},
{
source: "a: 1",
value: struct {
B int `yaml:"a"`
}{1},
},
{
source: "a: 1\n",
value: yaml.MapItem{Key: "a", Value: 1},
},
{
source: "a: 1\nb: 2\nc: 3\n",
value: yaml.MapSlice{
{Key: "a", Value: 1},
{Key: "b", Value: 2},
{Key: "c", Value: 3},
},
},
{
source: "v:\n- A\n- 1\n- B:\n - 2\n - 3\n",
value: map[string]interface{}{
"v": []interface{}{
"A",
1,
map[string][]int{
"B": {2, 3},
},
},
},
},
{
source: "a:\n b: c\n",
value: map[string]interface{}{
"a": map[string]string{
"b": "c",
},
},
},
{
source: "a: {x: 1}\n",
value: map[string]map[string]int{
"a": {
"x": 1,
},
},
},
{
source: "t2: 2018-01-09T10:40:47Z\nt4: 2098-01-09T10:40:47Z\n",
value: map[string]string{
"t2": "2018-01-09T10:40:47Z",
"t4": "2098-01-09T10:40:47Z",
},
},
{
source: "a: [1, 2]\n",
value: map[string][]int{
"a": {1, 2},
},
},
{
source: "a: {b: c, d: e}\n",
value: map[string]interface{}{
"a": map[string]string{
"b": "c",
"d": "e",
},
},
},
{
source: "a: 3s\n",
value: map[string]string{
"a": "3s",
},
},
{
source: "a: \n",
value: map[string]string{"a": ""},
},
{
source: "a: \"1:1\"\n",
value: map[string]string{"a": "1:1"},
},
{
source: "a: 1.2.3.4\n",
value: map[string]string{"a": "1.2.3.4"},
},
{
source: "a: 'b: c'\n",
value: map[string]string{"a": "b: c"},
},
{
source: "a: 'Hello #comment'\n",
value: map[string]string{"a": "Hello #comment"},
},
{
source: "a: 100.5\n",
value: map[string]interface{}{
"a": 100.5,
},
},
{
source: "a: \"\\0\"\n",
value: map[string]string{"a": "\x00"},
},
{
source: "b: 2\na: 1\nd: 4\nc: 3\nsub:\n e: 5\n",
value: map[string]interface{}{
"b": 2,
"a": 1,
"d": 4,
"c": 3,
"sub": map[string]int{
"e": 5,
},
},
},
{
source: " a : b \n",
value: map[string]string{"a": "b"},
},
{
source: "a: b # comment\nb: c\n",
value: map[string]string{
"a": "b",
"b": "c",
},
},
{
source: "---\na: b\n",
value: map[string]string{"a": "b"},
},
{
source: "a: b\n...\n",
value: map[string]string{"a": "b"},
},
{
source: "%YAML 1.2\n---\n",
value: (*struct{})(nil),
eof: true,
},
{
source: "---\n",
value: (*struct{})(nil),
eof: true,
},
{
source: "...",
value: (*struct{})(nil),
eof: true,
},
{
source: "v: go test ./...",
value: map[string]string{"v": "go test ./..."},
},
{
source: "v: echo ---",
value: map[string]string{"v": "echo ---"},
},
{
source: "v: |\n hello\n ...\n world\n",
value: map[string]string{"v": "hello\n...\nworld\n"},
},
{
source: "a: !!binary gIGC\n",
value: map[string]string{"a": "\x80\x81\x82"},
},
{
source: "a: !!binary |\n " + strings.Repeat("kJCQ", 17) + "kJ\n CQ\n",
value: map[string]string{"a": strings.Repeat("\x90", 54)},
},
{
source: "v:\n- A\n- |-\n B\n C\n",
value: map[string][]string{
"v": {
"A", "B\nC",
},
},
},
{
source: "v:\n- A\n- |-\n B\n C\n\n\n",
value: map[string][]string{
"v": {
"A", "B\nC",
},
},
},
{
source: "v:\n- A\n- >-\n B\n C\n",
value: map[string][]string{
"v": {
"A", "B C",
},
},
},
{
source: "v:\n- A\n- >-\n B\n C\n\n\n",
value: map[string][]string{
"v": {
"A", "B C",
},
},
},
{
source: "a: b\nc: d\n",
value: struct {
A string
C string `yaml:"c"`
}{
"b", "d",
},
},
{
source: "a: 1\nb: 2\n",
value: struct {
A int
B int `yaml:"-"`
}{
1, 0,
},
},
{
source: "a: 1\nb: 2\n",
value: struct {
A int
Child `yaml:",inline"`
}{
1,
Child{
B: 2,
C: 0,
},
},
},
// Anchors and aliases.
{
source: "a: &x 1\nb: &y 2\nc: *x\nd: *y\n",
value: struct{ A, B, C, D int }{1, 2, 1, 2},
},
{
source: "a: &a {c: 1}\nb: *a\n",
value: struct {
A, B struct {
C int
}
}{struct{ C int }{1}, struct{ C int }{1}},
},
{
source: "a: &a [1, 2]\nb: *a\n",
value: struct{ B []int }{[]int{1, 2}},
},
{
source: "key1: &anchor\n subkey: *anchor\nkey2: *anchor\n",
value: map[string]any{
"key1": map[string]any{
"subkey": nil,
},
"key2": map[string]any{
"subkey": nil,
},
},
},
{
source: `{a: &a c, *a : b}`,
value: map[string]string{"a": "c", "c": "b"},
},
{
source: "tags:\n- hello-world\na: foo",
value: struct {
Tags []string
A string
}{Tags: []string{"hello-world"}, A: "foo"},
},
{
source: "",
value: (*struct{})(nil),
eof: true,
},
{
source: "{}",
value: struct{}{},
},
{
source: "{a: , b: c}",
value: map[string]any{"a": nil, "b": "c"},
},
{
source: "v: /a/{b}",
value: map[string]string{"v": "/a/{b}"},
},
{
source: "v: 1[]{},!%?&*",
value: map[string]string{"v": "1[]{},!%?&*"},
},
{
source: "v: user's item",
value: map[string]string{"v": "user's item"},
},
{
source: "v: [1,[2,[3,[4,5],6],7],8]",
value: map[string]interface{}{
"v": []interface{}{
1,
[]interface{}{
2,
[]interface{}{
3,
[]int{4, 5},
6,
},
7,
},
8,
},
},
},
{
source: "v: {a: {b: {c: {d: e},f: g},h: i},j: k}",
value: 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",
},
},
},
{
source: `---
- a:
b:
- c: d
`,
value: []map[string]interface{}{
{
"a": map[string]interface{}{
"b": nil,
},
},
{
"c": "d",
},
},
},
{
source: `---
a:
b:
c: d
`,
value: map[string]interface{}{
"a": map[string]interface{}{
"b": nil,
},
"c": "d",
},
},
{
source: `---
a:
b:
c:
`,
value: map[string]interface{}{
"a": nil,
"b": nil,
"c": nil,
},
},
{
source: `---
a: go test ./...
b:
c:
`,
value: map[string]interface{}{
"a": "go test ./...",
"b": nil,
"c": nil,
},
},
{
source: `---
a: |
hello
...
world
b:
c:
`,
value: map[string]interface{}{
"a": "hello\n...\nworld\n",
"b": nil,
"c": nil,
},
},
// Multi bytes
{
source: "v: あいうえお\nv2: かきくけこ",
value: map[string]string{"v": "あいうえお", "v2": "かきくけこ"},
},
{
source: `
- "Fun with \\"
- "\" \a \b \e \f"
- "\n \r \t \v \0"
- "\ \_ \N \L \P \
\x41 \u0041 \U00000041"
`,
value: []string{"Fun with \\", "\" \u0007 \b \u001b \f", "\n \r \t \u000b \u0000", "\u0020 \u00a0 \u0085 \u2028 \u2029 A A A"},
},
{
source: `"\ud83e\udd23"`,
value: "🤣",
},
{
source: `"\uD83D\uDE00\uD83D\uDE01"`,
value: "😀😁",
},
{
source: `"\uD83D\uDE00a\uD83D\uDE01"`,
value: "😀a😁",
},
}
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 test.eof && err == io.EOF {
return
}
t.Fatalf("%s: %+v", test.source, err)
}
if test.eof {
t.Fatal("expected EOF but got no error")
}
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_Invalid(t *testing.T) {
tests := []struct {
src string
expect string
}{
{
"*-0",
`
[1:2] could not find alias "-0"
> 1 | *-0
^
`,
},
}
for _, test := range tests {
t.Run(test.src, func(t *testing.T) {
var v any
err := yaml.Unmarshal([]byte(test.src), &v)
if err == nil {
t.Fatal("cannot catch decode error")
}
actual := "\n" + err.Error()
if test.expect != actual {
t.Fatalf("expected: [%s] but got [%s]", test.expect, actual)
}
})
}
}
func TestDecoder_ScientificNotation(t *testing.T) {
tests := []struct {
source string
value interface{}
}{
{
"v: 1e3",
map[string]uint{"v": 1000},
},
{
"v: 1e-3",
map[string]uint{"v": 0},
},
{
"v: 1e3",
map[string]int{"v": 1000},
},
{
"v: 1e-3",
map[string]int{"v": 0},
},
{
"v: 1e3",
map[string]float32{"v": 1000},
},
{
"v: 1.0e3",
map[string]float64{"v": 1000},
},
{
"v: 1e-3",
map[string]float64{"v": 0.001},
},
{
"v: 1.0e-3",
map[string]float64{"v": 0.001},
},
{
"v: 1.0e+3",
map[string]float64{"v": 1000},
},
{
"v: 1.0e+3",
map[string]float64{"v": 1000},
},
}
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 !strings.Contains(err.Error(), msg) {
t.Fatalf("expected error message: %s to contain: %s", err.Error(), msg)
}
})
t.Run("string to uint", func(t *testing.T) {
var v T
err := yaml.Unmarshal([]byte(`b: str`), &v)
if err == nil {
t.Fatal("expected to error")
}
msg := "cannot unmarshal string into Go struct field T.B of type uint"
if !strings.Contains(err.Error(), msg) {
t.Fatalf("expected error message: %s to contain: %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 !strings.Contains(err.Error(), msg) {
t.Fatalf("expected error message: %s to contain: %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 !strings.Contains(err.Error(), msg) {
t.Fatalf("expected error message: %s to contain: %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 !strings.Contains(err.Error(), msg) {
t.Fatalf("expected error message: %s to contain: %s", err.Error(), msg)
}
})
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 !strings.Contains(err.Error(), msg) {
t.Fatalf("expected error message: %s to contain: %s", err.Error(), msg)
}
})
})
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 !strings.Contains(err.Error(), msg) {
t.Fatalf("expected error message: %s to contain: %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 !strings.Contains(err.Error(), msg) {
t.Fatalf("expected error message: %s to contain: %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 !strings.Contains(err.Error(), msg) {
t.Fatalf("expected error message: %s to contain: %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 !strings.Contains(err.Error(), msg) {
t.Fatalf("expected error message: %s to contain: %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 !strings.Contains(err.Error(), msg) {
t.Fatalf("expected error message: %s to contain: %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 !strings.Contains(err.Error(), msg) {
t.Fatalf("expected error message: %s to contain: %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 !strings.Contains(err.Error(), msg) {
t.Fatalf("expected error message: %s to contain: %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.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, yaml.AllowDuplicateMapKey())
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_AllowDuplicateMapKey(t *testing.T) {
yml := `
a: b
a: c
`
t.Run("map", func(t *testing.T) {
var v map[string]string
if err := yaml.NewDecoder(strings.NewReader(yml), yaml.AllowDuplicateMapKey()).Decode(&v); err != nil {
t.Fatal(err)
}
})
t.Run("struct", func(t *testing.T) {
var v struct {
A string
}
if err := yaml.NewDecoder(strings.NewReader(yml), yaml.AllowDuplicateMapKey()).Decode(&v); err != nil {
t.Fatal(err)
}
})
}
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 ExampleUnmarshal_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)
}
}
func TestDecoder_CustomUnmarshaler(t *testing.T) {
t.Run("override struct type", func(t *testing.T) {
type T struct {
Foo string `yaml:"foo"`
}
src := []byte(`foo: "bar"`)
var v T
if err := yaml.UnmarshalWithOptions(src, &v, yaml.CustomUnmarshaler[T](func(dst *T, b []byte) error {
if !bytes.Equal(src, b) {
t.Fatalf("failed to get decode target buffer. expected %q but got %q", src, b)
}
var v T
if err := yaml.Unmarshal(b, &v); err != nil {
return err
}
if v.Foo != "bar" {
t.Fatal("failed to decode")
}
dst.Foo = "bazbaz" // assign another value to target
return nil
})); err != nil {
t.Fatal(err)
}
if v.Foo != "bazbaz" {
t.Fatalf("failed to switch to custom unmarshaler. got: %v", v.Foo)
}
})
t.Run("override bytes type", func(t *testing.T) {
type T struct {
Foo []byte `yaml:"foo"`
}
src := []byte(`foo: "bar"`)
var v T
if err := yaml.UnmarshalWithOptions(src, &v, yaml.CustomUnmarshaler[[]byte](func(dst *[]byte, b []byte) error {
if !bytes.Equal(b, []byte(`"bar"`)) {
t.Fatalf("failed to get target buffer: %q", b)
}
*dst = []byte("bazbaz")
return nil
})); err != nil {
t.Fatal(err)
}
if !bytes.Equal(v.Foo, []byte("bazbaz")) {
t.Fatalf("failed to switch to custom unmarshaler. got: %q", v.Foo)
}
})
t.Run("override bytes type with context", func(t *testing.T) {
type T struct {
Foo []byte `yaml:"foo"`
}
src := []byte(`foo: "bar"`)
var v T
ctx := context.WithValue(context.Background(), "plop", uint(42))
if err := yaml.UnmarshalContext(ctx, src, &v, yaml.CustomUnmarshalerContext[[]byte](func(ctx context.Context, dst *[]byte, b []byte) error {
if !bytes.Equal(b, []byte(`"bar"`)) {
t.Fatalf("failed to get target buffer: %q", b)
}
if ctx.Value("plop") != uint(42) {
t.Fatalf("context value is not correct")
}
*dst = []byte("bazbaz")
return nil
})); err != nil {
t.Fatal(err)
}
if !bytes.Equal(v.Foo, []byte("bazbaz")) {
t.Fatalf("failed to switch to custom unmarshaler. got: %q", v.Foo)
}
})
}
type unmarshalContext struct {
v int
}
func (c *unmarshalContext) UnmarshalYAML(ctx context.Context, b []byte) error {
v, ok := ctx.Value("k").(int)
if !ok {
return errors.New("cannot get valid context")
}
if v != 1 {
return errors.New("cannot get valid context")
}
if string(b) != "1" {
return errors.New("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 !errors.Is(err, yaml.ErrDecodeRequiredPointerType) {
t.Fatalf("unexpected error: %s", err)
}
})
}
func TestCommentWithCustomUnmarshaler(t *testing.T) {
type T struct{}
for idx, test := range []string{
`
foo:
# comment
- a: b
`,
`
foo: # comment
bar: 1
baz: true
`,
} {
t.Run(strconv.Itoa(idx), func(t *testing.T) {
m := yaml.CommentMap{}
var v T
if err := yaml.UnmarshalWithOptions(
[]byte(test),
&v,
yaml.CommentToMap(m),
yaml.CustomUnmarshaler[T](func(dst *T, b []byte) error {
expected := bytes.Trim([]byte(test), "\n")
if !bytes.Equal(b, expected) {
return fmt.Errorf("failed to decode: got\n%s", string(test))
}
return nil
}),
); err != nil {
t.Fatal(err)
}
})
}
}
func ExampleUnmarshal_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 ExampleDecoder_Decode_disallowUnknownField() {
var v struct {
A string `yaml:"simple"`
C string `yaml:"complicated"`
}
const src = `---
simple: string
unknown: string
`
err := yaml.NewDecoder(strings.NewReader(src), yaml.DisallowUnknownField()).Decode(&v)
fmt.Printf("%v\n", err)
// OUTPUT:
// [3:1] unknown field "unknown"
// 1 | ---
// 2 | simple: string
// > 3 | unknown: string
// ^
}
func ExampleNodeToValue() {
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")
}
})
t.Run("quoted map keys", func(t *testing.T) {
t.Parallel()
yml := `
a:
"b" : 2
'c': true
`
var v struct {
A struct {
B int
C bool
}
}
if err := yaml.Unmarshal([]byte(yml), &v); err != nil {
t.Fatalf("failed to unmarshal %v", err)
}
if v.A.B != 2 {
t.Fatalf("expected a.b to equal 2 but was %d", v.A.B)
}
if !v.A.C {
t.Fatal("expected a.c to be true but was false")
}
})
}
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) {
type value struct {
String unmarshalYAMLWithAliasString
Map unmarshalYAMLWithAliasMap
}
tests := []struct {
name string
yaml string
expectedValue value
err string
}{
{
name: "ok",
yaml: `
anchors:
w: &w "\"hello\" \"world\""
map: &x
a: b
c: d
d: *w
string: *w
map:
<<: *x
e: f
`,
expectedValue: value{
String: unmarshalYAMLWithAliasString(`"hello" "world"`),
Map: unmarshalYAMLWithAliasMap(map[string]interface{}{
"a": "b",
"c": "d",
"d": `"hello" "world"`,
"e": "f",
}),
},
},
{
name: "unknown alias",
yaml: `
anchors:
w: &w "\"hello\" \"world\""
map: &x
a: b
c: d
d: *w
string: *y
map:
<<: *z
e: f
`,
err: `could not find alias "y"`,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
var v value
err := yaml.Unmarshal([]byte(test.yaml), &v)
if test.err != "" {
if err == nil {
t.Fatal("expected to error")
}
if !strings.Contains(err.Error(), test.err) {
t.Fatalf("expected error message: %s to contain: %s", err.Error(), test.err)
}
} else {
if err != nil {
t.Fatalf("%+v", err)
}
if !reflect.DeepEqual(test.expectedValue, v) {
t.Fatalf("non matching values:\nexpected[%s]\ngot [%s]", test.expectedValue, v)
}
}
})
}
}
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 # comment
# comment
d: | # comment
hello
hello
f: g
- h: i`
actual := "\n" + string(b)
if expected != actual {
return fmt.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_DecodeWithAnchorAnyValue(t *testing.T) {
type Config struct {
Env []string `json:"env"`
}
type Schema struct {
Def map[string]any `json:"def"`
Config Config `json:"config"`
}
data := `
def:
myenv: &my_env
- VAR1=1
- VAR2=2
config:
env: *my_env
`
var cfg Schema
if err := yaml.NewDecoder(strings.NewReader(data)).Decode(&cfg); err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(cfg.Config.Env, []string{"VAR1=1", "VAR2=2"}) {
t.Fatalf("failed to decode value. actual = %+v", cfg)
}
}
func TestDecoder_UnmarshalBytesWithSeparatedList(t *testing.T) {
yml := `
a:
- b: c # comment
# comment
d: | # comment
hello
hello
f: g
- h: i
`
var v struct {
A unmarshalList
}
cm := yaml.CommentMap{}
if err := yaml.UnmarshalWithOptions([]byte(yml), &v, yaml.CommentToMap(cm)); 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{
{
Node: "hello\nworld",
},
{
Node: "hello\nworld\n",
},
{
LastNode: "hello\nworld",
},
{
LastNode: "hello\nworld\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)
}
})
}
func TestDecodeWithSameAnchor(t *testing.T) {
yml := `
a: &a 1
b: &a 2
c: &a 3
d: *a
`
type T struct {
A int
B int
C int
D int
}
var v T
if err := yaml.Unmarshal([]byte(yml), &v); err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(v, T{A: 1, B: 2, C: 3, D: 3}) {
t.Fatalf("failed to decode same anchor: %+v", v)
}
}
func TestUnmarshalMapSliceParallel(t *testing.T) {
content := `
steps:
req0:
desc: Get /users/1
req:
/users/1:
get: nil
test: |
current.res.status == 200
req1:
desc: Get /private
req:
/private:
get: nil
test: |
current.res.status == 403
req2:
desc: Get /users
req:
/users:
get: nil
test: |
current.res.status == 200
`
type mappedSteps struct {
Steps yaml.MapSlice `yaml:"steps,omitempty"`
}
for i := 0; i < 100; i++ {
t.Run(fmt.Sprintf("i=%d", i), func(t *testing.T) {
t.Parallel()
for i := 0; i < 10; i++ {
m := mappedSteps{
Steps: yaml.MapSlice{},
}
if err := yaml.Unmarshal([]byte(content), &m); err != nil {
t.Fatal(err)
}
for _, s := range m.Steps {
_, ok := s.Value.(map[string]interface{})
if !ok {
t.Fatal("unexpected error")
}
}
}
})
}
}
func TestSameNameInineStruct(t *testing.T) {
type X struct {
X float64 `yaml:"x"`
}
type T struct {
X `yaml:",inline"`
}
var v T
if err := yaml.Unmarshal([]byte(`x: 0.7`), &v); err != nil {
t.Fatal(err)
}
if fmt.Sprint(v.X.X) != "0.7" {
t.Fatalf("failed to decode")
}
}
type unmarshableMapKey struct {
Key string
}
func (mk *unmarshableMapKey) UnmarshalYAML(b []byte) error {
mk.Key = string(b)
return nil
}
type testNodeUnmarshalerCtx struct {
outErr error
received ast.Node
}
func (u *testNodeUnmarshalerCtx) UnmarshalYAML(ctx context.Context, node ast.Node) error {
if u.outErr != nil {
return u.outErr
}
if ctx == nil {
return errors.New("nil context")
}
u.received = node
return nil
}
func TestNodeUnmarshalerContext(t *testing.T) {
type testNodeUnmarshalerBody struct {
Root testNodeUnmarshalerCtx `yaml:"root"`
}
cases := []struct {
name string
expectErr string
src []string
body testNodeUnmarshalerBody
}{
{
name: "should pass node",
src: []string{
"root:",
" foo: bar",
" fizz: buzz",
},
},
{
name: "should pass returned error",
body: testNodeUnmarshalerBody{
Root: testNodeUnmarshalerCtx{
outErr: errors.New("test error"),
},
},
expectErr: "test error",
src: []string{
"root:",
" foo: bar",
" fizz: buzz",
},
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
src := []byte(strings.Join(c.src, "\n"))
out := c.body
err := yaml.Unmarshal(src, &out)
if c.expectErr != "" {
if err == nil {
t.Fatal("expected error but got nil")
return
}
if !strings.Contains(err.Error(), c.expectErr) {
t.Fatalf("error message %q should contain %q", err.Error(), c.expectErr)
}
return
}
expect := struct {
Root ast.Node `yaml:"root"`
}{}
if err := yaml.UnmarshalContext(context.TODO(), src, &expect); err != nil {
t.Fatal("invalid test yaml:", err)
return
}
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
if !reflect.DeepEqual(out.Root.received, expect.Root) {
t.Fatalf("expected:\n%#v\n but got:\n%#v", expect.Root, out.Root.received)
}
})
}
}
type testNodeUnmarshaler struct {
outErr error
received ast.Node
}
func (u *testNodeUnmarshaler) UnmarshalYAML(node ast.Node) error {
if u.outErr != nil {
return u.outErr
}
u.received = node
return nil
}
func TestNodeUnmarshaler(t *testing.T) {
type testNodeUnmarshalerBody struct {
Root testNodeUnmarshaler `yaml:"root"`
}
cases := []struct {
name string
expectErr string
src []string
body testNodeUnmarshalerBody
}{
{
name: "should pass node",
src: []string{
"root:",
" foo: bar",
" fizz: buzz",
},
},
{
name: "should pass returned error",
body: testNodeUnmarshalerBody{
Root: testNodeUnmarshaler{
outErr: errors.New("test error"),
},
},
expectErr: "test error",
src: []string{
"root:",
" foo: bar",
" fizz: buzz",
},
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
src := []byte(strings.Join(c.src, "\n"))
out := c.body
err := yaml.Unmarshal(src, &out)
if c.expectErr != "" {
if err == nil {
t.Fatal("expected error but got nil")
return
}
if !strings.Contains(err.Error(), c.expectErr) {
t.Fatalf("error message %q should contain %q", err.Error(), c.expectErr)
}
return
}
expect := struct {
Root ast.Node `yaml:"root"`
}{}
if err := yaml.Unmarshal(src, &expect); err != nil {
t.Fatal("invalid test yaml:", err)
return
}
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
if !reflect.DeepEqual(out.Root.received, expect.Root) {
t.Fatalf("expected:\n%#v\n but got:\n%#v", expect.Root, out.Root.received)
}
})
}
}
func TestMapKeyCustomUnmarshaler(t *testing.T) {
var m map[unmarshableMapKey]string
if err := yaml.Unmarshal([]byte(`key: value`), &m); err != nil {
t.Fatalf("failed to unmarshal %v", err)
}
if len(m) != 1 {
t.Fatalf("expected 1 element in map, but got %d", len(m))
}
val, ok := m[unmarshableMapKey{Key: "key"}]
if !ok {
t.Fatal("expected to have element 'key' in map")
}
if val != "value" {
t.Fatalf("expected to have value \"value\", but got %q", val)
}
}
type bytesUnmershalerWithMapAlias struct{}
func (*bytesUnmershalerWithMapAlias) UnmarshalYAML(b []byte) error {
expected := strings.TrimPrefix(`
aaaaa:
bbbbb:
bar:
- |
foo
bar
- name: |
foo
bar
`, "\n")
if string(b) != expected {
return fmt.Errorf("failed to decode: expected:\n[%s]\nbut got:\n[%s]\n", expected, string(b))
}
return nil
}
func TestBytesUnmarshalerWithMapAlias(t *testing.T) {
yml := `
x-foo: &data
bar:
- |
foo
bar
- name: |
foo
bar
foo:
aaaaa:
bbbbb: *data
`
type T struct {
Foo bytesUnmershalerWithMapAlias `yaml:"foo"`
}
var v T
if err := yaml.Unmarshal([]byte(yml), &v); err != nil {
t.Fatal(err)
}
}
func TestBytesUnmarshalerWithEmptyValue(t *testing.T) {
type T struct{}
unmarshaler := func(dst *T, b []byte) error {
var v any
return yaml.Unmarshal(b, &v)
}
yml := `
map: &m {}
seq: &seq []
foo: # comment
bar: *m
baz: *seq
`
m := yaml.CommentMap{}
var v T
if err := yaml.UnmarshalWithOptions(
[]byte(yml),
&v,
yaml.CommentToMap(m),
yaml.CustomUnmarshaler[T](unmarshaler),
); err != nil {
t.Fatal(err)
}
if err := yaml.UnmarshalWithOptions(
[]byte(yml),
&v,
yaml.CustomUnmarshaler[T](unmarshaler),
); err != nil {
t.Fatal(err)
}
}
func TestIssue650(t *testing.T) {
type Disk struct {
Name string `yaml:"name"`
Format *bool `yaml:"format"`
}
type Sample struct {
Disks []Disk `yaml:"disks"`
}
unmarshalDisk := func(dst *Disk, b []byte) error {
var s string
if err := yaml.Unmarshal(b, &s); err == nil {
*dst = Disk{Name: s}
return nil
}
return yaml.Unmarshal(b, dst)
}
data := []byte(`
disks:
- name: foo
format: true
`)
var sample Sample
if err := yaml.UnmarshalWithOptions(data, &sample, yaml.CustomUnmarshaler[Disk](unmarshalDisk)); err != nil {
t.Fatal(err)
}
}
func TestBytesUnmarshalerWithLiteral(t *testing.T) {
t.Run("map value", func(t *testing.T) {
type Literal string
unmarshalLit := func(dst *Literal, b []byte) error {
var s string
if err := yaml.Unmarshal(b, &s); err != nil {
return err
}
*dst = Literal(s)
return nil
}
data := []byte(`
- name: |
foo
bar
- name:
|
foo
bar
`)
var v []map[string]Literal
if err := yaml.UnmarshalWithOptions(data, &v, yaml.CustomUnmarshaler[Literal](unmarshalLit)); err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(v, []map[string]Literal{{"name": "foo\n bar\n"}, {"name": "foo\nbar\n"}}) {
t.Fatalf("failed to get decoded value. got: %q", v)
}
})
t.Run("sequence value", func(t *testing.T) {
type Literal string
unmarshalLit := func(dst *Literal, b []byte) error {
var s string
if err := yaml.Unmarshal(b, &s); err != nil {
return err
}
*dst = Literal(s)
return nil
}
data := []byte(`
- |
foo
bar
-
|
foo
bar
`)
var v []Literal
if err := yaml.UnmarshalWithOptions(data, &v, yaml.CustomUnmarshaler[Literal](unmarshalLit)); err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(v, []Literal{"foo\n bar\n", "foo\nbar\n"}) {
t.Fatalf("failed to get decoded value. got: %q", v)
}
})
}
func TestDecoderPreservesDefaultValues(t *testing.T) {
type nested struct {
Val string `yaml:"val"`
}
type test struct {
First string `yaml:"first"`
Default nested `yaml:"nested"`
}
yml := `
first: "Test"
nested:
# Just some comment here
# val: "default"
`
v := test{Default: nested{Val: "default"}}
if err := yaml.Unmarshal([]byte(yml), &v); err != nil {
t.Fatal(err)
}
if v.Default.Val != "default" {
t.Fatal("decoder doesn't preserve struct defaults")
}
}
func TestDecodeError(t *testing.T) {
tests := []struct {
name string
source string
}{
{
name: "duplicated map key name with anchor-alias",
source: "&0: *0\n*0:\n*0:",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
var v any
if err := yaml.Unmarshal([]byte(test.source), &v); err == nil {
t.Fatal("cannot catch decode error")
}
})
}
}
func TestIssue617(t *testing.T) {
data := `
a: !Not [!Equals [!Ref foo, 'bar']]
`
var v map[string][]any
if err := yaml.Unmarshal([]byte(data), &v); err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(v, map[string][]any{
"a": {[]any{"foo", "bar"}},
}) {
t.Fatalf("found unexpected value: %v", v)
}
}
type issue337Template struct{}
func (i *issue337Template) UnmarshalYAML(b []byte) error {
expected := strings.TrimPrefix(`
|
apiVersion: v1
kind: ConfigMap
metadata:
name: "abc"
namespace: "abc"
data:
foo: FOO
`, "\n")
if !bytes.Equal(b, []byte(expected)) {
return fmt.Errorf("expected:\n%s\nbut got:\n%s\n", expected, string(b))
}
return nil
}
func TestIssue337(t *testing.T) {
yml := `
releases:
- name: foo
chart: ./raw
values:
- templates:
- |
apiVersion: v1
kind: ConfigMap
metadata:
name: "abc"
namespace: "abc"
data:
foo: FOO
`
type Value struct {
Templates []*issue337Template `yaml:"templates"`
}
type Release struct {
Values []*Value `yaml:"values"`
}
var v struct {
Releases []*Release `yaml:"releases"`
}
if err := yaml.Unmarshal([]byte(yml), &v); err != nil {
t.Fatal(err)
}
}
func TestSetNullValue(t *testing.T) {
tests := []struct {
name string
src string
}{
{
name: "empty document",
src: "",
},
{
name: "null value",
src: "null",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
t.Run("set null", func(t *testing.T) {
var v any
v = 0x1
if err := yaml.Unmarshal([]byte(test.src), &v); err != nil {
t.Fatal(err)
}
if v != nil {
t.Fatal("failed to set nil value")
}
})
t.Run("invalid value", func(t *testing.T) {
var v *struct{}
if err := yaml.Unmarshal([]byte(test.src), v); err != nil {
t.Fatal(err)
}
if v != nil {
t.Fatal("failed to set nil value")
}
})
})
}
}
goccy-go-yaml-52dacb8/docs/ 0000775 0000000 0000000 00000000000 15015766371 0015616 5 ustar 00root root 0000000 0000000 goccy-go-yaml-52dacb8/docs/playground/ 0000775 0000000 0000000 00000000000 15015766371 0020002 5 ustar 00root root 0000000 0000000 goccy-go-yaml-52dacb8/docs/playground/.gitignore 0000664 0000000 0000000 00000000031 15015766371 0021764 0 ustar 00root root 0000000 0000000 node_modules
*.wasm
dist
goccy-go-yaml-52dacb8/docs/playground/.vite/ 0000775 0000000 0000000 00000000000 15015766371 0021027 5 ustar 00root root 0000000 0000000 goccy-go-yaml-52dacb8/docs/playground/.vite/deps/ 0000775 0000000 0000000 00000000000 15015766371 0021762 5 ustar 00root root 0000000 0000000 goccy-go-yaml-52dacb8/docs/playground/.vite/deps/_metadata.json 0000664 0000000 0000000 00000000222 15015766371 0024570 0 ustar 00root root 0000000 0000000 {
"hash": "a456637e",
"configHash": "358b79f2",
"lockfileHash": "c66b5dca",
"browserHash": "923911c5",
"optimized": {},
"chunks": {}
} goccy-go-yaml-52dacb8/docs/playground/.vite/deps/package.json 0000664 0000000 0000000 00000000027 15015766371 0024247 0 ustar 00root root 0000000 0000000 {
"type": "module"
}
goccy-go-yaml-52dacb8/docs/playground/Makefile 0000664 0000000 0000000 00000000357 15015766371 0021447 0 ustar 00root root 0000000 0000000 .PHONY: build
build: build/wasm build/page
.PHONY: build/wasm
build/wasm:
GOOS=js GOARCH=wasm go build -o src/yaml.wasm ./cmd/yaml
.PHONY: build/page
build/page:
npm run build
.PHONY: deps
deps:
npm ci
.PHONY: dev
dev:
npm run dev
goccy-go-yaml-52dacb8/docs/playground/README.md 0000664 0000000 0000000 00000000365 15015766371 0021265 0 ustar 00root root 0000000 0000000 # Playground
## Architecture
- Vite
- React
- TypeScript
- WebWorker
- WebAssembly
- Built with Go
- Components
- [Monaco Editor](https://microsoft.github.io/monaco-editor/)
- [Xterm.js](https://xtermjs.org/)
- [MUI](https://mui.com/)
goccy-go-yaml-52dacb8/docs/playground/cmd/ 0000775 0000000 0000000 00000000000 15015766371 0020545 5 ustar 00root root 0000000 0000000 goccy-go-yaml-52dacb8/docs/playground/cmd/yaml/ 0000775 0000000 0000000 00000000000 15015766371 0021507 5 ustar 00root root 0000000 0000000 goccy-go-yaml-52dacb8/docs/playground/cmd/yaml/main.go 0000664 0000000 0000000 00000054673 15015766371 0023001 0 ustar 00root root 0000000 0000000 //go:build js && wasm
package main
import (
"bytes"
"context"
"errors"
"fmt"
"io"
"strings"
"syscall/js"
"github.com/goccy/go-graphviz"
"github.com/goccy/go-json"
"github.com/goccy/go-yaml"
"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 response(v any, err error) map[string]any {
if err != nil {
return map[string]any{
"error": err.Error(),
}
}
return map[string]any{
"response": v,
}
}
func decode(this js.Value, args []js.Value) any {
v := args[0].String()
b, err := Decode(v)
if err != nil {
return response(nil, err)
}
return response(string(b), nil)
}
func tokenize(this js.Value, args []js.Value) any {
v := args[0].String()
b, err := Tokenize(v)
if err != nil {
return response(nil, err)
}
return response(string(b), nil)
}
func parseGroup(this js.Value, args []js.Value) any {
v := args[0].String()
b, err := ParseGroup(v)
if err != nil {
return response(nil, err)
}
return response(string(b), nil)
}
func parse(this js.Value, args []js.Value) any {
v := args[0].String()
b, err := Parse(context.Background(), v)
if err != nil {
return response(nil, err)
}
return response(string(b), nil)
}
func Decode(v string) ([]byte, error) {
var ret []string
dec := yaml.NewDecoder(strings.NewReader(v))
for {
var v any
if err := dec.Decode(&v); err != nil {
if err == io.EOF {
break
}
return nil, errors.New(yaml.FormatError(err, true, true))
}
got, err := json.MarshalIndentWithOption(v, "", " ", json.Colorize(json.DefaultColorScheme))
if err != nil {
return nil, err
}
ret = append(ret, string(got))
}
return []byte(strings.Join(ret, "\n")), nil
}
type Token struct {
Type string `json:"type"`
Value string `json:"value"`
Origin string `json:"origin"`
Error string `json:"error"`
Line int `json:"line"`
Column int `json:"column"`
Offset int `json:"offset"`
}
func Tokenize(v string) ([]byte, error) {
tks := lexer.Tokenize(v)
ret := make([]*Token, 0, len(tks))
for _, tk := range tks {
ret = append(ret, toToken(tk))
}
b, err := json.Marshal(ret)
if err != nil {
return nil, err
}
return b, nil
}
func toToken(tk *token.Token) *Token {
if tk == nil {
return nil
}
return &Token{
Type: tk.Type.String(),
Value: tk.Value,
Origin: tk.Origin,
Error: tk.Error,
Line: tk.Position.Line,
Column: tk.Position.Column,
Offset: tk.Position.Offset,
}
}
type GroupedToken struct {
Token *Token `json:"token"`
Group *TokenGroup `json:"group"`
LineComment *Token `json:"lineComment"`
}
type TokenGroup struct {
Type string `json:"type"`
Tokens []*GroupedToken `json:"tokens"`
}
func toGroupedToken(tk *parser.Token) *GroupedToken {
if tk == nil {
return nil
}
return &GroupedToken{
Token: toToken(tk.Token),
Group: toTokenGroup(tk.Group),
LineComment: toToken(tk.LineComment),
}
}
func toTokenGroup(g *parser.TokenGroup) *TokenGroup {
if g == nil {
return nil
}
tokens := make([]*GroupedToken, 0, len(g.Tokens))
for _, tk := range g.Tokens {
tokens = append(tokens, toGroupedToken(tk))
}
return &TokenGroup{
Type: g.Type.String(),
Tokens: tokens,
}
}
func ParseGroup(v string) ([]byte, error) {
tks, err := parser.CreateGroupedTokens(lexer.Tokenize(v))
if err != nil {
return nil, err
}
ret := make([]*GroupedToken, 0, len(tks))
for _, tk := range tks {
ret = append(ret, toGroupedToken(tk))
}
b, err := json.Marshal(ret)
if err != nil {
return nil, err
}
return b, nil
}
func Parse(ctx context.Context, v string) ([]byte, error) {
gv, err := graphviz.New(ctx)
if err != nil {
return nil, err
}
file, err := parser.ParseBytes([]byte(v), parser.ParseComments)
if err != nil {
return nil, err
}
graph, err := gv.Graph()
if err != nil {
return nil, err
}
graph.SetCompound(true)
defer func() {
if err := graph.Close(); err != nil {
panic(err)
}
gv.Close()
}()
renderer := &NodeRenderer{}
if _, err := renderer.renderFile(graph, file); err != nil {
return nil, err
}
var out bytes.Buffer
if err := gv.Render(ctx, graph, graphviz.SVG, &out); err != nil {
return nil, err
}
return out.Bytes(), nil
}
type NodeRenderer struct {
id int
edges []*Edge
}
type Edge struct {
start *Node
end *Node
}
type Node struct {
graphName string
node *graphviz.Node
}
func (r *NodeRenderer) createID() string {
r.id++
return fmt.Sprint(r.id)
}
func (r *NodeRenderer) createNodeGraph(parent *graphviz.Graph, node any, name string) (*graphviz.Graph, error) {
id := r.createID()
sub, err := parent.CreateSubGraphByName("cluster" + id)
if err != nil {
return nil, err
}
sub.SetCompound(true)
sub.SetLabel(name)
sub.SetStyle(graphviz.FilledGraphStyle)
sub.SetBackgroundColor("white")
return sub, nil
}
func (r *NodeRenderer) createNode(graph *graphviz.Graph, name string) (*graphviz.Node, error) {
node, err := graph.CreateNodeByName(r.createID())
if err != nil {
return nil, err
}
node.SetLabel(name)
node.SetStyle(graphviz.FilledNodeStyle)
node.SetFillColor("white")
return node, nil
}
func (r *NodeRenderer) createEdge(fromGraph *graphviz.Graph, fromNode *graphviz.Node, toGraph *graphviz.Graph) error {
to, err := toGraph.FirstNode()
if err != nil {
return err
}
edge, err := fromGraph.CreateEdgeByName("", fromNode, to)
if err != nil {
return err
}
fromGraphName, err := fromGraph.Name()
if err != nil {
return err
}
toGraphName, err := toGraph.Name()
if err != nil {
return err
}
edge.SetLogicalTail(fromGraphName)
edge.SetLogicalHead(toGraphName)
edge.SetMinLen(2)
return nil
}
func (r *NodeRenderer) renderFile(graph *graphviz.Graph, file *ast.File) (*graphviz.Graph, error) {
fileGraph, err := r.createNodeGraph(graph, file, "FileNode")
if err != nil {
return nil, err
}
fileGraph.SetBackgroundColor("ivory")
for idx, doc := range file.Docs {
node, err := r.createNode(fileGraph, fmt.Sprintf("docs[%d]", idx))
if err != nil {
return nil, err
}
docGraph, err := r.renderDocument(fileGraph, doc)
if err != nil {
return nil, err
}
if err := r.createEdge(fileGraph, node, docGraph); err != nil {
return nil, err
}
}
return fileGraph, nil
}
func (r *NodeRenderer) renderDocument(graph *graphviz.Graph, doc *ast.DocumentNode) (*graphviz.Graph, error) {
docGraph, err := r.createNodeGraph(graph, doc, "DocumentNode")
if err != nil {
return nil, err
}
docGraph.SetBackgroundColor("mintcream")
if err := r.renderToken(docGraph, doc.Start); err != nil {
return nil, err
}
if err := r.renderToken(docGraph, doc.End); err != nil {
return nil, err
}
body, err := r.createNode(docGraph, "body")
if err != nil {
return nil, err
}
bodyGraph, err := r.renderNode(docGraph, doc.Body)
if err != nil {
return nil, err
}
if err := r.createEdge(docGraph, body, bodyGraph); err != nil {
return nil, err
}
return docGraph, nil
}
func (r *NodeRenderer) renderNode(graph *graphviz.Graph, node ast.Node) (*graphviz.Graph, error) {
switch n := node.(type) {
case *ast.MappingNode:
return r.renderMappingNode(graph, n)
case *ast.NullNode:
return r.renderNullNode(graph, n)
case *ast.IntegerNode:
return r.renderIntegerNode(graph, n)
case *ast.FloatNode:
return r.renderFloatNode(graph, n)
case *ast.StringNode:
return r.renderStringNode(graph, n)
case *ast.LiteralNode:
return r.renderLiteralNode(graph, n)
case *ast.MergeKeyNode:
return r.renderMergeKeyNode(graph, n)
case *ast.BoolNode:
return r.renderBoolNode(graph, n)
case *ast.InfinityNode:
return r.renderInfinityNode(graph, n)
case *ast.NanNode:
return r.renderNaNNode(graph, n)
case *ast.MappingKeyNode:
return r.renderMappingKeyNode(graph, n)
case *ast.SequenceNode:
return r.renderSequenceNode(graph, n)
case *ast.AnchorNode:
return r.renderAnchorNode(graph, n)
case *ast.AliasNode:
return r.renderAliasNode(graph, n)
case *ast.DirectiveNode:
return r.renderDirectiveNode(graph, n)
case *ast.TagNode:
return r.renderTagNode(graph, n)
case *ast.CommentNode:
case *ast.CommentGroupNode:
}
return nil, fmt.Errorf("unexpected node type %T", node)
}
func (r *NodeRenderer) renderMappingNode(graph *graphviz.Graph, node *ast.MappingNode) (*graphviz.Graph, error) {
mapGraph, err := r.createNodeGraph(graph, node, "MappingNode")
if err != nil {
return nil, err
}
mapGraph.SetBackgroundColor("honeydew")
if err := r.renderPath(mapGraph, node.GetPath()); err != nil {
return nil, err
}
if err := r.renderToken(mapGraph, node.Start); err != nil {
return nil, err
}
if err := r.renderToken(mapGraph, node.End); err != nil {
return nil, err
}
if _, err := r.renderCommentGroupNode(mapGraph, node.GetComment(), yaml.CommentHeadPosition); err != nil {
return nil, err
}
if _, err := r.renderCommentGroupNode(mapGraph, node.FootComment, yaml.CommentFootPosition); err != nil {
return nil, err
}
for idx, value := range node.Values {
node, err := r.createNode(mapGraph, fmt.Sprintf("values[%d]", idx))
if err != nil {
return nil, err
}
valueGraph, err := r.renderMappingValueNode(mapGraph, value)
if err != nil {
return nil, err
}
if err := r.createEdge(mapGraph, node, valueGraph); err != nil {
return nil, err
}
}
return mapGraph, nil
}
func (r *NodeRenderer) renderMappingValueNode(graph *graphviz.Graph, node *ast.MappingValueNode) (*graphviz.Graph, error) {
valueGraph, err := r.createNodeGraph(graph, node, "MappingValueNode")
if err != nil {
return nil, err
}
valueGraph.SetBackgroundColor("seashell")
if err := r.renderPath(valueGraph, node.GetPath()); err != nil {
return nil, err
}
if err := r.renderToken(valueGraph, node.Start); err != nil {
return nil, err
}
if _, err := r.renderCommentGroupNode(valueGraph, node.GetComment(), yaml.CommentHeadPosition); err != nil {
return nil, err
}
if _, err := r.renderCommentGroupNode(valueGraph, node.FootComment, yaml.CommentFootPosition); err != nil {
return nil, err
}
keyNode, err := r.createNode(valueGraph, "key")
if err != nil {
return nil, err
}
valueNode, err := r.createNode(valueGraph, "value")
if err != nil {
return nil, err
}
keyGraph, err := r.renderNode(valueGraph, node.Key)
if err != nil {
return nil, err
}
valueContentGraph, err := r.renderNode(valueGraph, node.Value)
if err != nil {
return nil, err
}
if err := r.createEdge(valueGraph, keyNode, keyGraph); err != nil {
return nil, err
}
if err := r.createEdge(valueGraph, valueNode, valueContentGraph); err != nil {
return nil, err
}
return valueGraph, nil
}
func (r *NodeRenderer) renderStringNode(graph *graphviz.Graph, n *ast.StringNode) (*graphviz.Graph, error) {
subGraph, err := r.createNodeGraph(graph, n, "StringNode")
if err != nil {
return nil, err
}
subGraph.SetBackgroundColor("lavenderblush")
if err := r.renderPath(subGraph, n.GetPath()); err != nil {
return nil, err
}
if err := r.renderToken(subGraph, n.Token); err != nil {
return nil, err
}
if _, err := r.renderCommentGroupNode(subGraph, n.GetComment(), yaml.CommentLinePosition); err != nil {
return nil, err
}
return subGraph, nil
}
func (r *NodeRenderer) renderNullNode(graph *graphviz.Graph, n *ast.NullNode) (*graphviz.Graph, error) {
subGraph, err := r.createNodeGraph(graph, n, "NullNode")
if err != nil {
return nil, err
}
subGraph.SetBackgroundColor("lavenderblush")
if err := r.renderPath(subGraph, n.GetPath()); err != nil {
return nil, err
}
if _, err := r.renderCommentGroupNode(subGraph, n.GetComment(), yaml.CommentLinePosition); err != nil {
return nil, err
}
if _, err := r.createNode(subGraph, "null"); err != nil {
return nil, err
}
return subGraph, nil
}
func (r *NodeRenderer) renderIntegerNode(graph *graphviz.Graph, n *ast.IntegerNode) (*graphviz.Graph, error) {
subGraph, err := r.createNodeGraph(graph, n, "IntegerNode")
if err != nil {
return nil, err
}
subGraph.SetBackgroundColor("lavenderblush")
if err := r.renderPath(subGraph, n.GetPath()); err != nil {
return nil, err
}
if _, err := r.renderCommentGroupNode(subGraph, n.GetComment(), yaml.CommentLinePosition); err != nil {
return nil, err
}
if _, err := r.createNode(subGraph, n.Token.Value); err != nil {
return nil, err
}
return subGraph, nil
}
func (r *NodeRenderer) renderFloatNode(graph *graphviz.Graph, n *ast.FloatNode) (*graphviz.Graph, error) {
subGraph, err := r.createNodeGraph(graph, n, "FloatNode")
if err != nil {
return nil, err
}
subGraph.SetBackgroundColor("lavenderblush")
if err := r.renderPath(subGraph, n.GetPath()); err != nil {
return nil, err
}
if _, err := r.createNode(subGraph, n.Token.Value); err != nil {
return nil, err
}
return subGraph, nil
}
func (r *NodeRenderer) renderLiteralNode(graph *graphviz.Graph, n *ast.LiteralNode) (*graphviz.Graph, error) {
subGraph, err := r.createNodeGraph(graph, n, "LiteralNode")
if err != nil {
return nil, err
}
subGraph.SetBackgroundColor("beige")
if err := r.renderPath(subGraph, n.GetPath()); err != nil {
return nil, err
}
if _, err := r.renderCommentGroupNode(subGraph, n.GetComment(), yaml.CommentLinePosition); err != nil {
return nil, err
}
value, err := r.createNode(subGraph, "value")
if err != nil {
return nil, err
}
strGraph, err := r.renderStringNode(subGraph, n.Value)
if err != nil {
return nil, err
}
if err := r.createEdge(subGraph, value, strGraph); err != nil {
return nil, err
}
return subGraph, nil
}
func (r *NodeRenderer) renderMergeKeyNode(graph *graphviz.Graph, n *ast.MergeKeyNode) (*graphviz.Graph, error) {
subGraph, err := r.createNodeGraph(graph, n, "MergeKeyNode")
if err != nil {
return nil, err
}
subGraph.SetBackgroundColor("cornsilk")
if err := r.renderPath(subGraph, n.GetPath()); err != nil {
return nil, err
}
if _, err := r.renderCommentGroupNode(subGraph, n.GetComment(), yaml.CommentHeadPosition); err != nil {
return nil, err
}
if _, err := r.createNode(subGraph, n.Token.Value); err != nil {
return nil, err
}
return subGraph, nil
}
func (r *NodeRenderer) renderBoolNode(graph *graphviz.Graph, n *ast.BoolNode) (*graphviz.Graph, error) {
subGraph, err := r.createNodeGraph(graph, n, "BoolNode")
if err != nil {
return nil, err
}
subGraph.SetBackgroundColor("lavenderblush")
if err := r.renderPath(subGraph, n.GetPath()); err != nil {
return nil, err
}
if _, err := r.renderCommentGroupNode(subGraph, n.GetComment(), yaml.CommentLinePosition); err != nil {
return nil, err
}
if _, err := r.createNode(subGraph, n.Token.Value); err != nil {
return nil, err
}
return subGraph, nil
}
func (r *NodeRenderer) renderInfinityNode(graph *graphviz.Graph, n *ast.InfinityNode) (*graphviz.Graph, error) {
subGraph, err := r.createNodeGraph(graph, n, "InfinityNode")
if err != nil {
return nil, err
}
subGraph.SetBackgroundColor("lavenderblush")
if err := r.renderPath(subGraph, n.GetPath()); err != nil {
return nil, err
}
if _, err := r.renderCommentGroupNode(subGraph, n.GetComment(), yaml.CommentLinePosition); err != nil {
return nil, err
}
if _, err := r.createNode(subGraph, n.Token.Value); err != nil {
return nil, err
}
return subGraph, nil
}
func (r *NodeRenderer) renderNaNNode(graph *graphviz.Graph, n *ast.NanNode) (*graphviz.Graph, error) {
subGraph, err := r.createNodeGraph(graph, n, "NaNNode")
if err != nil {
return nil, err
}
subGraph.SetBackgroundColor("lavenderblush")
if err := r.renderPath(subGraph, n.GetPath()); err != nil {
return nil, err
}
if _, err := r.renderCommentGroupNode(subGraph, n.GetComment(), yaml.CommentLinePosition); err != nil {
return nil, err
}
if _, err := r.createNode(subGraph, n.Token.Value); err != nil {
return nil, err
}
return subGraph, nil
}
func (r *NodeRenderer) renderMappingKeyNode(graph *graphviz.Graph, n *ast.MappingKeyNode) (*graphviz.Graph, error) {
subGraph, err := r.createNodeGraph(graph, n, "MappingKeyNode")
if err != nil {
return nil, err
}
subGraph.SetBackgroundColor("cornsilk")
if err := r.renderPath(subGraph, n.GetPath()); err != nil {
return nil, err
}
if _, err := r.renderCommentGroupNode(subGraph, n.GetComment(), yaml.CommentHeadPosition); err != nil {
return nil, err
}
value, err := r.createNode(subGraph, "value")
if err != nil {
return nil, err
}
valueGraph, err := r.renderNode(subGraph, n.Value)
if err != nil {
return nil, err
}
if err := r.createEdge(subGraph, value, valueGraph); err != nil {
return nil, err
}
return subGraph, nil
}
func (r *NodeRenderer) renderSequenceNode(graph *graphviz.Graph, n *ast.SequenceNode) (*graphviz.Graph, error) {
subGraph, err := r.createNodeGraph(graph, n, "SequenceNode")
if err != nil {
return nil, err
}
subGraph.SetBackgroundColor("honeydew")
if err := r.renderPath(subGraph, n.GetPath()); err != nil {
return nil, err
}
if err := r.renderToken(subGraph, n.Start); err != nil {
return nil, err
}
if err := r.renderToken(subGraph, n.End); err != nil {
return nil, err
}
for _, head := range n.ValueHeadComments {
if _, err := r.renderCommentGroupNode(subGraph, head, yaml.CommentHeadPosition); err != nil {
return nil, err
}
}
if _, err := r.renderCommentGroupNode(subGraph, n.FootComment, yaml.CommentFootPosition); err != nil {
return nil, err
}
for idx, value := range n.Values {
node, err := r.createNode(subGraph, fmt.Sprintf("values[%d]", idx))
if err != nil {
return nil, err
}
valueGraph, err := r.renderNode(subGraph, value)
if err != nil {
return nil, err
}
if err := r.createEdge(subGraph, node, valueGraph); err != nil {
return nil, err
}
}
return subGraph, nil
}
func (r *NodeRenderer) renderAnchorNode(graph *graphviz.Graph, n *ast.AnchorNode) (*graphviz.Graph, error) {
subGraph, err := r.createNodeGraph(graph, n, "AnchorNode")
if err != nil {
return nil, err
}
subGraph.SetBackgroundColor("oldlace")
if err := r.renderPath(subGraph, n.GetPath()); err != nil {
return nil, err
}
if _, err := r.renderCommentGroupNode(subGraph, n.GetComment(), yaml.CommentLinePosition); err != nil {
return nil, err
}
if err := r.renderToken(subGraph, n.Start); err != nil {
return nil, err
}
name, err := r.createNode(subGraph, "name")
if err != nil {
return nil, err
}
value, err := r.createNode(subGraph, "value")
if err != nil {
return nil, err
}
nameGraph, err := r.renderNode(subGraph, n.Name)
if err != nil {
return nil, err
}
valueGraph, err := r.renderNode(subGraph, n.Value)
if err != nil {
return nil, err
}
if err := r.createEdge(subGraph, name, nameGraph); err != nil {
return nil, err
}
if err := r.createEdge(subGraph, value, valueGraph); err != nil {
return nil, err
}
return subGraph, nil
}
func (r *NodeRenderer) renderAliasNode(graph *graphviz.Graph, n *ast.AliasNode) (*graphviz.Graph, error) {
subGraph, err := r.createNodeGraph(graph, n, "AliasNode")
if err != nil {
return nil, err
}
subGraph.SetBackgroundColor("lavenderblush")
if err := r.renderPath(subGraph, n.GetPath()); err != nil {
return nil, err
}
if _, err := r.renderCommentGroupNode(subGraph, n.GetComment(), yaml.CommentLinePosition); err != nil {
return nil, err
}
if err := r.renderToken(subGraph, n.Start); err != nil {
return nil, err
}
value, err := r.createNode(subGraph, "value")
if err != nil {
return nil, err
}
valueGraph, err := r.renderNode(subGraph, n.Value)
if err != nil {
return nil, err
}
if err := r.createEdge(subGraph, value, valueGraph); err != nil {
return nil, err
}
return subGraph, nil
}
func (r *NodeRenderer) renderDirectiveNode(graph *graphviz.Graph, n *ast.DirectiveNode) (*graphviz.Graph, error) {
subGraph, err := r.createNodeGraph(graph, n, "DirectiveNode")
if err != nil {
return nil, err
}
subGraph.SetBackgroundColor("snow")
if err := r.renderToken(subGraph, n.Start); err != nil {
return nil, err
}
if _, err := r.renderCommentGroupNode(subGraph, n.GetComment(), yaml.CommentLinePosition); err != nil {
return nil, err
}
name, err := r.createNode(subGraph, "name")
if err != nil {
return nil, err
}
nameGraph, err := r.renderNode(subGraph, n.Name)
if err != nil {
return nil, err
}
if err := r.createEdge(subGraph, name, nameGraph); err != nil {
return nil, err
}
for idx, value := range n.Values {
val, err := r.createNode(subGraph, fmt.Sprintf("values[%d]", idx))
if err != nil {
return nil, err
}
valGraph, err := r.renderNode(subGraph, value)
if err != nil {
return nil, err
}
if err := r.createEdge(subGraph, val, valGraph); err != nil {
return nil, err
}
}
return subGraph, nil
}
func (r *NodeRenderer) renderTagNode(graph *graphviz.Graph, n *ast.TagNode) (*graphviz.Graph, error) {
subGraph, err := r.createNodeGraph(graph, n, "TagNode")
if err != nil {
return nil, err
}
subGraph.SetBackgroundColor("ghostwhite")
if err := r.renderPath(subGraph, n.GetPath()); err != nil {
return nil, err
}
if _, err := r.renderCommentGroupNode(subGraph, n.GetComment(), yaml.CommentLinePosition); err != nil {
return nil, err
}
if err := r.renderToken(subGraph, n.Start); err != nil {
return nil, err
}
value, err := r.createNode(subGraph, "value")
if err != nil {
return nil, err
}
valueGraph, err := r.renderNode(subGraph, n.Value)
if err != nil {
return nil, err
}
if err := r.createEdge(subGraph, value, valueGraph); err != nil {
return nil, err
}
return subGraph, nil
}
func (r *NodeRenderer) renderCommentGroupNode(graph *graphviz.Graph, n *ast.CommentGroupNode, pos yaml.CommentPosition) (*graphviz.Graph, error) {
if n == nil {
return nil, nil
}
subGraph, err := r.createNodeGraph(graph, n, fmt.Sprintf("CommentGroupNode (%s)", pos))
if err != nil {
return nil, err
}
subGraph.SetBackgroundColor("whitesmoke")
for _, cm := range n.Comments {
if err := r.renderToken(subGraph, cm.Token); err != nil {
return nil, err
}
}
return subGraph, nil
}
func (r *NodeRenderer) renderPath(graph *graphviz.Graph, p string) error {
node, err := graph.CreateNodeByName(r.createID())
if err != nil {
return err
}
node.SetLabel(fmt.Sprintf("{path|%s}", p))
node.SetShape("record")
node.SetStyle(graphviz.FilledNodeStyle)
node.SetFillColor("white")
return nil
}
func (r *NodeRenderer) renderToken(graph *graphviz.Graph, tk *token.Token) error {
if tk == nil {
return nil
}
node, err := graph.CreateNodeByName(r.createID())
if err != nil {
return err
}
pos := tk.Position
node.SetLabel(fmt.Sprintf("{pos|%d:%d}|{value|%s}", pos.Line, pos.Column, tk.Value))
node.SetShape("record")
node.SetStyle(graphviz.FilledNodeStyle)
node.SetFillColor("white")
return nil
}
func main() {
js.Global().Set("decode", js.FuncOf(decode))
js.Global().Set("tokenize", js.FuncOf(tokenize))
js.Global().Set("parseGroup", js.FuncOf(parseGroup))
js.Global().Set("parse", js.FuncOf(parse))
<-make(chan struct{})
}
goccy-go-yaml-52dacb8/docs/playground/eslint.config.js 0000664 0000000 0000000 00000001336 15015766371 0023105 0 ustar 00root root 0000000 0000000 import js from '@eslint/js'
import globals from 'globals'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import tseslint from 'typescript-eslint'
export default tseslint.config(
{ ignores: ['dist'] },
{
extends: [js.configs.recommended, ...tseslint.configs.recommended],
files: ['**/*.{ts,tsx}'],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
},
plugins: {
'react-hooks': reactHooks,
'react-refresh': reactRefresh,
},
rules: {
...reactHooks.configs.recommended.rules,
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
},
},
)
goccy-go-yaml-52dacb8/docs/playground/go.mod 0000664 0000000 0000000 00000001255 15015766371 0021113 0 ustar 00root root 0000000 0000000 module demo
go 1.22.8
require (
github.com/goccy/go-graphviz v0.2.10-0.20250109095217-4ceff9e58e1a
github.com/goccy/go-json v0.10.4
github.com/goccy/go-yaml v1.15.13
)
require (
github.com/disintegration/imaging v1.6.2 // indirect
github.com/flopp/go-findfont v0.1.0 // indirect
github.com/fogleman/gg v1.3.0 // indirect
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
github.com/tetratelabs/wazero v1.8.1 // indirect
golang.org/x/image v0.21.0 // indirect
golang.org/x/text v0.19.0 // indirect
)
replace github.com/goccy/go-yaml => ../../
replace github.com/flopp/go-findfont => github.com/goccy/go-findfont v0.0.0-20250109093214-c2e12b298c75
goccy-go-yaml-52dacb8/docs/playground/go.sum 0000664 0000000 0000000 00000004362 15015766371 0021142 0 ustar 00root root 0000000 0000000 github.com/corona10/goimagehash v1.1.0 h1:teNMX/1e+Wn/AYSbLHX8mj+mF9r60R1kBeqE9MkoYwI=
github.com/corona10/goimagehash v1.1.0/go.mod h1:VkvE0mLn84L4aF8vCb6mafVajEb6QYMHl2ZJLn0mOGI=
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8=
github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
github.com/goccy/go-findfont v0.0.0-20250109093214-c2e12b298c75 h1:XIuIPArJ/7VuKj7uygrjl7yBYP+sTa58ljJzOxU4qDc=
github.com/goccy/go-findfont v0.0.0-20250109093214-c2e12b298c75/go.mod h1:bBfDkbCgtwEhoHfxProwm41bZX3SAcOHZbgbillrYQs=
github.com/goccy/go-graphviz v0.2.10-0.20250109095217-4ceff9e58e1a h1:xAEcKHIL4BB8ztdnQ8Tr0Bd9Qt/k0Ps6/CHEAlSNhUo=
github.com/goccy/go-graphviz v0.2.10-0.20250109095217-4ceff9e58e1a/go.mod h1:0pcNDbqQkokzppSVae3PfFgsUkta30nkVUFOPGNE9wA=
github.com/goccy/go-json v0.10.4 h1:JSwxQzIqKfmFX1swYPpUThQZp/Ka4wzJdK0LWVytLPM=
github.com/goccy/go-json v0.10.4/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
github.com/tetratelabs/wazero v1.8.1 h1:NrcgVbWfkWvVc4UtT4LRLDf91PsOzDzefMdwhLfA550=
github.com/tetratelabs/wazero v1.8.1/go.mod h1:yAI0XTsMBhREkM/YDAK/zNou3GoiAce1P6+rp/wQhjs=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.21.0 h1:c5qV36ajHpdj4Qi0GnE0jUc/yuo33OLFaa0d+crTD5s=
golang.org/x/image v0.21.0/go.mod h1:vUbsLavqK/W303ZroQQVKQ+Af3Yl6Uz1Ppu5J/cLz78=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
goccy-go-yaml-52dacb8/docs/playground/index.html 0000664 0000000 0000000 00000002160 15015766371 0021776 0 ustar 00root root 0000000 0000000
goccy/go-yaml Playground