build(deps): bump github.com/onsi/ginkgo/v2 from 2.25.3 to 2.26.0

Bumps [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) from 2.25.3 to 2.26.0.
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.25.3...v2.26.0)

---
updated-dependencies:
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-version: 2.26.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
This commit is contained in:
dependabot[bot]
2025-10-02 14:15:13 +00:00
committed by Ralf Haferkamp
parent 4e06b0c376
commit 4c00db867c
56 changed files with 7287 additions and 3379 deletions

5
go.mod
View File

@@ -60,7 +60,7 @@ require (
github.com/oklog/run v1.2.0
github.com/olekukonko/tablewriter v1.1.0
github.com/onsi/ginkgo v1.16.5
github.com/onsi/ginkgo/v2 v2.25.3
github.com/onsi/ginkgo/v2 v2.26.0
github.com/onsi/gomega v1.38.2
github.com/open-policy-agent/opa v1.9.0
github.com/opencloud-eu/icap-client v0.0.0-20250930132611-28a2afe62d89
@@ -228,7 +228,7 @@ require (
github.com/gobwas/pool v0.2.1 // indirect
github.com/gobwas/ws v1.2.1 // indirect
github.com/goccy/go-json v0.10.5 // indirect
github.com/goccy/go-yaml v1.12.0 // indirect
github.com/goccy/go-yaml v1.18.0 // indirect
github.com/gofrs/flock v0.12.1 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
@@ -378,7 +378,6 @@ require (
golang.org/x/sys v0.36.0 // indirect
golang.org/x/time v0.13.0 // indirect
golang.org/x/tools v0.36.0 // indirect
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 // indirect
gopkg.in/cenkalti/backoff.v1 v1.1.0 // indirect

22
go.sum
View File

@@ -357,6 +357,12 @@ github.com/getkin/kin-openapi v0.13.0/go.mod h1:WGRs2ZMM1Q8LR1QBEwUxC6RJEfaBcD0s
github.com/ggwhite/go-masker v1.1.0 h1:kN/KIvktu2U+hd3KWrSlLj7xBGD1iBfc9/xdbVgFbRc=
github.com/ggwhite/go-masker v1.1.0/go.mod h1:xnTRHwrIU9FtBADwEjUC5Dy/BVedvoTxyOE7/d3CNwY=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gkampitakis/ciinfo v0.3.2 h1:JcuOPk8ZU7nZQjdUhctuhQofk7BGHuIy0c9Ez8BNhXs=
github.com/gkampitakis/ciinfo v0.3.2/go.mod h1:1NIwaOcFChN4fa/B0hEBdAb6npDlFL8Bwx4dfRLRqAo=
github.com/gkampitakis/go-diff v1.3.2 h1:Qyn0J9XJSDTgnsgHRdz9Zp24RaJeKMUHg2+PDZZdC4M=
github.com/gkampitakis/go-diff v1.3.2/go.mod h1:LLgOrpqleQe26cte8s36HTWcTmMEur6OPYerdAAS9tk=
github.com/gkampitakis/go-snaps v0.5.14 h1:3fAqdB6BCPKHDMHAKRwtPUwYexKtGrNuw8HX/T/4neo=
github.com/gkampitakis/go-snaps v0.5.14/go.mod h1:HNpx/9GoKisdhw9AFOBT1N7DBs9DiHo/hGheFGBZ+mc=
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
github.com/go-acme/lego/v4 v4.4.0 h1:uHhU5LpOYQOdp3aDU+XY2bajseu8fuExphTL1Ss6/Fc=
@@ -471,8 +477,8 @@ github.com/gobwas/ws v1.2.1 h1:F2aeBZrm2NDsc7vbovKrWSogd4wvfAxg0FQ89/iqOTk=
github.com/gobwas/ws v1.2.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/goccy/go-yaml v1.12.0 h1:/1WHjnMsI1dlIBQutrvSMGZRQufVO3asrHfTwfACoPM=
github.com/goccy/go-yaml v1.12.0/go.mod h1:wKnAMd44+9JAAnGQpWVEgBzGt3YuTaQ4uXoHvE4m7WU=
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E=
github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0=
@@ -687,6 +693,8 @@ github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfC
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I=
github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60=
github.com/joshdk/go-junit v1.0.0 h1:S86cUKIdwBHWwA6xCmFlf3RTLfVXYQfvanM5Uh+K6GE=
github.com/joshdk/go-junit v1.0.0/go.mod h1:TiiV0PqkaNfFXjEiyjWM3XXrhVyCa1K4Zfga6W52ung=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
@@ -780,6 +788,8 @@ github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czP
github.com/magiconair/properties v1.8.4/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE=
github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/maruel/natural v1.1.1 h1:Hja7XhhmvEFhcByqDoHz9QZbkWey+COd9xWfCfn1ioo=
github.com/maruel/natural v1.1.1/go.mod h1:v+Rfd79xlw1AgVBjbO0BEQmptqb5HvL/k9GRHB7ZKEg=
github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ=
github.com/mattermost/xml-roundtrip-validator v0.1.0 h1:RXbVD2UAl7A7nOTR4u7E3ILa4IbtvKBHw64LDsmu9hU=
github.com/mattermost/xml-roundtrip-validator v0.1.0/go.mod h1:qccnGMcpgwcNaBnxqpJpWWUiPNr5H3O8eDgGV9gT5To=
@@ -817,6 +827,8 @@ github.com/maxymania/go-system v0.0.0-20170110133659-647cc364bf0b h1:Q53idHrTuQD
github.com/maxymania/go-system v0.0.0-20170110133659-647cc364bf0b/go.mod h1:KirJrATYGbTyUwVR26xIkaipRqRcMRXBf8N5dacvGus=
github.com/mendsley/gojwk v0.0.0-20141217222730-4d5ec6e58103 h1:Z/i1e+gTZrmcGeZyWckaLfucYG6KYOXLWo4co8pZYNY=
github.com/mendsley/gojwk v0.0.0-20141217222730-4d5ec6e58103/go.mod h1:o9YPB5aGP8ob35Vy6+vyq3P3bWe7NQWzf+JLiXCiMaE=
github.com/mfridman/tparse v0.18.0 h1:wh6dzOKaIwkUGyKgOntDW4liXSo37qg5AXbIhkMV3vE=
github.com/mfridman/tparse v0.18.0/go.mod h1:gEvqZTuCgEhPbYk/2lS3Kcxg1GmTxxU7kTC8DvP0i/A=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/miekg/dns v1.1.40/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
github.com/miekg/dns v1.1.57 h1:Jzi7ApEIzwEPLHWRcafCN9LZSBbqQpxjt/wpgvg7wcM=
@@ -919,8 +931,8 @@ github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
github.com/onsi/ginkgo/v2 v2.25.3 h1:Ty8+Yi/ayDAGtk4XxmmfUy4GabvM+MegeB4cDLRi6nw=
github.com/onsi/ginkgo/v2 v2.25.3/go.mod h1:43uiyQC4Ed2tkOzLsEYm7hnrb7UJTWHYNsuy3bG/snE=
github.com/onsi/ginkgo/v2 v2.26.0 h1:1J4Wut1IlYZNEAWIV3ALrT9NfiaGW2cDCJQSFQMs/gE=
github.com/onsi/ginkgo/v2 v2.26.0/go.mod h1:qhEywmzWTBUY88kfO0BRvX4py7scov9yR+Az2oavUzw=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
@@ -1641,8 +1653,6 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk=
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=

3
vendor/github.com/goccy/go-yaml/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,3 @@
bin/
.idea/
cover.out

65
vendor/github.com/goccy/go-yaml/.golangci.yml generated vendored Normal file
View File

@@ -0,0 +1,65 @@
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$

View File

@@ -1,19 +1,55 @@
## 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 -coverprofile=cover.out ./...
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:
go build -o ycat ./cmd/ycat
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)

View File

@@ -7,27 +7,71 @@
<img width="300px" src="https://user-images.githubusercontent.com/209884/67159116-64d94b80-f37b-11e9-9b28-f8379636a43c.png"></img>
## 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 feel that some features are lacking, namely:
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:
- Pretty format for error notifications
- Direct manipulation of YAML abstract syntax tree
- Support for `Anchor` and `Alias` when marshaling
- Allow referencing elements declared in another file via anchors
- 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
- Supports `Scanner` or `Lexer` or `Parser` as public API
- Supports `Anchor` and `Alias` to Marshaler
- 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
- Extract value or AST by YAMLPath ( YAMLPath is like a JSONPath )
# 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 -u github.com/goccy/go-yaml
go get github.com/goccy/go-yaml
```
# Synopsis
@@ -148,7 +192,9 @@ fmt.Printf("%+v\n", v) // {A:{B:1 C:hello}}
### 3.1. Explicitly declared `Anchor` name and `Alias` name
If you want to use `anchor` or `alias`, you can define it as a struct tag.
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 {
@@ -178,10 +224,7 @@ d: *x
If you do not explicitly declare the anchor name, the default behavior is to
use the equivalent of `strings.ToLower($FieldName)` as the name of the anchor.
If you do not explicitly declare the alias name AND the value is a pointer
to another element, we look up the anchor name by finding out which anchor
field the value is assigned to by looking up its pointer address.
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 {
@@ -191,8 +234,8 @@ type T struct {
var v struct {
A *T `yaml:"a,anchor"`
B *T `yaml:"b,anchor"`
C *T `yaml:"c,alias"`
D *T `yaml:"d,alias"`
C *T `yaml:"c"`
D *T `yaml:"d"`
}
v.A = &T{I: 1, S: "hello"}
v.B = &T{I: 2, S: "world"}
@@ -358,9 +401,16 @@ print yaml file with color
### Installation
```sh
go install github.com/goccy/go-yaml/cmd/ycat@latest
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.

View File

@@ -1,6 +1,7 @@
package ast
import (
"errors"
"fmt"
"io"
"math"
@@ -8,13 +9,12 @@ import (
"strings"
"github.com/goccy/go-yaml/token"
"golang.org/x/xerrors"
)
var (
ErrInvalidTokenType = xerrors.New("invalid token type")
ErrInvalidAnchorName = xerrors.New("invalid anchor name")
ErrInvalidAliasName = xerrors.New("invalid alias name")
ErrInvalidTokenType = errors.New("invalid token type")
ErrInvalidAnchorName = errors.New("invalid anchor name")
ErrInvalidAliasName = errors.New("invalid alias name")
)
// NodeType type identifier of node
@@ -51,6 +51,8 @@ const (
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
@@ -98,6 +100,8 @@ func (t NodeType) String() string {
return "MappingValue"
case SequenceType:
return "Sequence"
case SequenceEntryType:
return "SequenceEntry"
case AnchorType:
return "Anchor"
case AliasType:
@@ -148,6 +152,8 @@ func (t NodeType) YAMLName() string {
return "value"
case SequenceType:
return "sequence"
case SequenceEntryType:
return "value"
case AnchorType:
return "anchor"
case AliasType:
@@ -196,6 +202,7 @@ type Node interface {
// MapKeyNode type for map key node
type MapKeyNode interface {
Node
IsMergeKey() bool
// String node to text without comment
stringWithoutComment() string
}
@@ -278,6 +285,49 @@ func readNode(p []byte, node Node) (int, error) {
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{
@@ -298,105 +348,30 @@ func Bool(tk *token.Token) *BoolNode {
// Integer create node for integer value
func Integer(tk *token.Token) *IntegerNode {
value := removeUnderScoreFromNumber(tk.Value)
switch tk.Type {
case token.BinaryIntegerType:
// skip two characters because binary token starts with '0b'
skipCharacterNum := 2
negativePrefix := ""
if value[0] == '-' {
skipCharacterNum++
negativePrefix = "-"
}
if len(negativePrefix) > 0 {
i, _ := strconv.ParseInt(negativePrefix+value[skipCharacterNum:], 2, 64)
return &IntegerNode{
BaseNode: &BaseNode{},
Token: tk,
Value: i,
}
}
i, _ := strconv.ParseUint(negativePrefix+value[skipCharacterNum:], 2, 64)
return &IntegerNode{
BaseNode: &BaseNode{},
Token: tk,
Value: i,
}
case token.OctetIntegerType:
// octet token starts with '0o' or '-0o' or '0' or '-0'
skipCharacterNum := 1
negativePrefix := ""
if value[0] == '-' {
skipCharacterNum++
if len(value) > 2 && value[2] == 'o' {
skipCharacterNum++
}
negativePrefix = "-"
} else {
if value[1] == 'o' {
skipCharacterNum++
}
}
if len(negativePrefix) > 0 {
i, _ := strconv.ParseInt(negativePrefix+value[skipCharacterNum:], 8, 64)
return &IntegerNode{
BaseNode: &BaseNode{},
Token: tk,
Value: i,
}
}
i, _ := strconv.ParseUint(value[skipCharacterNum:], 8, 64)
return &IntegerNode{
BaseNode: &BaseNode{},
Token: tk,
Value: i,
}
case token.HexIntegerType:
// hex token starts with '0x' or '-0x'
skipCharacterNum := 2
negativePrefix := ""
if value[0] == '-' {
skipCharacterNum++
negativePrefix = "-"
}
if len(negativePrefix) > 0 {
i, _ := strconv.ParseInt(negativePrefix+value[skipCharacterNum:], 16, 64)
return &IntegerNode{
BaseNode: &BaseNode{},
Token: tk,
Value: i,
}
}
i, _ := strconv.ParseUint(value[skipCharacterNum:], 16, 64)
return &IntegerNode{
BaseNode: &BaseNode{},
Token: tk,
Value: i,
}
var v any
if num := token.ToNumber(tk.Value); num != nil {
v = num.Value
}
if value[0] == '-' || value[0] == '+' {
i, _ := strconv.ParseInt(value, 10, 64)
return &IntegerNode{
BaseNode: &BaseNode{},
Token: tk,
Value: i,
}
}
i, _ := strconv.ParseUint(value, 10, 64)
return &IntegerNode{
BaseNode: &BaseNode{},
Token: tk,
Value: i,
Value: v,
}
}
// Float create node for float value
func Float(tk *token.Token) *FloatNode {
f, _ := strconv.ParseFloat(removeUnderScoreFromNumber(tk.Value), 64)
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: f,
Value: v,
}
}
@@ -607,7 +582,9 @@ func (d *DocumentNode) String() string {
if d.Start != nil {
doc = append(doc, d.Start.Value)
}
doc = append(doc, d.Body.String())
if d.Body != nil {
doc = append(doc, d.Body.String())
}
if d.End != nil {
doc = append(doc, d.End.Value)
}
@@ -619,10 +596,6 @@ func (d *DocumentNode) MarshalYAML() ([]byte, error) {
return []byte(d.String()), nil
}
func removeUnderScoreFromNumber(num string) string {
return strings.ReplaceAll(num, "_", "")
}
// NullNode type of null node
type NullNode struct {
*BaseNode
@@ -654,6 +627,12 @@ func (n *NullNode) GetValue() interface{} {
// 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)
}
@@ -669,6 +648,11 @@ 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
@@ -716,6 +700,11 @@ 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
@@ -764,6 +753,11 @@ 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
@@ -794,6 +788,11 @@ 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 {
@@ -831,11 +830,12 @@ func (n *StringNode) String() string {
// 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", space, v))
values = append(values, fmt.Sprintf("%s%s%s", space, indent, v))
}
block := strings.TrimSuffix(strings.TrimSuffix(strings.Join(values, lbc), fmt.Sprintf("%s %s", lbc, space)), fmt.Sprintf(" %s", space))
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)
@@ -862,11 +862,12 @@ func (n *StringNode) stringWithoutComment() string {
// 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", space, v))
values = append(values, fmt.Sprintf("%s%s%s", space, indent, v))
}
block := strings.TrimSuffix(strings.TrimSuffix(strings.Join(values, lbc), fmt.Sprintf("%s %s", lbc, space)), fmt.Sprintf(" %s", space))
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)
@@ -931,6 +932,11 @@ 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
@@ -974,6 +980,11 @@ 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
@@ -1021,6 +1032,11 @@ 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
@@ -1068,6 +1084,11 @@ 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
@@ -1114,6 +1135,11 @@ 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
@@ -1147,6 +1173,11 @@ 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
@@ -1317,13 +1348,27 @@ 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
Key MapKeyNode
Value Node
FootComment *CommentGroupNode
Start *token.Token // delimiter token ':'.
CollectEntry *token.Token // collect entry token ','.
Key MapKeyNode
Value Node
FootComment *CommentGroupNode
IsFlowStyle bool
}
// Replace replace value node.
@@ -1360,6 +1405,7 @@ func (n *MappingValueNode) AddColumn(col int) {
// 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)
@@ -1390,12 +1436,20 @@ func (n *MappingValueNode) String() string {
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 {
return fmt.Sprintf("%s%s: %s", space, n.Key.String(), n.Value.String())
} else if keyIndentLevel < valueIndentLevel {
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",
@@ -1414,7 +1468,10 @@ func (n *MappingValueNode) toString() string {
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",
@@ -1485,13 +1542,14 @@ type SequenceNode struct {
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 xerrors.Errorf(
return fmt.Errorf(
"invalid index for sequence: sequence length is %d, but specified %d index",
len(n.Values), idx,
)
@@ -1507,6 +1565,11 @@ 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.
@@ -1562,7 +1625,15 @@ func (n *SequenceNode) blockStyleString() string {
}
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)
@@ -1585,9 +1656,10 @@ func (n *SequenceNode) blockStyleString() string {
}
newValue := strings.Join(newValues, "\n")
if len(n.ValueHeadComments) == len(n.Values) && n.ValueHeadComments[idx] != nil {
values = append(values, n.ValueHeadComments[idx].StringWithSpace(n.Start.Position.Column-1))
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", space, newValue))
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))
@@ -1616,6 +1688,87 @@ 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
@@ -1624,6 +1777,10 @@ type AnchorNode struct {
Value Node
}
func (n *AnchorNode) stringWithoutComment() string {
return n.Value.String()
}
func (n *AnchorNode) SetName(name string) error {
if n.Name == nil {
return ErrInvalidAnchorName
@@ -1649,6 +1806,10 @@ 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)
@@ -1662,15 +1823,18 @@ func (n *AnchorNode) AddColumn(col int) {
// String anchor to text
func (n *AnchorNode) String() string {
anchor := "&" + n.Name.String()
value := n.Value.String()
if len(strings.Split(value, "\n")) > 1 {
return fmt.Sprintf("&%s\n%s", n.Name.String(), value)
} else if s, ok := n.Value.(*SequenceNode); ok && !s.IsFlowStyle {
return fmt.Sprintf("&%s\n%s", n.Name.String(), value)
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", n.Name.String(), value)
return fmt.Sprintf("%s\n%s", anchor, value)
}
return fmt.Sprintf("&%s %s", n.Name.String(), value)
if value == "" {
// implicit null value.
return anchor
}
return fmt.Sprintf("%s %s", anchor, value)
}
// MarshalYAML encodes to a YAML text
@@ -1678,6 +1842,18 @@ 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
@@ -1685,6 +1861,10 @@ type AliasNode struct {
Value Node
}
func (n *AliasNode) stringWithoutComment() string {
return n.Value.String()
}
func (n *AliasNode) SetName(name string) error {
if n.Value == nil {
return ErrInvalidAliasName
@@ -1710,6 +1890,10 @@ 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)
@@ -1728,11 +1912,20 @@ 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
Value Node
// 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
@@ -1750,14 +1943,21 @@ func (n *DirectiveNode) GetToken() *token.Token {
// AddColumn add column number to child nodes recursively
func (n *DirectiveNode) AddColumn(col int) {
if n.Value != nil {
n.Value.AddColumn(col)
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 {
return fmt.Sprintf("%s%s", n.Start.Value, n.Value.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
@@ -1768,8 +1968,21 @@ func (n *DirectiveNode) MarshalYAML() ([]byte, error) {
// TagNode type of tag node
type TagNode struct {
*BaseNode
Start *token.Token
Value Node
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
@@ -1795,7 +2008,14 @@ func (n *TagNode) AddColumn(col int) {
// String tag to text
func (n *TagNode) String() string {
return fmt.Sprintf("%s %s", n.Start.Value, n.Value.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
@@ -1803,6 +2023,26 @@ 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
@@ -1880,10 +2120,13 @@ 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
@@ -1930,7 +2173,10 @@ func Walk(v Visitor, node Node) {
Walk(v, n.Value)
case *DirectiveNode:
walkComment(v, n.BaseNode)
Walk(v, n.Value)
Walk(v, n.Name)
for _, value := range n.Values {
Walk(v, value)
}
case *TagNode:
walkComment(v, n.BaseNode)
Walk(v, n.Value)
@@ -2016,7 +2262,14 @@ func (f *parentFinder) walk(parent, node Node) Node {
case *LiteralNode:
return f.walk(node, n.Value)
case *DirectiveNode:
return f.walk(node, n.Value)
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:
@@ -2092,10 +2345,10 @@ func Merge(dst Node, src Node) error {
err := &ErrInvalidMergeType{dst: dst, src: src}
switch dst.Type() {
case DocumentType:
node := dst.(*DocumentNode)
node, _ := dst.(*DocumentNode)
return Merge(node.Body, src)
case MappingType:
node := dst.(*MappingNode)
node, _ := dst.(*MappingNode)
target, ok := src.(*MappingNode)
if !ok {
return err
@@ -2103,7 +2356,7 @@ func Merge(dst Node, src Node) error {
node.Merge(target)
return nil
case SequenceType:
node := dst.(*SequenceNode)
node, _ := dst.(*SequenceNode)
target, ok := src.(*SequenceNode)
if !ok {
return err

37
vendor/github.com/goccy/go-yaml/context.go generated vendored Normal file
View File

@@ -0,0 +1,37 @@
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
}

View File

File diff suppressed because it is too large Load Diff

View File

@@ -17,7 +17,6 @@ import (
"github.com/goccy/go-yaml/parser"
"github.com/goccy/go-yaml/printer"
"github.com/goccy/go-yaml/token"
"golang.org/x/xerrors"
)
const (
@@ -29,24 +28,29 @@ const (
type Encoder struct {
writer io.Writer
opts []EncodeOption
indent int
indentSequence bool
singleQuote bool
isFlowStyle bool
isJSONStyle bool
useJSONMarshaler bool
enableSmartAnchor bool
aliasRefToName map[uintptr]string
anchorRefToName map[uintptr]string
anchorNameMap map[string]struct{}
anchorCallback func(*ast.AnchorNode, interface{}) error
anchorPtrToNameMap map[uintptr]string
customMarshalerMap map[reflect.Type]func(interface{}) ([]byte, error)
customMarshalerMap map[reflect.Type]func(context.Context, interface{}) ([]byte, error)
omitZero bool
omitEmpty bool
autoInt bool
useLiteralStyleIfMultiline bool
commentMap map[*Path][]*Comment
written bool
line int
column int
offset int
indentNum int
indentLevel int
line int
column int
offset int
indentNum int
indentLevel int
indentSequence bool
}
// NewEncoder returns a new encoder that writes to w.
@@ -55,12 +59,14 @@ func NewEncoder(w io.Writer, opts ...EncodeOption) *Encoder {
return &Encoder{
writer: w,
opts: opts,
indent: DefaultIndentSpaces,
anchorPtrToNameMap: map[uintptr]string{},
customMarshalerMap: map[reflect.Type]func(interface{}) ([]byte, error){},
customMarshalerMap: map[reflect.Type]func(context.Context, interface{}) ([]byte, error){},
line: 1,
column: 1,
offset: 0,
indentNum: DefaultIndentSpaces,
anchorRefToName: make(map[uintptr]string),
anchorNameMap: make(map[string]struct{}),
aliasRefToName: make(map[uintptr]string),
}
}
@@ -84,19 +90,19 @@ func (e *Encoder) Encode(v interface{}) error {
func (e *Encoder) EncodeContext(ctx context.Context, v interface{}) error {
node, err := e.EncodeToNodeContext(ctx, v)
if err != nil {
return errors.Wrapf(err, "failed to encode to node")
return err
}
if err := e.setCommentByCommentMap(node); err != nil {
return errors.Wrapf(err, "failed to set comment by comment map")
return err
}
if !e.written {
e.written = true
} else {
// write document separator
e.writer.Write([]byte("---\n"))
_, _ = e.writer.Write([]byte("---\n"))
}
var p printer.Printer
e.writer.Write(p.PrintNode(node))
_, _ = e.writer.Write(p.PrintNode(node))
return nil
}
@@ -109,12 +115,19 @@ func (e *Encoder) EncodeToNode(v interface{}) (ast.Node, error) {
func (e *Encoder) EncodeToNodeContext(ctx context.Context, v interface{}) (ast.Node, error) {
for _, opt := range e.opts {
if err := opt(e); err != nil {
return nil, errors.Wrapf(err, "failed to run option for encoder")
return nil, err
}
}
if e.enableSmartAnchor {
// during the first encoding, store all mappings between alias addresses and their names.
if _, err := e.encodeValue(ctx, reflect.ValueOf(v), 1); err != nil {
return nil, err
}
e.clearSmartAnchorRef()
}
node, err := e.encodeValue(ctx, reflect.ValueOf(v), 1)
if err != nil {
return nil, errors.Wrapf(err, "failed to encode value")
return nil, err
}
return node, nil
}
@@ -126,7 +139,7 @@ func (e *Encoder) setCommentByCommentMap(node ast.Node) error {
for path, comments := range e.commentMap {
n, err := path.FilterNode(node)
if err != nil {
return errors.Wrapf(err, "failed to filter node")
return err
}
if n == nil {
continue
@@ -140,15 +153,15 @@ func (e *Encoder) setCommentByCommentMap(node ast.Node) error {
switch comment.Position {
case CommentHeadPosition:
if err := e.setHeadComment(node, n, commentGroup); err != nil {
return errors.Wrapf(err, "failed to set head comment")
return err
}
case CommentLinePosition:
if err := e.setLineComment(node, n, commentGroup); err != nil {
return errors.Wrapf(err, "failed to set line comment")
return err
}
case CommentFootPosition:
if err := e.setFootComment(node, n, commentGroup); err != nil {
return errors.Wrapf(err, "failed to set foot comment")
return err
}
default:
return ErrUnknownCommentPositionType
@@ -166,11 +179,11 @@ func (e *Encoder) setHeadComment(node ast.Node, filtered ast.Node, comment *ast.
switch p := parent.(type) {
case *ast.MappingValueNode:
if err := p.SetComment(comment); err != nil {
return errors.Wrapf(err, "failed to set comment")
return err
}
case *ast.MappingNode:
if err := p.SetComment(comment); err != nil {
return errors.Wrapf(err, "failed to set comment")
return err
}
case *ast.SequenceNode:
if len(p.ValueHeadComments) == 0 {
@@ -196,11 +209,11 @@ func (e *Encoder) setLineComment(node ast.Node, filtered ast.Node, comment *ast.
// Line comment cannot be set for mapping value node.
// It should probably be set for the parent map node
if err := e.setLineCommentToParentMapNode(node, filtered, comment); err != nil {
return errors.Wrapf(err, "failed to set line comment to parent node")
return err
}
default:
if err := filtered.SetComment(comment); err != nil {
return errors.Wrapf(err, "failed to set comment")
return err
}
}
return nil
@@ -214,11 +227,11 @@ func (e *Encoder) setLineCommentToParentMapNode(node ast.Node, filtered ast.Node
switch p := parent.(type) {
case *ast.MappingValueNode:
if err := p.Key.SetComment(comment); err != nil {
return errors.Wrapf(err, "failed to set comment")
return err
}
case *ast.MappingNode:
if err := p.SetComment(comment); err != nil {
return errors.Wrapf(err, "failed to set comment")
return err
}
default:
return ErrUnsupportedLinePositionType(parent)
@@ -247,7 +260,7 @@ func (e *Encoder) setFootComment(node ast.Node, filtered ast.Node, comment *ast.
func (e *Encoder) encodeDocument(doc []byte) (ast.Node, error) {
f, err := parser.ParseBytes(doc, 0)
if err != nil {
return nil, errors.Wrapf(err, "failed to parse yaml")
return nil, err
}
for _, docNode := range f.Docs {
if docNode.Body != nil {
@@ -288,7 +301,7 @@ func (e *Encoder) existsTypeInCustomMarshalerMap(t reflect.Type) bool {
return false
}
func (e *Encoder) marshalerFromCustomMarshalerMap(t reflect.Type) (func(interface{}) ([]byte, error), bool) {
func (e *Encoder) marshalerFromCustomMarshalerMap(t reflect.Type) (func(context.Context, interface{}) ([]byte, error), bool) {
if marshaler, exists := e.customMarshalerMap[t]; exists {
return marshaler, exists
}
@@ -318,7 +331,7 @@ func (e *Encoder) canEncodeByMarshaler(v reflect.Value) bool {
return true
case InterfaceMarshaler:
return true
case time.Time:
case time.Time, *time.Time:
return true
case time.Duration:
return true
@@ -334,13 +347,13 @@ func (e *Encoder) encodeByMarshaler(ctx context.Context, v reflect.Value, column
iface := v.Interface()
if marshaler, exists := e.marshalerFromCustomMarshalerMap(v.Type()); exists {
doc, err := marshaler(iface)
doc, err := marshaler(ctx, iface)
if err != nil {
return nil, errors.Wrapf(err, "failed to MarshalYAML")
return nil, err
}
node, err := e.encodeDocument(doc)
if err != nil {
return nil, errors.Wrapf(err, "failed to encode document")
return nil, err
}
return node, nil
}
@@ -348,11 +361,11 @@ func (e *Encoder) encodeByMarshaler(ctx context.Context, v reflect.Value, column
if marshaler, ok := iface.(BytesMarshalerContext); ok {
doc, err := marshaler.MarshalYAML(ctx)
if err != nil {
return nil, errors.Wrapf(err, "failed to MarshalYAML")
return nil, err
}
node, err := e.encodeDocument(doc)
if err != nil {
return nil, errors.Wrapf(err, "failed to encode document")
return nil, err
}
return node, nil
}
@@ -360,11 +373,11 @@ func (e *Encoder) encodeByMarshaler(ctx context.Context, v reflect.Value, column
if marshaler, ok := iface.(BytesMarshaler); ok {
doc, err := marshaler.MarshalYAML()
if err != nil {
return nil, errors.Wrapf(err, "failed to MarshalYAML")
return nil, err
}
node, err := e.encodeDocument(doc)
if err != nil {
return nil, errors.Wrapf(err, "failed to encode document")
return nil, err
}
return node, nil
}
@@ -372,7 +385,7 @@ func (e *Encoder) encodeByMarshaler(ctx context.Context, v reflect.Value, column
if marshaler, ok := iface.(InterfaceMarshalerContext); ok {
marshalV, err := marshaler.MarshalYAML(ctx)
if err != nil {
return nil, errors.Wrapf(err, "failed to MarshalYAML")
return nil, err
}
return e.encodeValue(ctx, reflect.ValueOf(marshalV), column)
}
@@ -380,7 +393,7 @@ func (e *Encoder) encodeByMarshaler(ctx context.Context, v reflect.Value, column
if marshaler, ok := iface.(InterfaceMarshaler); ok {
marshalV, err := marshaler.MarshalYAML()
if err != nil {
return nil, errors.Wrapf(err, "failed to MarshalYAML")
return nil, err
}
return e.encodeValue(ctx, reflect.ValueOf(marshalV), column)
}
@@ -388,20 +401,21 @@ func (e *Encoder) encodeByMarshaler(ctx context.Context, v reflect.Value, column
if t, ok := iface.(time.Time); ok {
return e.encodeTime(t, column), nil
}
// Handle *time.Time explicitly since it implements TextMarshaler and shouldn't be treated as plain text
if t, ok := iface.(*time.Time); ok && t != nil {
return e.encodeTime(*t, column), nil
}
if t, ok := iface.(time.Duration); ok {
return e.encodeDuration(t, column), nil
}
if marshaler, ok := iface.(encoding.TextMarshaler); ok {
doc, err := marshaler.MarshalText()
text, err := marshaler.MarshalText()
if err != nil {
return nil, errors.Wrapf(err, "failed to MarshalText")
}
node, err := e.encodeDocument(doc)
if err != nil {
return nil, errors.Wrapf(err, "failed to encode document")
return nil, err
}
node := e.encodeString(string(text), column)
return node, nil
}
@@ -409,21 +423,21 @@ func (e *Encoder) encodeByMarshaler(ctx context.Context, v reflect.Value, column
if marshaler, ok := iface.(jsonMarshaler); ok {
jsonBytes, err := marshaler.MarshalJSON()
if err != nil {
return nil, errors.Wrapf(err, "failed to MarshalJSON")
return nil, err
}
doc, err := JSONToYAML(jsonBytes)
if err != nil {
return nil, errors.Wrapf(err, "failed to convert json to yaml")
return nil, err
}
node, err := e.encodeDocument(doc)
if err != nil {
return nil, errors.Wrapf(err, "failed to encode document")
return nil, err
}
return node, nil
}
}
return nil, xerrors.Errorf("does not implemented Marshaler")
return nil, errors.New("does not implemented Marshaler")
}
func (e *Encoder) encodeValue(ctx context.Context, v reflect.Value, column int) (ast.Node, error) {
@@ -433,7 +447,7 @@ func (e *Encoder) encodeValue(ctx context.Context, v reflect.Value, column int)
if e.canEncodeByMarshaler(v) {
node, err := e.encodeByMarshaler(ctx, v, column)
if err != nil {
return nil, errors.Wrapf(err, "failed to encode by marshaler")
return nil, err
}
return node, nil
}
@@ -447,12 +461,8 @@ func (e *Encoder) encodeValue(ctx context.Context, v reflect.Value, column int)
case reflect.Float64:
return e.encodeFloat(v.Float(), 64), nil
case reflect.Ptr:
anchorName := e.anchorPtrToNameMap[v.Pointer()]
if anchorName != "" {
aliasName := anchorName
alias := ast.Alias(token.New("*", "*", e.pos(column)))
alias.Value = ast.String(token.New(aliasName, aliasName, e.pos(column)))
return alias, nil
if value := e.encodePtrAnchor(v, column); value != nil {
return value, nil
}
return e.encodeValue(ctx, v.Elem(), column)
case reflect.Interface:
@@ -465,6 +475,9 @@ func (e *Encoder) encodeValue(ctx context.Context, v reflect.Value, column int)
if mapSlice, ok := v.Interface().(MapSlice); ok {
return e.encodeMapSlice(ctx, mapSlice, column)
}
if value := e.encodePtrAnchor(v, column); value != nil {
return value, nil
}
return e.encodeSlice(ctx, v)
case reflect.Array:
return e.encodeArray(ctx, v)
@@ -479,12 +492,27 @@ func (e *Encoder) encodeValue(ctx context.Context, v reflect.Value, column int)
}
return e.encodeStruct(ctx, v, column)
case reflect.Map:
return e.encodeMap(ctx, v, column), nil
if value := e.encodePtrAnchor(v, column); value != nil {
return value, nil
}
return e.encodeMap(ctx, v, column)
default:
return nil, xerrors.Errorf("unknown value type %s", v.Type().String())
return nil, fmt.Errorf("unknown value type %s", v.Type().String())
}
}
func (e *Encoder) encodePtrAnchor(v reflect.Value, column int) ast.Node {
anchorName, exists := e.getAnchor(v.Pointer())
if !exists {
return nil
}
aliasName := anchorName
alias := ast.Alias(token.New("*", "*", e.pos(column)))
alias.Value = ast.String(token.New(aliasName, aliasName, e.pos(column)))
e.setSmartAlias(aliasName, v.Pointer())
return alias
}
func (e *Encoder) pos(column int) *token.Position {
return &token.Position{
Line: e.line,
@@ -501,12 +529,12 @@ func (e *Encoder) encodeNil() *ast.NullNode {
}
func (e *Encoder) encodeInt(v int64) *ast.IntegerNode {
value := fmt.Sprint(v)
value := strconv.FormatInt(v, 10)
return ast.Integer(token.New(value, value, e.pos(e.column)))
}
func (e *Encoder) encodeUint(v uint64) *ast.IntegerNode {
value := fmt.Sprint(v)
value := strconv.FormatUint(v, 10)
return ast.Integer(token.New(value, value, e.pos(e.column)))
}
@@ -523,6 +551,9 @@ func (e *Encoder) encodeFloat(v float64, bitSize int) ast.Node {
}
value := strconv.FormatFloat(v, 'g', -1, bitSize)
if !strings.Contains(value, ".") && !strings.Contains(value, "e") {
if e.autoInt {
return ast.Integer(token.New(value, value, e.pos(e.column)))
}
// append x.0 suffix to keep float value context
value = fmt.Sprintf("%s.0", value)
}
@@ -539,6 +570,17 @@ func (e *Encoder) isNeedQuoted(v string) bool {
if e.isFlowStyle && strings.ContainsAny(v, `]},'"`) {
return true
}
if e.isFlowStyle {
for i := 0; i < len(v); i++ {
if v[i] != ':' {
continue
}
if i+1 < len(v) && v[i+1] == '/' {
continue
}
return true
}
}
if token.IsNeedQuoted(v) {
return true
}
@@ -557,45 +599,41 @@ func (e *Encoder) encodeString(v string, column int) *ast.StringNode {
}
func (e *Encoder) encodeBool(v bool) *ast.BoolNode {
value := fmt.Sprint(v)
value := strconv.FormatBool(v)
return ast.Bool(token.New(value, value, e.pos(e.column)))
}
func (e *Encoder) encodeSlice(ctx context.Context, value reflect.Value) (*ast.SequenceNode, error) {
if e.indentSequence {
e.column += e.indent
e.column += e.indentNum
defer func() { e.column -= e.indentNum }()
}
column := e.column
sequence := ast.Sequence(token.New("-", "-", e.pos(column)), e.isFlowStyle)
for i := 0; i < value.Len(); i++ {
node, err := e.encodeValue(ctx, value.Index(i), column)
if err != nil {
return nil, errors.Wrapf(err, "failed to encode value for slice")
return nil, err
}
sequence.Values = append(sequence.Values, node)
}
if e.indentSequence {
e.column -= e.indent
}
return sequence, nil
}
func (e *Encoder) encodeArray(ctx context.Context, value reflect.Value) (*ast.SequenceNode, error) {
if e.indentSequence {
e.column += e.indent
e.column += e.indentNum
defer func() { e.column -= e.indentNum }()
}
column := e.column
sequence := ast.Sequence(token.New("-", "-", e.pos(column)), e.isFlowStyle)
for i := 0; i < value.Len(); i++ {
node, err := e.encodeValue(ctx, value.Index(i), column)
if err != nil {
return nil, errors.Wrapf(err, "failed to encode value for array")
return nil, err
}
sequence.Values = append(sequence.Values, node)
}
if e.indentSequence {
e.column -= e.indent
}
return sequence, nil
}
@@ -604,10 +642,13 @@ func (e *Encoder) encodeMapItem(ctx context.Context, item MapItem, column int) (
v := reflect.ValueOf(item.Value)
value, err := e.encodeValue(ctx, v, column)
if err != nil {
return nil, errors.Wrapf(err, "failed to encode MapItem")
return nil, err
}
if e.isMapNode(value) {
value.AddColumn(e.indent)
value.AddColumn(e.indentNum)
}
if e.isTagAndMapNode(value) {
value.AddColumn(e.indentNum)
}
return ast.MappingValue(
token.New("", "", e.pos(column)),
@@ -619,11 +660,11 @@ func (e *Encoder) encodeMapItem(ctx context.Context, item MapItem, column int) (
func (e *Encoder) encodeMapSlice(ctx context.Context, value MapSlice, column int) (*ast.MappingNode, error) {
node := ast.Mapping(token.New("", "", e.pos(column)), e.isFlowStyle)
for _, item := range value {
value, err := e.encodeMapItem(ctx, item, column)
encoded, err := e.encodeMapItem(ctx, item, column)
if err != nil {
return nil, errors.Wrapf(err, "failed to encode MapItem for MapSlice")
return nil, err
}
node.Values = append(node.Values, value)
node.Values = append(node.Values, encoded)
}
return node, nil
}
@@ -633,7 +674,12 @@ func (e *Encoder) isMapNode(node ast.Node) bool {
return ok
}
func (e *Encoder) encodeMap(ctx context.Context, value reflect.Value, column int) ast.Node {
func (e *Encoder) isTagAndMapNode(node ast.Node) bool {
tn, ok := node.(*ast.TagNode)
return ok && e.isMapNode(tn.Value)
}
func (e *Encoder) encodeMap(ctx context.Context, value reflect.Value, column int) (ast.Node, error) {
node := ast.Mapping(token.New("", "", e.pos(column)), e.isFlowStyle)
keys := make([]interface{}, len(value.MapKeys()))
for i, k := range value.MapKeys() {
@@ -645,20 +691,35 @@ func (e *Encoder) encodeMap(ctx context.Context, value reflect.Value, column int
for _, key := range keys {
k := reflect.ValueOf(key)
v := value.MapIndex(k)
value, err := e.encodeValue(ctx, v, column)
encoded, err := e.encodeValue(ctx, v, column)
if err != nil {
return nil
return nil, err
}
if e.isMapNode(value) {
value.AddColumn(e.indent)
if e.isMapNode(encoded) {
encoded.AddColumn(e.indentNum)
}
if e.isTagAndMapNode(encoded) {
encoded.AddColumn(e.indentNum)
}
keyText := fmt.Sprint(key)
vRef := e.toPointer(v)
// during the second encoding, an anchor is assigned if it is found to be used by an alias.
if aliasName, exists := e.getSmartAlias(vRef); exists {
anchorName := aliasName
anchorNode := ast.Anchor(token.New("&", "&", e.pos(column)))
anchorNode.Name = ast.String(token.New(anchorName, anchorName, e.pos(column)))
anchorNode.Value = encoded
encoded = anchorNode
}
node.Values = append(node.Values, ast.MappingValue(
nil,
e.encodeString(fmt.Sprint(key), column),
value,
e.encodeString(keyText, column),
encoded,
))
e.setSmartAnchor(vRef, keyText)
}
return node
return node, nil
}
// IsZeroer is used to check whether an object is zero to determine
@@ -668,7 +729,70 @@ type IsZeroer interface {
IsZero() bool
}
func (e *Encoder) isZeroValue(v reflect.Value) bool {
func (e *Encoder) isOmittedByOmitZero(v reflect.Value) bool {
kind := v.Kind()
if z, ok := v.Interface().(IsZeroer); ok {
if (kind == reflect.Ptr || kind == reflect.Interface) && v.IsNil() {
return true
}
return z.IsZero()
}
switch kind {
case reflect.String:
return len(v.String()) == 0
case reflect.Interface, reflect.Ptr, reflect.Slice, reflect.Map:
return v.IsNil()
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return v.Int() == 0
case reflect.Float32, reflect.Float64:
return v.Float() == 0
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return v.Uint() == 0
case reflect.Bool:
return !v.Bool()
case reflect.Struct:
vt := v.Type()
for i := v.NumField() - 1; i >= 0; i-- {
if vt.Field(i).PkgPath != "" {
continue // private field
}
if !e.isOmittedByOmitZero(v.Field(i)) {
return false
}
}
return true
}
return false
}
func (e *Encoder) isOmittedByOmitEmptyOption(v reflect.Value) bool {
switch v.Kind() {
case reflect.String:
return len(v.String()) == 0
case reflect.Interface, reflect.Ptr:
return v.IsNil()
case reflect.Slice, reflect.Map:
return v.Len() == 0
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return v.Int() == 0
case reflect.Float32, reflect.Float64:
return v.Float() == 0
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return v.Uint() == 0
case reflect.Bool:
return !v.Bool()
}
return false
}
// The current implementation of the omitempty tag combines the functionality of encoding/json's omitempty and omitzero tags.
// This stems from a historical decision to respect the implementation of gopkg.in/yaml.v2, but it has caused confusion,
// so we are working to integrate it into the functionality of encoding/json. (However, this will take some time.)
// In the current implementation, in addition to the exclusion conditions of omitempty,
// if a type implements IsZero, that implementation will be used.
// Furthermore, for non-pointer structs, if all fields are eligible for exclusion,
// the struct itself will also be excluded. These behaviors are originally the functionality of omitzero.
func (e *Encoder) isOmittedByOmitEmptyTag(v reflect.Value) bool {
kind := v.Kind()
if z, ok := v.Interface().(IsZeroer); ok {
if (kind == reflect.Ptr || kind == reflect.Interface) && v.IsNil() {
@@ -681,9 +805,7 @@ func (e *Encoder) isZeroValue(v reflect.Value) bool {
return len(v.String()) == 0
case reflect.Interface, reflect.Ptr:
return v.IsNil()
case reflect.Slice:
return v.Len() == 0
case reflect.Map:
case reflect.Slice, reflect.Map:
return v.Len() == 0
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return v.Int() == 0
@@ -699,7 +821,7 @@ func (e *Encoder) isZeroValue(v reflect.Value) bool {
if vt.Field(i).PkgPath != "" {
continue // private field
}
if !e.isZeroValue(v.Field(i)) {
if !e.isOmittedByOmitEmptyTag(v.Field(i)) {
return false
}
}
@@ -730,14 +852,14 @@ func (e *Encoder) encodeAnchor(anchorName string, value ast.Node, fieldValue ref
anchorNode.Value = value
if e.anchorCallback != nil {
if err := e.anchorCallback(anchorNode, fieldValue.Interface()); err != nil {
return nil, errors.Wrapf(err, "failed to marshal anchor")
return nil, err
}
if snode, ok := anchorNode.Name.(*ast.StringNode); ok {
anchorName = snode.Value
}
}
if fieldValue.Kind() == reflect.Ptr {
e.anchorPtrToNameMap[fieldValue.Pointer()] = anchorName
e.setAnchor(fieldValue.Pointer(), anchorName)
}
return anchorNode, nil
}
@@ -745,9 +867,9 @@ func (e *Encoder) encodeAnchor(anchorName string, value ast.Node, fieldValue ref
func (e *Encoder) encodeStruct(ctx context.Context, value reflect.Value, column int) (ast.Node, error) {
node := ast.Mapping(token.New("", "", e.pos(column)), e.isFlowStyle)
structType := value.Type()
structFieldMap, err := structFieldMap(structType)
fieldMap, err := structFieldMap(structType)
if err != nil {
return nil, errors.Wrapf(err, "failed to get struct field map")
return nil, err
}
hasInlineAnchorField := false
var inlineAnchorValue reflect.Value
@@ -757,119 +879,190 @@ func (e *Encoder) encodeStruct(ctx context.Context, value reflect.Value, column
continue
}
fieldValue := value.FieldByName(field.Name)
structField := structFieldMap[field.Name]
if structField.IsOmitEmpty && e.isZeroValue(fieldValue) {
// omit encoding
sf := fieldMap[field.Name]
if (e.omitZero || sf.IsOmitZero) && e.isOmittedByOmitZero(fieldValue) {
// omit encoding by omitzero tag or OmitZero option.
continue
}
if e.omitEmpty && e.isOmittedByOmitEmptyOption(fieldValue) {
// omit encoding by OmitEmpty option.
continue
}
if sf.IsOmitEmpty && e.isOmittedByOmitEmptyTag(fieldValue) {
// omit encoding by omitempty tag.
continue
}
ve := e
if !e.isFlowStyle && structField.IsFlow {
if !e.isFlowStyle && sf.IsFlow {
ve = &Encoder{}
*ve = *e
ve.isFlowStyle = true
}
value, err := ve.encodeValue(ctx, fieldValue, column)
encoded, err := ve.encodeValue(ctx, fieldValue, column)
if err != nil {
return nil, errors.Wrapf(err, "failed to encode value")
return nil, err
}
if e.isMapNode(value) {
value.AddColumn(e.indent)
if e.isMapNode(encoded) {
encoded.AddColumn(e.indentNum)
}
var key ast.MapKeyNode = e.encodeString(structField.RenderName, column)
var key ast.MapKeyNode = e.encodeString(sf.RenderName, column)
switch {
case structField.AnchorName != "":
anchorNode, err := e.encodeAnchor(structField.AnchorName, value, fieldValue, column)
case encoded.Type() == ast.AliasType:
if aliasName := sf.AliasName; aliasName != "" {
alias, ok := encoded.(*ast.AliasNode)
if !ok {
return nil, errors.ErrUnexpectedNodeType(encoded.Type(), ast.AliasType, encoded.GetToken())
}
got := alias.Value.String()
if aliasName != got {
return nil, fmt.Errorf("expected alias name is %q but got %q", aliasName, got)
}
}
if sf.IsInline {
// if both used alias and inline, output `<<: *alias`
key = ast.MergeKey(token.New("<<", "<<", e.pos(column)))
}
case sf.AnchorName != "":
anchorNode, err := e.encodeAnchor(sf.AnchorName, encoded, fieldValue, column)
if err != nil {
return nil, errors.Wrapf(err, "failed to encode anchor")
return nil, err
}
value = anchorNode
case structField.IsAutoAlias:
if fieldValue.Kind() != reflect.Ptr {
return nil, xerrors.Errorf(
"%s in struct is not pointer type. but required automatically alias detection",
structField.FieldName,
)
}
anchorName := e.anchorPtrToNameMap[fieldValue.Pointer()]
if anchorName == "" {
return nil, xerrors.Errorf(
"cannot find anchor name from pointer address for automatically alias detection",
)
}
aliasName := anchorName
alias := ast.Alias(token.New("*", "*", e.pos(column)))
alias.Value = ast.String(token.New(aliasName, aliasName, e.pos(column)))
value = alias
if structField.IsInline {
// if both used alias and inline, output `<<: *alias`
key = ast.MergeKey(token.New("<<", "<<", e.pos(column)))
}
case structField.AliasName != "":
aliasName := structField.AliasName
alias := ast.Alias(token.New("*", "*", e.pos(column)))
alias.Value = ast.String(token.New(aliasName, aliasName, e.pos(column)))
value = alias
if structField.IsInline {
// if both used alias and inline, output `<<: *alias`
key = ast.MergeKey(token.New("<<", "<<", e.pos(column)))
}
case structField.IsInline:
isAutoAnchor := structField.IsAutoAnchor
encoded = anchorNode
case sf.IsInline:
isAutoAnchor := sf.IsAutoAnchor
if !hasInlineAnchorField {
hasInlineAnchorField = isAutoAnchor
}
if isAutoAnchor {
inlineAnchorValue = fieldValue
}
mapNode, ok := value.(ast.MapNode)
mapNode, ok := encoded.(ast.MapNode)
if !ok {
// if an inline field is null, skip encoding it
if _, ok := value.(*ast.NullNode); ok {
if _, ok := encoded.(*ast.NullNode); ok {
continue
}
return nil, xerrors.Errorf("inline value is must be map or struct type")
return nil, errors.New("inline value is must be map or struct type")
}
mapIter := mapNode.MapRange()
for mapIter.Next() {
key := mapIter.Key()
value := mapIter.Value()
keyName := key.GetToken().Value
if structFieldMap.isIncludedRenderName(keyName) {
// if declared same key name, skip encoding this field
mapKey := mapIter.Key()
mapValue := mapIter.Value()
keyName := mapKey.GetToken().Value
if fieldMap.isIncludedRenderName(keyName) {
// if declared the same key name, skip encoding this field
continue
}
key.AddColumn(-e.indent)
value.AddColumn(-e.indent)
node.Values = append(node.Values, ast.MappingValue(nil, key, value))
mapKey.AddColumn(-e.indentNum)
mapValue.AddColumn(-e.indentNum)
node.Values = append(node.Values, ast.MappingValue(nil, mapKey, mapValue))
}
continue
case structField.IsAutoAnchor:
anchorNode, err := e.encodeAnchor(structField.RenderName, value, fieldValue, column)
case sf.IsAutoAnchor:
anchorNode, err := e.encodeAnchor(sf.RenderName, encoded, fieldValue, column)
if err != nil {
return nil, errors.Wrapf(err, "failed to encode anchor")
return nil, err
}
value = anchorNode
encoded = anchorNode
}
node.Values = append(node.Values, ast.MappingValue(nil, key, value))
node.Values = append(node.Values, ast.MappingValue(nil, key, encoded))
}
if hasInlineAnchorField {
node.AddColumn(e.indent)
node.AddColumn(e.indentNum)
anchorName := "anchor"
anchorNode := ast.Anchor(token.New("&", "&", e.pos(column)))
anchorNode.Name = ast.String(token.New(anchorName, anchorName, e.pos(column)))
anchorNode.Value = node
if e.anchorCallback != nil {
if err := e.anchorCallback(anchorNode, value.Addr().Interface()); err != nil {
return nil, errors.Wrapf(err, "failed to marshal anchor")
return nil, err
}
if snode, ok := anchorNode.Name.(*ast.StringNode); ok {
anchorName = snode.Value
}
}
if inlineAnchorValue.Kind() == reflect.Ptr {
e.anchorPtrToNameMap[inlineAnchorValue.Pointer()] = anchorName
e.setAnchor(inlineAnchorValue.Pointer(), anchorName)
}
return anchorNode, nil
}
return node, nil
}
func (e *Encoder) toPointer(v reflect.Value) uintptr {
if e.isInvalidValue(v) {
return 0
}
switch v.Type().Kind() {
case reflect.Ptr:
return v.Pointer()
case reflect.Interface:
return e.toPointer(v.Elem())
case reflect.Slice:
return v.Pointer()
case reflect.Map:
return v.Pointer()
}
return 0
}
func (e *Encoder) clearSmartAnchorRef() {
if !e.enableSmartAnchor {
return
}
e.anchorRefToName = make(map[uintptr]string)
e.anchorNameMap = make(map[string]struct{})
}
func (e *Encoder) setSmartAnchor(ptr uintptr, name string) {
if !e.enableSmartAnchor {
return
}
e.setAnchor(ptr, e.generateAnchorName(name))
}
func (e *Encoder) setAnchor(ptr uintptr, name string) {
if ptr == 0 {
return
}
if name == "" {
return
}
e.anchorRefToName[ptr] = name
e.anchorNameMap[name] = struct{}{}
}
func (e *Encoder) generateAnchorName(base string) string {
if _, exists := e.anchorNameMap[base]; !exists {
return base
}
for i := 1; i < 100; i++ {
name := base + strconv.Itoa(i)
if _, exists := e.anchorNameMap[name]; exists {
continue
}
return name
}
return ""
}
func (e *Encoder) getAnchor(ref uintptr) (string, bool) {
anchorName, exists := e.anchorRefToName[ref]
return anchorName, exists
}
func (e *Encoder) setSmartAlias(name string, ref uintptr) {
if !e.enableSmartAnchor {
return
}
e.aliasRefToName[ref] = name
}
func (e *Encoder) getSmartAlias(ref uintptr) (string, bool) {
if !e.enableSmartAnchor {
return "", false
}
aliasName, exists := e.aliasRefToName[ref]
return aliasName, exists
}

View File

@@ -1,62 +1,77 @@
package yaml
import (
"fmt"
"github.com/goccy/go-yaml/ast"
"golang.org/x/xerrors"
"github.com/goccy/go-yaml/internal/errors"
)
var (
ErrInvalidQuery = xerrors.New("invalid query")
ErrInvalidPath = xerrors.New("invalid path instance")
ErrInvalidPathString = xerrors.New("invalid path string")
ErrNotFoundNode = xerrors.New("node not found")
ErrUnknownCommentPositionType = xerrors.New("unknown comment position type")
ErrInvalidCommentMapValue = xerrors.New("invalid comment map value. it must be not nil value")
ErrInvalidQuery = errors.New("invalid query")
ErrInvalidPath = errors.New("invalid path instance")
ErrInvalidPathString = errors.New("invalid path string")
ErrNotFoundNode = errors.New("node not found")
ErrUnknownCommentPositionType = errors.New("unknown comment position type")
ErrInvalidCommentMapValue = errors.New("invalid comment map value. it must be not nil value")
ErrDecodeRequiredPointerType = errors.New("required pointer type value")
ErrExceededMaxDepth = errors.New("exceeded max depth")
FormatErrorWithToken = errors.FormatError
)
type (
SyntaxError = errors.SyntaxError
TypeError = errors.TypeError
OverflowError = errors.OverflowError
DuplicateKeyError = errors.DuplicateKeyError
UnknownFieldError = errors.UnknownFieldError
UnexpectedNodeTypeError = errors.UnexpectedNodeTypeError
Error = errors.Error
)
func ErrUnsupportedHeadPositionType(node ast.Node) error {
return xerrors.Errorf("unsupported comment head position for %s", node.Type())
return fmt.Errorf("unsupported comment head position for %s", node.Type())
}
func ErrUnsupportedLinePositionType(node ast.Node) error {
return xerrors.Errorf("unsupported comment line position for %s", node.Type())
return fmt.Errorf("unsupported comment line position for %s", node.Type())
}
func ErrUnsupportedFootPositionType(node ast.Node) error {
return xerrors.Errorf("unsupported comment foot position for %s", node.Type())
return fmt.Errorf("unsupported comment foot position for %s", node.Type())
}
// IsInvalidQueryError whether err is ErrInvalidQuery or not.
func IsInvalidQueryError(err error) bool {
return xerrors.Is(err, ErrInvalidQuery)
return errors.Is(err, ErrInvalidQuery)
}
// IsInvalidPathError whether err is ErrInvalidPath or not.
func IsInvalidPathError(err error) bool {
return xerrors.Is(err, ErrInvalidPath)
return errors.Is(err, ErrInvalidPath)
}
// IsInvalidPathStringError whether err is ErrInvalidPathString or not.
func IsInvalidPathStringError(err error) bool {
return xerrors.Is(err, ErrInvalidPathString)
return errors.Is(err, ErrInvalidPathString)
}
// IsNotFoundNodeError whether err is ErrNotFoundNode or not.
func IsNotFoundNodeError(err error) bool {
return xerrors.Is(err, ErrNotFoundNode)
return errors.Is(err, ErrNotFoundNode)
}
// IsInvalidTokenTypeError whether err is ast.ErrInvalidTokenType or not.
func IsInvalidTokenTypeError(err error) bool {
return xerrors.Is(err, ast.ErrInvalidTokenType)
return errors.Is(err, ast.ErrInvalidTokenType)
}
// IsInvalidAnchorNameError whether err is ast.ErrInvalidAnchorName or not.
func IsInvalidAnchorNameError(err error) bool {
return xerrors.Is(err, ast.ErrInvalidAnchorName)
return errors.Is(err, ast.ErrInvalidAnchorName)
}
// IsInvalidAliasNameError whether err is ast.ErrInvalidAliasName or not.
func IsInvalidAliasNameError(err error) bool {
return xerrors.Is(err, ast.ErrInvalidAliasName)
return errors.Is(err, ast.ErrInvalidAliasName)
}

View File

@@ -1,225 +1,45 @@
package errors
import (
"bytes"
"errors"
"fmt"
"reflect"
"github.com/goccy/go-yaml/ast"
"github.com/goccy/go-yaml/printer"
"github.com/goccy/go-yaml/token"
"golang.org/x/xerrors"
)
const (
defaultColorize = false
defaultIncludeSource = true
)
var (
ErrDecodeRequiredPointerType = xerrors.New("required pointer type value")
As = errors.As
Is = errors.Is
New = errors.New
)
// Wrapf wrap error for stack trace
func Wrapf(err error, msg string, args ...interface{}) error {
return &wrapError{
baseError: &baseError{},
err: xerrors.Errorf(msg, args...),
nextErr: err,
frame: xerrors.Caller(1),
}
const (
defaultFormatColor = false
defaultIncludeSource = true
)
type Error interface {
error
GetToken() *token.Token
GetMessage() string
FormatError(bool, bool) string
}
// ErrSyntax create syntax error instance with message and token
func ErrSyntax(msg string, tk *token.Token) *syntaxError {
return &syntaxError{
baseError: &baseError{},
msg: msg,
token: tk,
frame: xerrors.Caller(1),
}
}
var (
_ Error = new(SyntaxError)
_ Error = new(TypeError)
_ Error = new(OverflowError)
_ Error = new(DuplicateKeyError)
_ Error = new(UnknownFieldError)
_ Error = new(UnexpectedNodeTypeError)
)
type baseError struct {
state fmt.State
verb rune
}
func (e *baseError) Error() string {
return ""
}
func (e *baseError) chainStateAndVerb(err error) {
wrapErr, ok := err.(*wrapError)
if ok {
wrapErr.state = e.state
wrapErr.verb = e.verb
}
syntaxErr, ok := err.(*syntaxError)
if ok {
syntaxErr.state = e.state
syntaxErr.verb = e.verb
}
}
type wrapError struct {
*baseError
err error
nextErr error
frame xerrors.Frame
}
type FormatErrorPrinter struct {
xerrors.Printer
Colored bool
InclSource bool
}
func (e *wrapError) As(target interface{}) bool {
err := e.nextErr
for {
if wrapErr, ok := err.(*wrapError); ok {
err = wrapErr.nextErr
continue
}
break
}
return xerrors.As(err, target)
}
func (e *wrapError) Unwrap() error {
return e.nextErr
}
func (e *wrapError) PrettyPrint(p xerrors.Printer, colored, inclSource bool) error {
return e.FormatError(&FormatErrorPrinter{Printer: p, Colored: colored, InclSource: inclSource})
}
func (e *wrapError) FormatError(p xerrors.Printer) error {
if _, ok := p.(*FormatErrorPrinter); !ok {
p = &FormatErrorPrinter{
Printer: p,
Colored: defaultColorize,
InclSource: defaultIncludeSource,
}
}
if e.verb == 'v' && e.state.Flag('+') {
// print stack trace for debugging
p.Print(e.err, "\n")
e.frame.Format(p)
e.chainStateAndVerb(e.nextErr)
return e.nextErr
}
err := e.nextErr
for {
if wrapErr, ok := err.(*wrapError); ok {
err = wrapErr.nextErr
continue
}
break
}
e.chainStateAndVerb(err)
if fmtErr, ok := err.(xerrors.Formatter); ok {
fmtErr.FormatError(p)
} else {
p.Print(err)
}
return nil
}
type wrapState struct {
org fmt.State
}
func (s *wrapState) Write(b []byte) (n int, err error) {
return s.org.Write(b)
}
func (s *wrapState) Width() (wid int, ok bool) {
return s.org.Width()
}
func (s *wrapState) Precision() (prec int, ok bool) {
return s.org.Precision()
}
func (s *wrapState) Flag(c int) bool {
// set true to 'printDetail' forced because when p.Detail() is false, xerrors.Printer no output any text
if c == '#' {
// ignore '#' keyword because xerrors.FormatError doesn't set true to printDetail.
// ( see https://github.com/golang/xerrors/blob/master/adaptor.go#L39-L43 )
return false
}
return true
}
func (e *wrapError) Format(state fmt.State, verb rune) {
e.state = state
e.verb = verb
xerrors.FormatError(e, &wrapState{org: state}, verb)
}
func (e *wrapError) Error() string {
var buf bytes.Buffer
e.PrettyPrint(&Sink{&buf}, defaultColorize, defaultIncludeSource)
return buf.String()
}
type syntaxError struct {
*baseError
msg string
token *token.Token
frame xerrors.Frame
}
func (e *syntaxError) PrettyPrint(p xerrors.Printer, colored, inclSource bool) error {
return e.FormatError(&FormatErrorPrinter{Printer: p, Colored: colored, InclSource: inclSource})
}
func (e *syntaxError) FormatError(p xerrors.Printer) error {
var pp printer.Printer
var colored, inclSource bool
if fep, ok := p.(*FormatErrorPrinter); ok {
colored = fep.Colored
inclSource = fep.InclSource
}
pos := fmt.Sprintf("[%d:%d] ", e.token.Position.Line, e.token.Position.Column)
msg := pp.PrintErrorMessage(fmt.Sprintf("%s%s", pos, e.msg), colored)
if inclSource {
msg += "\n" + pp.PrintErrorToken(e.token, colored)
}
p.Print(msg)
if e.verb == 'v' && e.state.Flag('+') {
// %+v
// print stack trace for debugging
e.frame.Format(p)
}
return nil
}
type PrettyPrinter interface {
PrettyPrint(xerrors.Printer, bool, bool) error
}
type Sink struct{ *bytes.Buffer }
func (es *Sink) Print(args ...interface{}) {
fmt.Fprint(es.Buffer, args...)
}
func (es *Sink) Printf(f string, args ...interface{}) {
fmt.Fprintf(es.Buffer, f, args...)
}
func (es *Sink) Detail() bool {
return false
}
func (e *syntaxError) Error() string {
var buf bytes.Buffer
e.PrettyPrint(&Sink{&buf}, defaultColorize, defaultIncludeSource)
return buf.String()
type SyntaxError struct {
Message string
Token *token.Token
}
type TypeError struct {
@@ -229,32 +49,198 @@ type TypeError struct {
Token *token.Token
}
func (e *TypeError) Error() string {
type OverflowError struct {
DstType reflect.Type
SrcNum string
Token *token.Token
}
type DuplicateKeyError struct {
Message string
Token *token.Token
}
type UnknownFieldError struct {
Message string
Token *token.Token
}
type UnexpectedNodeTypeError struct {
Actual ast.NodeType
Expected ast.NodeType
Token *token.Token
}
// ErrSyntax create syntax error instance with message and token
func ErrSyntax(msg string, tk *token.Token) *SyntaxError {
return &SyntaxError{
Message: msg,
Token: tk,
}
}
// ErrOverflow creates an overflow error instance with message and a token.
func ErrOverflow(dstType reflect.Type, num string, tk *token.Token) *OverflowError {
return &OverflowError{
DstType: dstType,
SrcNum: num,
Token: tk,
}
}
// ErrTypeMismatch cerates an type mismatch error instance with token.
func ErrTypeMismatch(dstType, srcType reflect.Type, token *token.Token) *TypeError {
return &TypeError{
DstType: dstType,
SrcType: srcType,
Token: token,
}
}
// ErrDuplicateKey creates an duplicate key error instance with token.
func ErrDuplicateKey(msg string, tk *token.Token) *DuplicateKeyError {
return &DuplicateKeyError{
Message: msg,
Token: tk,
}
}
// ErrUnknownField creates an unknown field error instance with token.
func ErrUnknownField(msg string, tk *token.Token) *UnknownFieldError {
return &UnknownFieldError{
Message: msg,
Token: tk,
}
}
func ErrUnexpectedNodeType(actual, expected ast.NodeType, tk *token.Token) *UnexpectedNodeTypeError {
return &UnexpectedNodeTypeError{
Actual: actual,
Expected: expected,
Token: tk,
}
}
func (e *SyntaxError) GetMessage() string {
return e.Message
}
func (e *SyntaxError) GetToken() *token.Token {
return e.Token
}
func (e *SyntaxError) Error() string {
return e.FormatError(defaultFormatColor, defaultIncludeSource)
}
func (e *SyntaxError) FormatError(colored, inclSource bool) string {
return FormatError(e.Message, e.Token, colored, inclSource)
}
func (e *OverflowError) GetMessage() string {
return e.msg()
}
func (e *OverflowError) GetToken() *token.Token {
return e.Token
}
func (e *OverflowError) Error() string {
return e.FormatError(defaultFormatColor, defaultIncludeSource)
}
func (e *OverflowError) FormatError(colored, inclSource bool) string {
return FormatError(e.msg(), e.Token, colored, inclSource)
}
func (e *OverflowError) msg() string {
return fmt.Sprintf("cannot unmarshal %s into Go value of type %s ( overflow )", e.SrcNum, e.DstType)
}
func (e *TypeError) msg() string {
if e.StructFieldName != nil {
return fmt.Sprintf("cannot unmarshal %s into Go struct field %s of type %s", e.SrcType, *e.StructFieldName, e.DstType)
}
return fmt.Sprintf("cannot unmarshal %s into Go value of type %s", e.SrcType, e.DstType)
}
func (e *TypeError) PrettyPrint(p xerrors.Printer, colored, inclSource bool) error {
return e.FormatError(&FormatErrorPrinter{Printer: p, Colored: colored, InclSource: inclSource})
func (e *TypeError) GetMessage() string {
return e.msg()
}
func (e *TypeError) FormatError(p xerrors.Printer) error {
func (e *TypeError) GetToken() *token.Token {
return e.Token
}
func (e *TypeError) Error() string {
return e.FormatError(defaultFormatColor, defaultIncludeSource)
}
func (e *TypeError) FormatError(colored, inclSource bool) string {
return FormatError(e.msg(), e.Token, colored, inclSource)
}
func (e *DuplicateKeyError) GetMessage() string {
return e.Message
}
func (e *DuplicateKeyError) GetToken() *token.Token {
return e.Token
}
func (e *DuplicateKeyError) Error() string {
return e.FormatError(defaultFormatColor, defaultIncludeSource)
}
func (e *DuplicateKeyError) FormatError(colored, inclSource bool) string {
return FormatError(e.Message, e.Token, colored, inclSource)
}
func (e *UnknownFieldError) GetMessage() string {
return e.Message
}
func (e *UnknownFieldError) GetToken() *token.Token {
return e.Token
}
func (e *UnknownFieldError) Error() string {
return e.FormatError(defaultFormatColor, defaultIncludeSource)
}
func (e *UnknownFieldError) FormatError(colored, inclSource bool) string {
return FormatError(e.Message, e.Token, colored, inclSource)
}
func (e *UnexpectedNodeTypeError) GetMessage() string {
return e.msg()
}
func (e *UnexpectedNodeTypeError) GetToken() *token.Token {
return e.Token
}
func (e *UnexpectedNodeTypeError) Error() string {
return e.FormatError(defaultFormatColor, defaultIncludeSource)
}
func (e *UnexpectedNodeTypeError) FormatError(colored, inclSource bool) string {
return FormatError(e.msg(), e.Token, colored, inclSource)
}
func (e *UnexpectedNodeTypeError) msg() string {
return fmt.Sprintf("%s was used where %s is expected", e.Actual.YAMLName(), e.Expected.YAMLName())
}
func FormatError(errMsg string, token *token.Token, colored, inclSource bool) string {
var pp printer.Printer
var colored, inclSource bool
if fep, ok := p.(*FormatErrorPrinter); ok {
colored = fep.Colored
inclSource = fep.InclSource
if token == nil {
return pp.PrintErrorMessage(errMsg, colored)
}
pos := fmt.Sprintf("[%d:%d] ", e.Token.Position.Line, e.Token.Position.Column)
msg := pp.PrintErrorMessage(fmt.Sprintf("%s%s", pos, e.Error()), colored)
pos := fmt.Sprintf("[%d:%d] ", token.Position.Line, token.Position.Column)
msg := pp.PrintErrorMessage(fmt.Sprintf("%s%s", pos, errMsg), colored)
if inclSource {
msg += "\n" + pp.PrintErrorToken(e.Token, colored)
msg += "\n" + pp.PrintErrorToken(token, colored)
}
p.Print(msg)
return nil
return msg
}

View File

@@ -0,0 +1,539 @@
package format
import (
"strings"
"github.com/goccy/go-yaml/ast"
"github.com/goccy/go-yaml/token"
)
func FormatNodeWithResolvedAlias(n ast.Node, anchorNodeMap map[string]ast.Node) string {
tk := getFirstToken(n)
if tk == nil {
return ""
}
formatter := newFormatter(tk, hasComment(n))
formatter.anchorNodeMap = anchorNodeMap
return formatter.format(n)
}
func FormatNode(n ast.Node) string {
tk := getFirstToken(n)
if tk == nil {
return ""
}
return newFormatter(tk, hasComment(n)).format(n)
}
func FormatFile(file *ast.File) string {
if len(file.Docs) == 0 {
return ""
}
tk := getFirstToken(file.Docs[0])
if tk == nil {
return ""
}
return newFormatter(tk, hasCommentFile(file)).formatFile(file)
}
func hasCommentFile(f *ast.File) bool {
for _, doc := range f.Docs {
if hasComment(doc.Body) {
return true
}
}
return false
}
func hasComment(n ast.Node) bool {
if n == nil {
return false
}
switch nn := n.(type) {
case *ast.DocumentNode:
return hasComment(nn.Body)
case *ast.NullNode:
return nn.Comment != nil
case *ast.BoolNode:
return nn.Comment != nil
case *ast.IntegerNode:
return nn.Comment != nil
case *ast.FloatNode:
return nn.Comment != nil
case *ast.StringNode:
return nn.Comment != nil
case *ast.InfinityNode:
return nn.Comment != nil
case *ast.NanNode:
return nn.Comment != nil
case *ast.LiteralNode:
return nn.Comment != nil
case *ast.DirectiveNode:
if nn.Comment != nil {
return true
}
for _, value := range nn.Values {
if hasComment(value) {
return true
}
}
case *ast.TagNode:
if nn.Comment != nil {
return true
}
return hasComment(nn.Value)
case *ast.MappingNode:
if nn.Comment != nil || nn.FootComment != nil {
return true
}
for _, value := range nn.Values {
if value.Comment != nil || value.FootComment != nil {
return true
}
if hasComment(value.Key) {
return true
}
if hasComment(value.Value) {
return true
}
}
case *ast.MappingKeyNode:
return nn.Comment != nil
case *ast.MergeKeyNode:
return nn.Comment != nil
case *ast.SequenceNode:
if nn.Comment != nil || nn.FootComment != nil {
return true
}
for _, entry := range nn.Entries {
if entry.Comment != nil || entry.HeadComment != nil || entry.LineComment != nil {
return true
}
if hasComment(entry.Value) {
return true
}
}
case *ast.AnchorNode:
if nn.Comment != nil {
return true
}
if hasComment(nn.Name) || hasComment(nn.Value) {
return true
}
case *ast.AliasNode:
if nn.Comment != nil {
return true
}
if hasComment(nn.Value) {
return true
}
}
return false
}
func getFirstToken(n ast.Node) *token.Token {
if n == nil {
return nil
}
switch nn := n.(type) {
case *ast.DocumentNode:
if nn.Start != nil {
return nn.Start
}
return getFirstToken(nn.Body)
case *ast.NullNode:
return nn.Token
case *ast.BoolNode:
return nn.Token
case *ast.IntegerNode:
return nn.Token
case *ast.FloatNode:
return nn.Token
case *ast.StringNode:
return nn.Token
case *ast.InfinityNode:
return nn.Token
case *ast.NanNode:
return nn.Token
case *ast.LiteralNode:
return nn.Start
case *ast.DirectiveNode:
return nn.Start
case *ast.TagNode:
return nn.Start
case *ast.MappingNode:
if nn.IsFlowStyle {
return nn.Start
}
if len(nn.Values) == 0 {
return nn.Start
}
return getFirstToken(nn.Values[0].Key)
case *ast.MappingKeyNode:
return nn.Start
case *ast.MergeKeyNode:
return nn.Token
case *ast.SequenceNode:
return nn.Start
case *ast.AnchorNode:
return nn.Start
case *ast.AliasNode:
return nn.Start
}
return nil
}
type Formatter struct {
existsComment bool
tokenToOriginMap map[*token.Token]string
anchorNodeMap map[string]ast.Node
}
func newFormatter(tk *token.Token, existsComment bool) *Formatter {
tokenToOriginMap := make(map[*token.Token]string)
for tk.Prev != nil {
tk = tk.Prev
}
tokenToOriginMap[tk] = tk.Origin
var origin string
for tk.Next != nil {
tk = tk.Next
if tk.Type == token.CommentType {
origin += strings.Repeat("\n", strings.Count(normalizeNewLineChars(tk.Origin), "\n"))
continue
}
origin += tk.Origin
tokenToOriginMap[tk] = origin
origin = ""
}
return &Formatter{
existsComment: existsComment,
tokenToOriginMap: tokenToOriginMap,
}
}
func getIndentNumByFirstLineToken(tk *token.Token) int {
defaultIndent := tk.Position.Column - 1
// key: value
// ^
// next
if tk.Type == token.SequenceEntryType {
// If the current token is the sequence entry.
// the indent is calculated from the column value of the current token.
return defaultIndent
}
// key: value
// ^
// next
if tk.Next != nil && tk.Next.Type == token.MappingValueType {
// If the current token is the key in the mapping-value,
// the indent is calculated from the column value of the current token.
return defaultIndent
}
if tk.Prev == nil {
return defaultIndent
}
prev := tk.Prev
// key: value
// ^
// prev
if prev.Type == token.MappingValueType {
// If the current token is the value in the mapping-value,
// the indent is calculated from the column value of the key two steps back.
if prev.Prev == nil {
return defaultIndent
}
return prev.Prev.Position.Column - 1
}
// - value
// ^
// prev
if prev.Type == token.SequenceEntryType {
// If the value is not a mapping-value and the previous token was a sequence entry,
// the indent is calculated using the column value of the sequence entry token.
return prev.Position.Column - 1
}
return defaultIndent
}
func (f *Formatter) format(n ast.Node) string {
return f.trimSpacePrefix(
f.trimIndentSpace(
getIndentNumByFirstLineToken(getFirstToken(n)),
f.trimNewLineCharPrefix(f.formatNode(n)),
),
)
}
func (f *Formatter) formatFile(file *ast.File) string {
if len(file.Docs) == 0 {
return ""
}
var ret string
for _, doc := range file.Docs {
ret += f.formatDocument(doc)
}
return ret
}
func (f *Formatter) origin(tk *token.Token) string {
if tk == nil {
return ""
}
if f.existsComment {
return tk.Origin
}
return f.tokenToOriginMap[tk]
}
func (f *Formatter) formatDocument(n *ast.DocumentNode) string {
return f.origin(n.Start) + f.formatNode(n.Body) + f.origin(n.End)
}
func (f *Formatter) formatNull(n *ast.NullNode) string {
return f.origin(n.Token) + f.formatCommentGroup(n.Comment)
}
func (f *Formatter) formatString(n *ast.StringNode) string {
return f.origin(n.Token) + f.formatCommentGroup(n.Comment)
}
func (f *Formatter) formatInteger(n *ast.IntegerNode) string {
return f.origin(n.Token) + f.formatCommentGroup(n.Comment)
}
func (f *Formatter) formatFloat(n *ast.FloatNode) string {
return f.origin(n.Token) + f.formatCommentGroup(n.Comment)
}
func (f *Formatter) formatBool(n *ast.BoolNode) string {
return f.origin(n.Token) + f.formatCommentGroup(n.Comment)
}
func (f *Formatter) formatInfinity(n *ast.InfinityNode) string {
return f.origin(n.Token) + f.formatCommentGroup(n.Comment)
}
func (f *Formatter) formatNan(n *ast.NanNode) string {
return f.origin(n.Token) + f.formatCommentGroup(n.Comment)
}
func (f *Formatter) formatLiteral(n *ast.LiteralNode) string {
return f.origin(n.Start) + f.formatCommentGroup(n.Comment) + f.origin(n.Value.Token)
}
func (f *Formatter) formatMergeKey(n *ast.MergeKeyNode) string {
return f.origin(n.Token)
}
func (f *Formatter) formatMappingValue(n *ast.MappingValueNode) string {
return f.formatCommentGroup(n.Comment) +
f.origin(n.Key.GetToken()) + ":" + f.formatCommentGroup(n.Key.GetComment()) + f.formatNode(n.Value) +
f.formatCommentGroup(n.FootComment)
}
func (f *Formatter) formatDirective(n *ast.DirectiveNode) string {
ret := f.origin(n.Start) + f.formatNode(n.Name)
for _, val := range n.Values {
ret += f.formatNode(val)
}
return ret
}
func (f *Formatter) formatMapping(n *ast.MappingNode) string {
var ret string
if n.IsFlowStyle {
ret = f.origin(n.Start)
}
ret += f.formatCommentGroup(n.Comment)
for _, value := range n.Values {
if value.CollectEntry != nil {
ret += f.origin(value.CollectEntry)
}
ret += f.formatMappingValue(value)
}
if n.IsFlowStyle {
ret += f.origin(n.End)
}
return ret
}
func (f *Formatter) formatTag(n *ast.TagNode) string {
return f.origin(n.Start) + f.formatNode(n.Value)
}
func (f *Formatter) formatMappingKey(n *ast.MappingKeyNode) string {
return f.origin(n.Start) + f.formatNode(n.Value)
}
func (f *Formatter) formatSequence(n *ast.SequenceNode) string {
var ret string
if n.IsFlowStyle {
ret = f.origin(n.Start)
}
if n.Comment != nil {
// add head comment.
ret += f.formatCommentGroup(n.Comment)
}
for _, entry := range n.Entries {
ret += f.formatNode(entry)
}
if n.IsFlowStyle {
ret += f.origin(n.End)
}
ret += f.formatCommentGroup(n.FootComment)
return ret
}
func (f *Formatter) formatSequenceEntry(n *ast.SequenceEntryNode) string {
return f.formatCommentGroup(n.HeadComment) + f.origin(n.Start) + f.formatCommentGroup(n.LineComment) + f.formatNode(n.Value)
}
func (f *Formatter) formatAnchor(n *ast.AnchorNode) string {
return f.origin(n.Start) + f.formatNode(n.Name) + f.formatNode(n.Value)
}
func (f *Formatter) formatAlias(n *ast.AliasNode) string {
if f.anchorNodeMap != nil {
anchorName := n.Value.GetToken().Value
node := f.anchorNodeMap[anchorName]
if node != nil {
formatted := f.formatNode(node)
// If formatted text contains newline characters, indentation needs to be considered.
if strings.Contains(formatted, "\n") {
// If the first character is not a newline, the first line should be output without indentation.
isIgnoredFirstLine := !strings.HasPrefix(formatted, "\n")
formatted = f.addIndentSpace(n.GetToken().Position.IndentNum, formatted, isIgnoredFirstLine)
}
return formatted
}
}
return f.origin(n.Start) + f.formatNode(n.Value)
}
func (f *Formatter) formatNode(n ast.Node) string {
switch nn := n.(type) {
case *ast.DocumentNode:
return f.formatDocument(nn)
case *ast.NullNode:
return f.formatNull(nn)
case *ast.BoolNode:
return f.formatBool(nn)
case *ast.IntegerNode:
return f.formatInteger(nn)
case *ast.FloatNode:
return f.formatFloat(nn)
case *ast.StringNode:
return f.formatString(nn)
case *ast.InfinityNode:
return f.formatInfinity(nn)
case *ast.NanNode:
return f.formatNan(nn)
case *ast.LiteralNode:
return f.formatLiteral(nn)
case *ast.DirectiveNode:
return f.formatDirective(nn)
case *ast.TagNode:
return f.formatTag(nn)
case *ast.MappingNode:
return f.formatMapping(nn)
case *ast.MappingKeyNode:
return f.formatMappingKey(nn)
case *ast.MappingValueNode:
return f.formatMappingValue(nn)
case *ast.MergeKeyNode:
return f.formatMergeKey(nn)
case *ast.SequenceNode:
return f.formatSequence(nn)
case *ast.SequenceEntryNode:
return f.formatSequenceEntry(nn)
case *ast.AnchorNode:
return f.formatAnchor(nn)
case *ast.AliasNode:
return f.formatAlias(nn)
}
return ""
}
func (f *Formatter) formatCommentGroup(g *ast.CommentGroupNode) string {
if g == nil {
return ""
}
var ret string
for _, cm := range g.Comments {
ret += f.formatComment(cm)
}
return ret
}
func (f *Formatter) formatComment(n *ast.CommentNode) string {
if n == nil {
return ""
}
return n.Token.Origin
}
// nolint: unused
func (f *Formatter) formatIndent(col int) string {
if col <= 1 {
return ""
}
return strings.Repeat(" ", col-1)
}
func (f *Formatter) trimNewLineCharPrefix(v string) string {
return strings.TrimLeftFunc(v, func(r rune) bool {
return r == '\n' || r == '\r'
})
}
func (f *Formatter) trimSpacePrefix(v string) string {
return strings.TrimLeftFunc(v, func(r rune) bool {
return r == ' '
})
}
func (f *Formatter) trimIndentSpace(trimIndentNum int, v string) string {
if trimIndentNum == 0 {
return v
}
lines := strings.Split(normalizeNewLineChars(v), "\n")
out := make([]string, 0, len(lines))
for _, line := range lines {
var cnt int
out = append(out, strings.TrimLeftFunc(line, func(r rune) bool {
cnt++
return r == ' ' && cnt <= trimIndentNum
}))
}
return strings.Join(out, "\n")
}
func (f *Formatter) addIndentSpace(indentNum int, v string, isIgnoredFirstLine bool) string {
if indentNum == 0 {
return v
}
indent := strings.Repeat(" ", indentNum)
lines := strings.Split(normalizeNewLineChars(v), "\n")
out := make([]string, 0, len(lines))
for idx, line := range lines {
if line == "" || (isIgnoredFirstLine && idx == 0) {
out = append(out, line)
continue
}
out = append(out, indent+line)
}
return strings.Join(out, "\n")
}
// normalizeNewLineChars normalize CRLF and CR to LF.
func normalizeNewLineChars(v string) string {
return strings.ReplaceAll(strings.ReplaceAll(v, "\r\n", "\n"), "\r", "\n")
}

View File

@@ -1,6 +1,7 @@
package yaml
import (
"context"
"io"
"reflect"
@@ -50,11 +51,10 @@ func Validator(v StructValidator) DecodeOption {
}
}
// Strict enable DisallowUnknownField and DisallowDuplicateKey
// Strict enable DisallowUnknownField
func Strict() DecodeOption {
return func(d *Decoder) error {
d.disallowUnknownField = true
d.disallowDuplicateKey = true
return nil
}
}
@@ -69,10 +69,10 @@ func DisallowUnknownField() DecodeOption {
}
}
// DisallowDuplicateKey causes an error when mapping keys that are duplicates
func DisallowDuplicateKey() DecodeOption {
// AllowDuplicateMapKey ignore syntax error when mapping keys that are duplicates.
func AllowDuplicateMapKey() DecodeOption {
return func(d *Decoder) error {
d.disallowDuplicateKey = true
d.allowDuplicateMapKey = true
return nil
}
}
@@ -102,20 +102,32 @@ func UseJSONUnmarshaler() DecodeOption {
func CustomUnmarshaler[T any](unmarshaler func(*T, []byte) error) DecodeOption {
return func(d *Decoder) error {
var typ *T
d.customUnmarshalerMap[reflect.TypeOf(typ)] = func(v interface{}, b []byte) error {
d.customUnmarshalerMap[reflect.TypeOf(typ)] = func(ctx context.Context, v interface{}, b []byte) error {
return unmarshaler(v.(*T), b)
}
return nil
}
}
// CustomUnmarshalerContext overrides any decoding process for the type specified in generics.
// Similar to CustomUnmarshaler, but allows passing a context to the unmarshaler function.
func CustomUnmarshalerContext[T any](unmarshaler func(context.Context, *T, []byte) error) DecodeOption {
return func(d *Decoder) error {
var typ *T
d.customUnmarshalerMap[reflect.TypeOf(typ)] = func(ctx context.Context, v interface{}, b []byte) error {
return unmarshaler(ctx, v.(*T), b)
}
return nil
}
}
// EncodeOption functional option type for Encoder
type EncodeOption func(e *Encoder) error
// Indent change indent number
func Indent(spaces int) EncodeOption {
return func(e *Encoder) error {
e.indent = spaces
e.indentNum = spaces
return nil
}
}
@@ -144,6 +156,18 @@ func Flow(isFlowStyle bool) EncodeOption {
}
}
// WithSmartAnchor when multiple map values share the same pointer,
// an anchor is automatically assigned to the first occurrence, and aliases are used for subsequent elements.
// The map key name is used as the anchor name by default.
// If key names conflict, a suffix is automatically added to avoid collisions.
// This is an experimental feature and cannot be used simultaneously with anchor tags.
func WithSmartAnchor() EncodeOption {
return func(e *Encoder) error {
e.enableSmartAnchor = true
return nil
}
}
// UseLiteralStyleIfMultiline causes encoding multiline strings with a literal syntax,
// no matter what characters they include
func UseLiteralStyleIfMultiline(useLiteralStyleIfMultiline bool) EncodeOption {
@@ -188,13 +212,54 @@ func UseJSONMarshaler() EncodeOption {
func CustomMarshaler[T any](marshaler func(T) ([]byte, error)) EncodeOption {
return func(e *Encoder) error {
var typ T
e.customMarshalerMap[reflect.TypeOf(typ)] = func(v interface{}) ([]byte, error) {
e.customMarshalerMap[reflect.TypeOf(typ)] = func(ctx context.Context, v interface{}) ([]byte, error) {
return marshaler(v.(T))
}
return nil
}
}
// CustomMarshalerContext overrides any encoding process for the type specified in generics.
// Similar to CustomMarshaler, but allows passing a context to the marshaler function.
func CustomMarshalerContext[T any](marshaler func(context.Context, T) ([]byte, error)) EncodeOption {
return func(e *Encoder) error {
var typ T
e.customMarshalerMap[reflect.TypeOf(typ)] = func(ctx context.Context, v interface{}) ([]byte, error) {
return marshaler(ctx, v.(T))
}
return nil
}
}
// AutoInt automatically converts floating-point numbers to integers when the fractional part is zero.
// For example, a value of 1.0 will be encoded as 1.
func AutoInt() EncodeOption {
return func(e *Encoder) error {
e.autoInt = true
return nil
}
}
// OmitEmpty behaves in the same way as the interpretation of the omitempty tag in the encoding/json library.
// set on all the fields.
// In the current implementation, the omitempty tag is not implemented in the same way as encoding/json,
// so please specify this option if you expect the same behavior.
func OmitEmpty() EncodeOption {
return func(e *Encoder) error {
e.omitEmpty = true
return nil
}
}
// OmitZero forces the encoder to assume an `omitzero` struct tag is
// set on all the fields. See `Marshal` commentary for the `omitzero` tag logic.
func OmitZero() EncodeOption {
return func(e *Encoder) error {
e.omitZero = true
return nil
}
}
// CommentPosition type of the position for comment.
type CommentPosition int

28
vendor/github.com/goccy/go-yaml/parser/color.go generated vendored Normal file
View File

@@ -0,0 +1,28 @@
package parser
import "fmt"
const (
colorFgHiBlack int = iota + 90
colorFgHiRed
colorFgHiGreen
colorFgHiYellow
colorFgHiBlue
colorFgHiMagenta
colorFgHiCyan
)
var colorTable = []int{
colorFgHiRed,
colorFgHiGreen,
colorFgHiYellow,
colorFgHiBlue,
colorFgHiMagenta,
colorFgHiCyan,
}
func colorize(idx int, content string) string {
colorIdx := idx % len(colorTable)
color := colorTable[colorIdx]
return fmt.Sprintf("\x1b[1;%dm", color) + content + "\x1b[22;0m"
}

View File

@@ -9,11 +9,15 @@ import (
// context context at parsing
type context struct {
parent *context
idx int
tokenRef *tokenRef
path string
isFlow bool
}
type tokenRef struct {
tokens []*Token
size int
tokens token.Tokens
path string
idx int
}
var pathSpecialChars = []string{
@@ -36,91 +40,28 @@ func normalizePath(path string) string {
return path
}
func (c *context) withChild(path string) *context {
ctx := c.copy()
path = normalizePath(path)
ctx.path += fmt.Sprintf(".%s", path)
return ctx
}
func (c *context) withIndex(idx uint) *context {
ctx := c.copy()
ctx.path += fmt.Sprintf("[%d]", idx)
return ctx
}
func (c *context) copy() *context {
return &context{
parent: c,
idx: c.idx,
size: c.size,
tokens: append(token.Tokens{}, c.tokens...),
path: c.path,
}
}
func (c *context) next() bool {
return c.idx < c.size
}
func (c *context) previousToken() *token.Token {
if c.idx > 0 {
return c.tokens[c.idx-1]
}
return nil
}
func (c *context) insertToken(idx int, tk *token.Token) {
if c.parent != nil {
c.parent.insertToken(idx, tk)
}
if c.size < idx {
return
}
if c.size == idx {
curToken := c.tokens[c.size-1]
tk.Next = curToken
curToken.Prev = tk
c.tokens = append(c.tokens, tk)
c.size = len(c.tokens)
return
}
curToken := c.tokens[idx]
tk.Next = curToken
curToken.Prev = tk
c.tokens = append(c.tokens[:idx+1], c.tokens[idx:]...)
c.tokens[idx] = tk
c.size = len(c.tokens)
}
func (c *context) currentToken() *token.Token {
if c.idx >= c.size {
func (c *context) currentToken() *Token {
if c.tokenRef.idx >= c.tokenRef.size {
return nil
}
return c.tokens[c.idx]
return c.tokenRef.tokens[c.tokenRef.idx]
}
func (c *context) nextToken() *token.Token {
if c.idx+1 >= c.size {
func (c *context) isComment() bool {
return c.currentToken().Type() == token.CommentType
}
func (c *context) nextToken() *Token {
if c.tokenRef.idx+1 >= c.tokenRef.size {
return nil
}
return c.tokens[c.idx+1]
return c.tokenRef.tokens[c.tokenRef.idx+1]
}
func (c *context) afterNextToken() *token.Token {
if c.idx+2 >= c.size {
return nil
}
return c.tokens[c.idx+2]
}
func (c *context) nextNotCommentToken() *token.Token {
for i := c.idx + 1; i < c.size; i++ {
tk := c.tokens[i]
if tk.Type == token.CommentType {
func (c *context) nextNotCommentToken() *Token {
for i := c.tokenRef.idx + 1; i < c.tokenRef.size; i++ {
tk := c.tokenRef.tokens[i]
if tk.Type() == token.CommentType {
continue
}
return tk
@@ -128,65 +69,119 @@ func (c *context) nextNotCommentToken() *token.Token {
return nil
}
func (c *context) afterNextNotCommentToken() *token.Token {
notCommentTokenCount := 0
for i := c.idx + 1; i < c.size; i++ {
tk := c.tokens[i]
if tk.Type == token.CommentType {
continue
}
notCommentTokenCount++
if notCommentTokenCount == 2 {
return tk
}
}
return nil
func (c *context) isTokenNotFound() bool {
return c.currentToken() == nil
}
func (c *context) isCurrentCommentToken() bool {
tk := c.currentToken()
if tk == nil {
return false
func (c *context) withGroup(g *TokenGroup) *context {
ctx := *c
ctx.tokenRef = &tokenRef{
tokens: g.Tokens,
size: len(g.Tokens),
}
return tk.Type == token.CommentType
return &ctx
}
func (c *context) progressIgnoreComment(num int) {
if c.parent != nil {
c.parent.progressIgnoreComment(num)
func (c *context) withChild(path string) *context {
ctx := *c
ctx.path = c.path + "." + normalizePath(path)
return &ctx
}
func (c *context) withIndex(idx uint) *context {
ctx := *c
ctx.path = c.path + "[" + fmt.Sprint(idx) + "]"
return &ctx
}
func (c *context) withFlow(isFlow bool) *context {
ctx := *c
ctx.isFlow = isFlow
return &ctx
}
func newContext() *context {
return &context{
path: "$",
}
if c.size <= c.idx+num {
c.idx = c.size
}
func (c *context) goNext() {
ref := c.tokenRef
if ref.size <= ref.idx+1 {
ref.idx = ref.size
} else {
c.idx += num
ref.idx++
}
}
func (c *context) progress(num int) {
if c.isCurrentCommentToken() {
func (c *context) next() bool {
return c.tokenRef.idx < c.tokenRef.size
}
func (c *context) insertNullToken(tk *Token) *Token {
nullToken := c.createImplicitNullToken(tk)
c.insertToken(nullToken)
c.goNext()
return nullToken
}
func (c *context) addNullValueToken(tk *Token) *Token {
nullToken := c.createImplicitNullToken(tk)
rawTk := nullToken.RawToken()
// add space for map or sequence value.
rawTk.Position.Column++
c.addToken(nullToken)
c.goNext()
return nullToken
}
func (c *context) createImplicitNullToken(base *Token) *Token {
pos := *(base.RawToken().Position)
pos.Column++
tk := token.New("null", " null", &pos)
tk.Type = token.ImplicitNullType
return &Token{Token: tk}
}
func (c *context) insertToken(tk *Token) {
ref := c.tokenRef
idx := ref.idx
if ref.size < idx {
return
}
c.progressIgnoreComment(num)
if ref.size == idx {
curToken := ref.tokens[ref.size-1]
tk.RawToken().Next = curToken.RawToken()
curToken.RawToken().Prev = tk.RawToken()
ref.tokens = append(ref.tokens, tk)
ref.size = len(ref.tokens)
return
}
curToken := ref.tokens[idx]
tk.RawToken().Next = curToken.RawToken()
curToken.RawToken().Prev = tk.RawToken()
ref.tokens = append(ref.tokens[:idx+1], ref.tokens[idx:]...)
ref.tokens[idx] = tk
ref.size = len(ref.tokens)
}
func newContext(tokens token.Tokens, mode Mode) *context {
filteredTokens := []*token.Token{}
if mode&ParseComments != 0 {
filteredTokens = tokens
} else {
for _, tk := range tokens {
if tk.Type == token.CommentType {
continue
}
// keep prev/next reference between tokens containing comments
// https://github.com/goccy/go-yaml/issues/254
filteredTokens = append(filteredTokens, tk)
}
}
return &context{
idx: 0,
size: len(filteredTokens),
tokens: token.Tokens(filteredTokens),
path: "$",
func (c *context) addToken(tk *Token) {
ref := c.tokenRef
lastTk := ref.tokens[ref.size-1]
if lastTk.Group != nil {
lastTk = lastTk.Group.Last()
}
lastTk.RawToken().Next = tk.RawToken()
tk.RawToken().Prev = lastTk.RawToken()
ref.tokens = append(ref.tokens, tk)
ref.size = len(ref.tokens)
}

257
vendor/github.com/goccy/go-yaml/parser/node.go generated vendored Normal file
View File

@@ -0,0 +1,257 @@
package parser
import (
"fmt"
"github.com/goccy/go-yaml/ast"
"github.com/goccy/go-yaml/internal/errors"
"github.com/goccy/go-yaml/token"
)
func newMappingNode(ctx *context, tk *Token, isFlow bool, values ...*ast.MappingValueNode) (*ast.MappingNode, error) {
node := ast.Mapping(tk.RawToken(), isFlow, values...)
node.SetPath(ctx.path)
return node, nil
}
func newMappingValueNode(ctx *context, colonTk, entryTk *Token, key ast.MapKeyNode, value ast.Node) (*ast.MappingValueNode, error) {
node := ast.MappingValue(colonTk.RawToken(), key, value)
node.SetPath(ctx.path)
node.CollectEntry = entryTk.RawToken()
if key.GetToken().Position.Line == value.GetToken().Position.Line {
// originally key was commented, but now that null value has been added, value must be commented.
if err := setLineComment(ctx, value, colonTk); err != nil {
return nil, err
}
// set line comment by colonTk or entryTk.
if err := setLineComment(ctx, value, entryTk); err != nil {
return nil, err
}
} else {
if err := setLineComment(ctx, key, colonTk); err != nil {
return nil, err
}
// set line comment by colonTk or entryTk.
if err := setLineComment(ctx, key, entryTk); err != nil {
return nil, err
}
}
return node, nil
}
func newMappingKeyNode(ctx *context, tk *Token) (*ast.MappingKeyNode, error) {
node := ast.MappingKey(tk.RawToken())
node.SetPath(ctx.path)
if err := setLineComment(ctx, node, tk); err != nil {
return nil, err
}
return node, nil
}
func newAnchorNode(ctx *context, tk *Token) (*ast.AnchorNode, error) {
node := ast.Anchor(tk.RawToken())
node.SetPath(ctx.path)
if err := setLineComment(ctx, node, tk); err != nil {
return nil, err
}
return node, nil
}
func newAliasNode(ctx *context, tk *Token) (*ast.AliasNode, error) {
node := ast.Alias(tk.RawToken())
node.SetPath(ctx.path)
if err := setLineComment(ctx, node, tk); err != nil {
return nil, err
}
return node, nil
}
func newDirectiveNode(ctx *context, tk *Token) (*ast.DirectiveNode, error) {
node := ast.Directive(tk.RawToken())
node.SetPath(ctx.path)
if err := setLineComment(ctx, node, tk); err != nil {
return nil, err
}
return node, nil
}
func newMergeKeyNode(ctx *context, tk *Token) (*ast.MergeKeyNode, error) {
node := ast.MergeKey(tk.RawToken())
node.SetPath(ctx.path)
if err := setLineComment(ctx, node, tk); err != nil {
return nil, err
}
return node, nil
}
func newNullNode(ctx *context, tk *Token) (*ast.NullNode, error) {
node := ast.Null(tk.RawToken())
node.SetPath(ctx.path)
if err := setLineComment(ctx, node, tk); err != nil {
return nil, err
}
return node, nil
}
func newBoolNode(ctx *context, tk *Token) (*ast.BoolNode, error) {
node := ast.Bool(tk.RawToken())
node.SetPath(ctx.path)
if err := setLineComment(ctx, node, tk); err != nil {
return nil, err
}
return node, nil
}
func newIntegerNode(ctx *context, tk *Token) (*ast.IntegerNode, error) {
node := ast.Integer(tk.RawToken())
node.SetPath(ctx.path)
if err := setLineComment(ctx, node, tk); err != nil {
return nil, err
}
return node, nil
}
func newFloatNode(ctx *context, tk *Token) (*ast.FloatNode, error) {
node := ast.Float(tk.RawToken())
node.SetPath(ctx.path)
if err := setLineComment(ctx, node, tk); err != nil {
return nil, err
}
return node, nil
}
func newInfinityNode(ctx *context, tk *Token) (*ast.InfinityNode, error) {
node := ast.Infinity(tk.RawToken())
node.SetPath(ctx.path)
if err := setLineComment(ctx, node, tk); err != nil {
return nil, err
}
return node, nil
}
func newNanNode(ctx *context, tk *Token) (*ast.NanNode, error) {
node := ast.Nan(tk.RawToken())
node.SetPath(ctx.path)
if err := setLineComment(ctx, node, tk); err != nil {
return nil, err
}
return node, nil
}
func newStringNode(ctx *context, tk *Token) (*ast.StringNode, error) {
node := ast.String(tk.RawToken())
node.SetPath(ctx.path)
if err := setLineComment(ctx, node, tk); err != nil {
return nil, err
}
return node, nil
}
func newLiteralNode(ctx *context, tk *Token) (*ast.LiteralNode, error) {
node := ast.Literal(tk.RawToken())
node.SetPath(ctx.path)
if err := setLineComment(ctx, node, tk); err != nil {
return nil, err
}
return node, nil
}
func newTagNode(ctx *context, tk *Token) (*ast.TagNode, error) {
node := ast.Tag(tk.RawToken())
node.SetPath(ctx.path)
if err := setLineComment(ctx, node, tk); err != nil {
return nil, err
}
return node, nil
}
func newSequenceNode(ctx *context, tk *Token, isFlow bool) (*ast.SequenceNode, error) {
node := ast.Sequence(tk.RawToken(), isFlow)
node.SetPath(ctx.path)
if err := setLineComment(ctx, node, tk); err != nil {
return nil, err
}
return node, nil
}
func newTagDefaultScalarValueNode(ctx *context, tag *token.Token) (ast.ScalarNode, error) {
pos := *(tag.Position)
pos.Column++
var (
tk *Token
node ast.ScalarNode
)
switch token.ReservedTagKeyword(tag.Value) {
case token.IntegerTag:
tk = &Token{Token: token.New("0", "0", &pos)}
n, err := newIntegerNode(ctx, tk)
if err != nil {
return nil, err
}
node = n
case token.FloatTag:
tk = &Token{Token: token.New("0", "0", &pos)}
n, err := newFloatNode(ctx, tk)
if err != nil {
return nil, err
}
node = n
case token.StringTag, token.BinaryTag, token.TimestampTag:
tk = &Token{Token: token.New("", "", &pos)}
n, err := newStringNode(ctx, tk)
if err != nil {
return nil, err
}
node = n
case token.BooleanTag:
tk = &Token{Token: token.New("false", "false", &pos)}
n, err := newBoolNode(ctx, tk)
if err != nil {
return nil, err
}
node = n
case token.NullTag:
tk = &Token{Token: token.New("null", "null", &pos)}
n, err := newNullNode(ctx, tk)
if err != nil {
return nil, err
}
node = n
default:
return nil, errors.ErrSyntax(fmt.Sprintf("cannot assign default value for %q tag", tag.Value), tag)
}
ctx.insertToken(tk)
ctx.goNext()
return node, nil
}
func setLineComment(ctx *context, node ast.Node, tk *Token) error {
if tk == nil || tk.LineComment == nil {
return nil
}
comment := ast.CommentGroup([]*token.Token{tk.LineComment})
comment.SetPath(ctx.path)
if err := node.SetComment(comment); err != nil {
return err
}
return nil
}
func setHeadComment(cm *ast.CommentGroupNode, value ast.Node) error {
if cm == nil {
return nil
}
switch n := value.(type) {
case *ast.MappingNode:
if len(n.Values) != 0 && value.GetComment() == nil {
cm.SetPath(n.Values[0].GetPath())
return n.Values[0].SetComment(cm)
}
case *ast.MappingValueNode:
cm.SetPath(n.GetPath())
return n.SetComment(cm)
}
cm.SetPath(value.GetPath())
return value.SetComment(cm)
}

12
vendor/github.com/goccy/go-yaml/parser/option.go generated vendored Normal file
View File

@@ -0,0 +1,12 @@
package parser
// Option represents parser's option.
type Option func(p *parser)
// AllowDuplicateMapKey allow the use of keys with the same name in the same map,
// but by default, this is not permitted.
func AllowDuplicateMapKey() Option {
return func(p *parser) {
p.allowDuplicateMapKey = true
}
}

View File

File diff suppressed because it is too large Load Diff

746
vendor/github.com/goccy/go-yaml/parser/token.go generated vendored Normal file
View File

@@ -0,0 +1,746 @@
package parser
import (
"fmt"
"os"
"strings"
"github.com/goccy/go-yaml/internal/errors"
"github.com/goccy/go-yaml/token"
)
type TokenGroupType int
const (
TokenGroupNone TokenGroupType = iota
TokenGroupDirective
TokenGroupDirectiveName
TokenGroupDocument
TokenGroupDocumentBody
TokenGroupAnchor
TokenGroupAnchorName
TokenGroupAlias
TokenGroupLiteral
TokenGroupFolded
TokenGroupScalarTag
TokenGroupMapKey
TokenGroupMapKeyValue
)
func (t TokenGroupType) String() string {
switch t {
case TokenGroupNone:
return "none"
case TokenGroupDirective:
return "directive"
case TokenGroupDirectiveName:
return "directive_name"
case TokenGroupDocument:
return "document"
case TokenGroupDocumentBody:
return "document_body"
case TokenGroupAnchor:
return "anchor"
case TokenGroupAnchorName:
return "anchor_name"
case TokenGroupAlias:
return "alias"
case TokenGroupLiteral:
return "literal"
case TokenGroupFolded:
return "folded"
case TokenGroupScalarTag:
return "scalar_tag"
case TokenGroupMapKey:
return "map_key"
case TokenGroupMapKeyValue:
return "map_key_value"
}
return "none"
}
type Token struct {
Token *token.Token
Group *TokenGroup
LineComment *token.Token
}
func (t *Token) RawToken() *token.Token {
if t == nil {
return nil
}
if t.Token != nil {
return t.Token
}
return t.Group.RawToken()
}
func (t *Token) Type() token.Type {
if t == nil {
return 0
}
if t.Token != nil {
return t.Token.Type
}
return t.Group.TokenType()
}
func (t *Token) GroupType() TokenGroupType {
if t == nil {
return TokenGroupNone
}
if t.Token != nil {
return TokenGroupNone
}
return t.Group.Type
}
func (t *Token) Line() int {
if t == nil {
return 0
}
if t.Token != nil {
return t.Token.Position.Line
}
return t.Group.Line()
}
func (t *Token) Column() int {
if t == nil {
return 0
}
if t.Token != nil {
return t.Token.Position.Column
}
return t.Group.Column()
}
func (t *Token) SetGroupType(typ TokenGroupType) {
if t.Group == nil {
return
}
t.Group.Type = typ
}
func (t *Token) Dump() {
ctx := new(groupTokenRenderContext)
if t.Token != nil {
fmt.Fprint(os.Stdout, t.Token.Value)
return
}
t.Group.dump(ctx)
fmt.Fprintf(os.Stdout, "\n")
}
func (t *Token) dump(ctx *groupTokenRenderContext) {
if t.Token != nil {
fmt.Fprint(os.Stdout, t.Token.Value)
return
}
t.Group.dump(ctx)
}
type groupTokenRenderContext struct {
num int
}
type TokenGroup struct {
Type TokenGroupType
Tokens []*Token
}
func (g *TokenGroup) First() *Token {
if len(g.Tokens) == 0 {
return nil
}
return g.Tokens[0]
}
func (g *TokenGroup) Last() *Token {
if len(g.Tokens) == 0 {
return nil
}
return g.Tokens[len(g.Tokens)-1]
}
func (g *TokenGroup) dump(ctx *groupTokenRenderContext) {
num := ctx.num
fmt.Fprint(os.Stdout, colorize(num, "("))
ctx.num++
for _, tk := range g.Tokens {
tk.dump(ctx)
}
fmt.Fprint(os.Stdout, colorize(num, ")"))
}
func (g *TokenGroup) RawToken() *token.Token {
if len(g.Tokens) == 0 {
return nil
}
return g.Tokens[0].RawToken()
}
func (g *TokenGroup) Line() int {
if len(g.Tokens) == 0 {
return 0
}
return g.Tokens[0].Line()
}
func (g *TokenGroup) Column() int {
if len(g.Tokens) == 0 {
return 0
}
return g.Tokens[0].Column()
}
func (g *TokenGroup) TokenType() token.Type {
if len(g.Tokens) == 0 {
return 0
}
return g.Tokens[0].Type()
}
func CreateGroupedTokens(tokens token.Tokens) ([]*Token, error) {
var err error
tks := newTokens(tokens)
tks = createLineCommentTokenGroups(tks)
tks, err = createLiteralAndFoldedTokenGroups(tks)
if err != nil {
return nil, err
}
tks, err = createAnchorAndAliasTokenGroups(tks)
if err != nil {
return nil, err
}
tks, err = createScalarTagTokenGroups(tks)
if err != nil {
return nil, err
}
tks, err = createAnchorWithScalarTagTokenGroups(tks)
if err != nil {
return nil, err
}
tks, err = createMapKeyTokenGroups(tks)
if err != nil {
return nil, err
}
tks = createMapKeyValueTokenGroups(tks)
tks, err = createDirectiveTokenGroups(tks)
if err != nil {
return nil, err
}
tks, err = createDocumentTokens(tks)
if err != nil {
return nil, err
}
return tks, nil
}
func newTokens(tks token.Tokens) []*Token {
ret := make([]*Token, 0, len(tks))
for _, tk := range tks {
ret = append(ret, &Token{Token: tk})
}
return ret
}
func createLineCommentTokenGroups(tokens []*Token) []*Token {
ret := make([]*Token, 0, len(tokens))
for i := 0; i < len(tokens); i++ {
tk := tokens[i]
switch tk.Type() {
case token.CommentType:
if i > 0 && tokens[i-1].Line() == tk.Line() {
tokens[i-1].LineComment = tk.RawToken()
} else {
ret = append(ret, tk)
}
default:
ret = append(ret, tk)
}
}
return ret
}
func createLiteralAndFoldedTokenGroups(tokens []*Token) ([]*Token, error) {
ret := make([]*Token, 0, len(tokens))
for i := 0; i < len(tokens); i++ {
tk := tokens[i]
switch tk.Type() {
case token.LiteralType:
tks := []*Token{tk}
if i+1 < len(tokens) {
tks = append(tks, tokens[i+1])
}
ret = append(ret, &Token{
Group: &TokenGroup{
Type: TokenGroupLiteral,
Tokens: tks,
},
})
i++
case token.FoldedType:
tks := []*Token{tk}
if i+1 < len(tokens) {
tks = append(tks, tokens[i+1])
}
ret = append(ret, &Token{
Group: &TokenGroup{
Type: TokenGroupFolded,
Tokens: tks,
},
})
i++
default:
ret = append(ret, tk)
}
}
return ret, nil
}
func createAnchorAndAliasTokenGroups(tokens []*Token) ([]*Token, error) {
ret := make([]*Token, 0, len(tokens))
for i := 0; i < len(tokens); i++ {
tk := tokens[i]
switch tk.Type() {
case token.AnchorType:
if i+1 >= len(tokens) {
return nil, errors.ErrSyntax("undefined anchor name", tk.RawToken())
}
if i+2 >= len(tokens) {
return nil, errors.ErrSyntax("undefined anchor value", tk.RawToken())
}
anchorName := &Token{
Group: &TokenGroup{
Type: TokenGroupAnchorName,
Tokens: []*Token{tk, tokens[i+1]},
},
}
valueTk := tokens[i+2]
if tk.Line() == valueTk.Line() && valueTk.Type() == token.SequenceEntryType {
return nil, errors.ErrSyntax("sequence entries are not allowed after anchor on the same line", valueTk.RawToken())
}
if tk.Line() == valueTk.Line() && isScalarType(valueTk) {
ret = append(ret, &Token{
Group: &TokenGroup{
Type: TokenGroupAnchor,
Tokens: []*Token{anchorName, valueTk},
},
})
i++
} else {
ret = append(ret, anchorName)
}
i++
case token.AliasType:
if i+1 == len(tokens) {
return nil, errors.ErrSyntax("undefined alias name", tk.RawToken())
}
ret = append(ret, &Token{
Group: &TokenGroup{
Type: TokenGroupAlias,
Tokens: []*Token{tk, tokens[i+1]},
},
})
i++
default:
ret = append(ret, tk)
}
}
return ret, nil
}
func createScalarTagTokenGroups(tokens []*Token) ([]*Token, error) {
ret := make([]*Token, 0, len(tokens))
for i := 0; i < len(tokens); i++ {
tk := tokens[i]
if tk.Type() != token.TagType {
ret = append(ret, tk)
continue
}
tag := tk.RawToken()
if strings.HasPrefix(tag.Value, "!!") {
// secondary tag.
switch token.ReservedTagKeyword(tag.Value) {
case token.IntegerTag, token.FloatTag, token.StringTag, token.BinaryTag, token.TimestampTag, token.BooleanTag, token.NullTag:
if len(tokens) <= i+1 {
ret = append(ret, tk)
continue
}
if tk.Line() != tokens[i+1].Line() {
ret = append(ret, tk)
continue
}
if tokens[i+1].GroupType() == TokenGroupAnchorName {
ret = append(ret, tk)
continue
}
if isScalarType(tokens[i+1]) {
ret = append(ret, &Token{
Group: &TokenGroup{
Type: TokenGroupScalarTag,
Tokens: []*Token{tk, tokens[i+1]},
},
})
i++
} else {
ret = append(ret, tk)
}
case token.MergeTag:
if len(tokens) <= i+1 {
ret = append(ret, tk)
continue
}
if tk.Line() != tokens[i+1].Line() {
ret = append(ret, tk)
continue
}
if tokens[i+1].GroupType() == TokenGroupAnchorName {
ret = append(ret, tk)
continue
}
if tokens[i+1].Type() != token.MergeKeyType {
return nil, errors.ErrSyntax("could not find merge key", tokens[i+1].RawToken())
}
ret = append(ret, &Token{
Group: &TokenGroup{
Type: TokenGroupScalarTag,
Tokens: []*Token{tk, tokens[i+1]},
},
})
i++
default:
ret = append(ret, tk)
}
} else {
if len(tokens) <= i+1 {
ret = append(ret, tk)
continue
}
if tk.Line() != tokens[i+1].Line() {
ret = append(ret, tk)
continue
}
if tokens[i+1].GroupType() == TokenGroupAnchorName {
ret = append(ret, tk)
continue
}
if isFlowType(tokens[i+1]) {
ret = append(ret, tk)
continue
}
ret = append(ret, &Token{
Group: &TokenGroup{
Type: TokenGroupScalarTag,
Tokens: []*Token{tk, tokens[i+1]},
},
})
i++
}
}
return ret, nil
}
func createAnchorWithScalarTagTokenGroups(tokens []*Token) ([]*Token, error) {
ret := make([]*Token, 0, len(tokens))
for i := 0; i < len(tokens); i++ {
tk := tokens[i]
switch tk.GroupType() {
case TokenGroupAnchorName:
if i+1 >= len(tokens) {
return nil, errors.ErrSyntax("undefined anchor value", tk.RawToken())
}
valueTk := tokens[i+1]
if tk.Line() == valueTk.Line() && valueTk.GroupType() == TokenGroupScalarTag {
ret = append(ret, &Token{
Group: &TokenGroup{
Type: TokenGroupAnchor,
Tokens: []*Token{tk, tokens[i+1]},
},
})
i++
} else {
ret = append(ret, tk)
}
default:
ret = append(ret, tk)
}
}
return ret, nil
}
func createMapKeyTokenGroups(tokens []*Token) ([]*Token, error) {
tks, err := createMapKeyByMappingKey(tokens)
if err != nil {
return nil, err
}
return createMapKeyByMappingValue(tks)
}
func createMapKeyByMappingKey(tokens []*Token) ([]*Token, error) {
ret := make([]*Token, 0, len(tokens))
for i := 0; i < len(tokens); i++ {
tk := tokens[i]
switch tk.Type() {
case token.MappingKeyType:
if i+1 >= len(tokens) {
return nil, errors.ErrSyntax("undefined map key", tk.RawToken())
}
ret = append(ret, &Token{
Group: &TokenGroup{
Type: TokenGroupMapKey,
Tokens: []*Token{tk, tokens[i+1]},
},
})
i++
default:
ret = append(ret, tk)
}
}
return ret, nil
}
func createMapKeyByMappingValue(tokens []*Token) ([]*Token, error) {
ret := make([]*Token, 0, len(tokens))
for i := 0; i < len(tokens); i++ {
tk := tokens[i]
switch tk.Type() {
case token.MappingValueType:
if i == 0 {
return nil, errors.ErrSyntax("unexpected key name", tk.RawToken())
}
mapKeyTk := tokens[i-1]
if isNotMapKeyType(mapKeyTk) {
return nil, errors.ErrSyntax("found an invalid key for this map", tokens[i].RawToken())
}
newTk := &Token{Token: mapKeyTk.Token, Group: mapKeyTk.Group}
mapKeyTk.Token = nil
mapKeyTk.Group = &TokenGroup{
Type: TokenGroupMapKey,
Tokens: []*Token{newTk, tk},
}
default:
ret = append(ret, tk)
}
}
return ret, nil
}
func createMapKeyValueTokenGroups(tokens []*Token) []*Token {
ret := make([]*Token, 0, len(tokens))
for i := 0; i < len(tokens); i++ {
tk := tokens[i]
switch tk.GroupType() {
case TokenGroupMapKey:
if len(tokens) <= i+1 {
ret = append(ret, tk)
continue
}
valueTk := tokens[i+1]
if tk.Line() != valueTk.Line() {
ret = append(ret, tk)
continue
}
if valueTk.GroupType() == TokenGroupAnchorName {
ret = append(ret, tk)
continue
}
if valueTk.Type() == token.TagType && valueTk.GroupType() != TokenGroupScalarTag {
ret = append(ret, tk)
continue
}
if isScalarType(valueTk) || valueTk.Type() == token.TagType {
ret = append(ret, &Token{
Group: &TokenGroup{
Type: TokenGroupMapKeyValue,
Tokens: []*Token{tk, valueTk},
},
})
i++
} else {
ret = append(ret, tk)
continue
}
default:
ret = append(ret, tk)
}
}
return ret
}
func createDirectiveTokenGroups(tokens []*Token) ([]*Token, error) {
ret := make([]*Token, 0, len(tokens))
for i := 0; i < len(tokens); i++ {
tk := tokens[i]
switch tk.Type() {
case token.DirectiveType:
if i+1 >= len(tokens) {
return nil, errors.ErrSyntax("undefined directive value", tk.RawToken())
}
directiveName := &Token{
Group: &TokenGroup{
Type: TokenGroupDirectiveName,
Tokens: []*Token{tk, tokens[i+1]},
},
}
i++
var valueTks []*Token
for j := i + 1; j < len(tokens); j++ {
if tokens[j].Line() != tk.Line() {
break
}
valueTks = append(valueTks, tokens[j])
i++
}
if i+1 >= len(tokens) || tokens[i+1].Type() != token.DocumentHeaderType {
return nil, errors.ErrSyntax("unexpected directive value. document not started", tk.RawToken())
}
if len(valueTks) != 0 {
ret = append(ret, &Token{
Group: &TokenGroup{
Type: TokenGroupDirective,
Tokens: append([]*Token{directiveName}, valueTks...),
},
})
} else {
ret = append(ret, directiveName)
}
default:
ret = append(ret, tk)
}
}
return ret, nil
}
func createDocumentTokens(tokens []*Token) ([]*Token, error) {
var ret []*Token
for i := 0; i < len(tokens); i++ {
tk := tokens[i]
switch tk.Type() {
case token.DocumentHeaderType:
if i != 0 {
ret = append(ret, &Token{
Group: &TokenGroup{Tokens: tokens[:i]},
})
}
if i+1 == len(tokens) {
// if current token is last token, add DocumentHeader only tokens to ret.
return append(ret, &Token{
Group: &TokenGroup{
Type: TokenGroupDocument,
Tokens: []*Token{tk},
},
}), nil
}
if tokens[i+1].Type() == token.DocumentHeaderType {
return append(ret, &Token{
Group: &TokenGroup{
Type: TokenGroupDocument,
Tokens: []*Token{tk},
},
}), nil
}
if tokens[i].Line() == tokens[i+1].Line() {
switch tokens[i+1].GroupType() {
case TokenGroupMapKey, TokenGroupMapKeyValue:
return nil, errors.ErrSyntax("value cannot be placed after document separator", tokens[i+1].RawToken())
}
switch tokens[i+1].Type() {
case token.SequenceEntryType:
return nil, errors.ErrSyntax("value cannot be placed after document separator", tokens[i+1].RawToken())
}
}
tks, err := createDocumentTokens(tokens[i+1:])
if err != nil {
return nil, err
}
if len(tks) != 0 {
tks[0].SetGroupType(TokenGroupDocument)
tks[0].Group.Tokens = append([]*Token{tk}, tks[0].Group.Tokens...)
return append(ret, tks...), nil
}
return append(ret, &Token{
Group: &TokenGroup{
Type: TokenGroupDocument,
Tokens: []*Token{tk},
},
}), nil
case token.DocumentEndType:
if i != 0 {
ret = append(ret, &Token{
Group: &TokenGroup{
Type: TokenGroupDocument,
Tokens: tokens[0 : i+1],
},
})
}
if i+1 == len(tokens) {
return ret, nil
}
if isScalarType(tokens[i+1]) {
return nil, errors.ErrSyntax("unexpected end content", tokens[i+1].RawToken())
}
tks, err := createDocumentTokens(tokens[i+1:])
if err != nil {
return nil, err
}
return append(ret, tks...), nil
}
}
return append(ret, &Token{
Group: &TokenGroup{
Type: TokenGroupDocument,
Tokens: tokens,
},
}), nil
}
func isScalarType(tk *Token) bool {
switch tk.GroupType() {
case TokenGroupMapKey, TokenGroupMapKeyValue:
return false
}
typ := tk.Type()
return typ == token.AnchorType ||
typ == token.AliasType ||
typ == token.LiteralType ||
typ == token.FoldedType ||
typ == token.NullType ||
typ == token.ImplicitNullType ||
typ == token.BoolType ||
typ == token.IntegerType ||
typ == token.BinaryIntegerType ||
typ == token.OctetIntegerType ||
typ == token.HexIntegerType ||
typ == token.FloatType ||
typ == token.InfinityType ||
typ == token.NanType ||
typ == token.StringType ||
typ == token.SingleQuoteType ||
typ == token.DoubleQuoteType
}
func isNotMapKeyType(tk *Token) bool {
typ := tk.Type()
return typ == token.DirectiveType ||
typ == token.DocumentHeaderType ||
typ == token.DocumentEndType ||
typ == token.CollectEntryType ||
typ == token.MappingStartType ||
typ == token.MappingValueType ||
typ == token.MappingEndType ||
typ == token.SequenceStartType ||
typ == token.SequenceEntryType ||
typ == token.SequenceEndType
}
func isFlowType(tk *Token) bool {
typ := tk.Type()
return typ == token.MappingStartType ||
typ == token.MappingEndType ||
typ == token.SequenceStartType ||
typ == token.SequenceEntryType
}

View File

@@ -8,7 +8,6 @@ import (
"strings"
"github.com/goccy/go-yaml/ast"
"github.com/goccy/go-yaml/internal/errors"
"github.com/goccy/go-yaml/parser"
"github.com/goccy/go-yaml/printer"
)
@@ -39,7 +38,7 @@ func PathString(s string) (*Path, error) {
case '.':
b, buf, c, err := parsePathDot(builder, buf, cursor)
if err != nil {
return nil, errors.Wrapf(err, "failed to parse path of dot")
return nil, err
}
length = len(buf)
builder = b
@@ -47,13 +46,13 @@ func PathString(s string) (*Path, error) {
case '[':
b, buf, c, err := parsePathIndex(builder, buf, cursor)
if err != nil {
return nil, errors.Wrapf(err, "failed to parse path of index")
return nil, err
}
length = len(buf)
builder = b
cursor = c
default:
return nil, errors.Wrapf(ErrInvalidPathString, "invalid path at %d", cursor)
return nil, fmt.Errorf("invalid path at %d: %w", cursor, ErrInvalidPathString)
}
}
return builder.Build(), nil
@@ -67,28 +66,31 @@ func parsePathRecursive(b *PathBuilder, buf []rune, cursor int) (*PathBuilder, [
c := buf[cursor]
switch c {
case '$':
return nil, nil, 0, errors.Wrapf(ErrInvalidPathString, "specified '$' after '..' character")
return nil, nil, 0, fmt.Errorf("specified '$' after '..' character: %w", ErrInvalidPathString)
case '*':
return nil, nil, 0, errors.Wrapf(ErrInvalidPathString, "specified '*' after '..' character")
return nil, nil, 0, fmt.Errorf("specified '*' after '..' character: %w", ErrInvalidPathString)
case '.', '[':
goto end
case ']':
return nil, nil, 0, errors.Wrapf(ErrInvalidPathString, "specified ']' after '..' character")
return nil, nil, 0, fmt.Errorf("specified ']' after '..' character: %w", ErrInvalidPathString)
}
}
end:
if start == cursor {
return nil, nil, 0, errors.Wrapf(ErrInvalidPathString, "not found recursive selector")
return nil, nil, 0, fmt.Errorf("not found recursive selector: %w", ErrInvalidPathString)
}
return b.Recursive(string(buf[start:cursor])), buf, cursor, nil
}
func parsePathDot(b *PathBuilder, buf []rune, cursor int) (*PathBuilder, []rune, int, error) {
if b.root == nil || b.node == nil {
return nil, nil, 0, fmt.Errorf("required '$' character at first: %w", ErrInvalidPathString)
}
length := len(buf)
if cursor+1 < length && buf[cursor+1] == '.' {
b, buf, c, err := parsePathRecursive(b, buf, cursor)
if err != nil {
return nil, nil, 0, errors.Wrapf(err, "failed to parse path of recursive")
return nil, nil, 0, err
}
return b, buf, c, nil
}
@@ -103,23 +105,27 @@ func parsePathDot(b *PathBuilder, buf []rune, cursor int) (*PathBuilder, []rune,
c := buf[cursor]
switch c {
case '$':
return nil, nil, 0, errors.Wrapf(ErrInvalidPathString, "specified '$' after '.' character")
return nil, nil, 0, fmt.Errorf("specified '$' after '.' character: %w", ErrInvalidPathString)
case '*':
return nil, nil, 0, errors.Wrapf(ErrInvalidPathString, "specified '*' after '.' character")
return nil, nil, 0, fmt.Errorf("specified '*' after '.' character: %w", ErrInvalidPathString)
case '.', '[':
goto end
case ']':
return nil, nil, 0, errors.Wrapf(ErrInvalidPathString, "specified ']' after '.' character")
return nil, nil, 0, fmt.Errorf("specified ']' after '.' character: %w", ErrInvalidPathString)
}
}
end:
if start == cursor {
return nil, nil, 0, errors.Wrapf(ErrInvalidPathString, "cloud not find by empty key")
return nil, nil, 0, fmt.Errorf("could not find by empty key: %w", ErrInvalidPathString)
}
return b.child(string(buf[start:cursor])), buf, cursor, nil
}
func parseQuotedKey(b *PathBuilder, buf []rune, cursor int) (*PathBuilder, []rune, int, error) {
if b.root == nil || b.node == nil {
return nil, nil, 0, fmt.Errorf("required '$' character at first: %w", ErrInvalidPathString)
}
cursor++ // skip single quote
start := cursor
length := len(buf)
@@ -136,31 +142,35 @@ func parseQuotedKey(b *PathBuilder, buf []rune, cursor int) (*PathBuilder, []run
}
end:
if !foundEndDelim {
return nil, nil, 0, errors.Wrapf(ErrInvalidPathString, "could not find end delimiter for key")
return nil, nil, 0, fmt.Errorf("could not find end delimiter for key: %w", ErrInvalidPathString)
}
if start == cursor {
return nil, nil, 0, errors.Wrapf(ErrInvalidPathString, "could not find by empty key")
return nil, nil, 0, fmt.Errorf("could not find by empty key: %w", ErrInvalidPathString)
}
selector := buf[start:cursor]
cursor++
if cursor < length {
switch buf[cursor] {
case '$':
return nil, nil, 0, errors.Wrapf(ErrInvalidPathString, "specified '$' after '.' character")
return nil, nil, 0, fmt.Errorf("specified '$' after '.' character: %w", ErrInvalidPathString)
case '*':
return nil, nil, 0, errors.Wrapf(ErrInvalidPathString, "specified '*' after '.' character")
return nil, nil, 0, fmt.Errorf("specified '*' after '.' character: %w", ErrInvalidPathString)
case ']':
return nil, nil, 0, errors.Wrapf(ErrInvalidPathString, "specified ']' after '.' character")
return nil, nil, 0, fmt.Errorf("specified ']' after '.' character: %w", ErrInvalidPathString)
}
}
return b.child(string(selector)), buf, cursor, nil
}
func parsePathIndex(b *PathBuilder, buf []rune, cursor int) (*PathBuilder, []rune, int, error) {
if b.root == nil || b.node == nil {
return nil, nil, 0, fmt.Errorf("required '$' character at first: %w", ErrInvalidPathString)
}
length := len(buf)
cursor++ // skip '[' character
if length <= cursor {
return nil, nil, 0, errors.Wrapf(ErrInvalidPathString, "unexpected end of YAML Path")
return nil, nil, 0, fmt.Errorf("unexpected end of YAML Path: %w", ErrInvalidPathString)
}
c := buf[cursor]
switch c {
@@ -176,7 +186,7 @@ func parsePathIndex(b *PathBuilder, buf []rune, cursor int) (*PathBuilder, []run
break
}
if buf[cursor] != ']' {
return nil, nil, 0, errors.Wrapf(ErrInvalidPathString, "invalid character %s at %d", string(buf[cursor]), cursor)
return nil, nil, 0, fmt.Errorf("invalid character %s at %d: %w", string(buf[cursor]), cursor, ErrInvalidPathString)
}
numOrAll := string(buf[start:cursor])
if numOrAll == "*" {
@@ -184,11 +194,11 @@ func parsePathIndex(b *PathBuilder, buf []rune, cursor int) (*PathBuilder, []run
}
num, err := strconv.ParseInt(numOrAll, 10, 64)
if err != nil {
return nil, nil, 0, errors.Wrapf(err, "failed to parse number")
return nil, nil, 0, err
}
return b.Index(uint(num)), buf, cursor + 1, nil
}
return nil, nil, 0, errors.Wrapf(ErrInvalidPathString, "invalid character %s at %d", c, cursor)
return nil, nil, 0, fmt.Errorf("invalid character %q at %d: %w", c, cursor, ErrInvalidPathString)
}
// Path represent YAMLPath ( like a JSONPath ).
@@ -205,10 +215,10 @@ func (p *Path) String() string {
func (p *Path) Read(r io.Reader, v interface{}) error {
node, err := p.ReadNode(r)
if err != nil {
return errors.Wrapf(err, "failed to read node")
return err
}
if err := Unmarshal([]byte(node.String()), v); err != nil {
return errors.Wrapf(err, "failed to unmarshal")
return err
}
return nil
}
@@ -220,15 +230,15 @@ func (p *Path) ReadNode(r io.Reader) (ast.Node, error) {
}
var buf bytes.Buffer
if _, err := io.Copy(&buf, r); err != nil {
return nil, errors.Wrapf(err, "failed to copy from reader")
return nil, err
}
f, err := parser.ParseBytes(buf.Bytes(), 0)
if err != nil {
return nil, errors.Wrapf(err, "failed to parse yaml")
return nil, err
}
node, err := p.FilterFile(f)
if err != nil {
return nil, errors.Wrapf(err, "failed to filter from ast.File")
return nil, err
}
return node, nil
}
@@ -237,10 +247,10 @@ func (p *Path) ReadNode(r io.Reader) (ast.Node, error) {
func (p *Path) Filter(target, v interface{}) error {
b, err := Marshal(target)
if err != nil {
return errors.Wrapf(err, "failed to marshal target value")
return err
}
if err := p.Read(bytes.NewBuffer(b), v); err != nil {
return errors.Wrapf(err, "failed to read")
return err
}
return nil
}
@@ -250,20 +260,23 @@ func (p *Path) FilterFile(f *ast.File) (ast.Node, error) {
for _, doc := range f.Docs {
node, err := p.FilterNode(doc.Body)
if err != nil {
return nil, errors.Wrapf(err, "failed to filter node by path ( %s )", p.node)
return nil, err
}
if node != nil {
return node, nil
}
}
return nil, errors.Wrapf(ErrNotFoundNode, "failed to find path ( %s )", p.node)
return nil, fmt.Errorf("failed to find path ( %s ): %w", p.node, ErrNotFoundNode)
}
// FilterNode filter from node by YAMLPath.
func (p *Path) FilterNode(node ast.Node) (ast.Node, error) {
if node == nil {
return nil, nil
}
n, err := p.node.filter(node)
if err != nil {
return nil, errors.Wrapf(err, "failed to filter node by path ( %s )", p.node)
return nil, err
}
return n, nil
}
@@ -272,14 +285,14 @@ func (p *Path) FilterNode(node ast.Node) (ast.Node, error) {
func (p *Path) MergeFromReader(dst *ast.File, src io.Reader) error {
var buf bytes.Buffer
if _, err := io.Copy(&buf, src); err != nil {
return errors.Wrapf(err, "failed to copy from reader")
return err
}
file, err := parser.ParseBytes(buf.Bytes(), 0)
if err != nil {
return errors.Wrapf(err, "failed to parse")
return err
}
if err := p.MergeFromFile(dst, file); err != nil {
return errors.Wrapf(err, "failed to merge file")
return err
}
return nil
}
@@ -288,11 +301,11 @@ func (p *Path) MergeFromReader(dst *ast.File, src io.Reader) error {
func (p *Path) MergeFromFile(dst *ast.File, src *ast.File) error {
base, err := p.FilterFile(dst)
if err != nil {
return errors.Wrapf(err, "failed to filter file")
return err
}
for _, doc := range src.Docs {
if err := ast.Merge(base, doc); err != nil {
return errors.Wrapf(err, "failed to merge")
return err
}
}
return nil
@@ -302,10 +315,10 @@ func (p *Path) MergeFromFile(dst *ast.File, src *ast.File) error {
func (p *Path) MergeFromNode(dst *ast.File, src ast.Node) error {
base, err := p.FilterFile(dst)
if err != nil {
return errors.Wrapf(err, "failed to filter file")
return err
}
if err := ast.Merge(base, src); err != nil {
return errors.Wrapf(err, "failed to merge")
return err
}
return nil
}
@@ -314,14 +327,14 @@ func (p *Path) MergeFromNode(dst *ast.File, src ast.Node) error {
func (p *Path) ReplaceWithReader(dst *ast.File, src io.Reader) error {
var buf bytes.Buffer
if _, err := io.Copy(&buf, src); err != nil {
return errors.Wrapf(err, "failed to copy from reader")
return err
}
file, err := parser.ParseBytes(buf.Bytes(), 0)
if err != nil {
return errors.Wrapf(err, "failed to parse")
return err
}
if err := p.ReplaceWithFile(dst, file); err != nil {
return errors.Wrapf(err, "failed to replace file")
return err
}
return nil
}
@@ -330,7 +343,7 @@ func (p *Path) ReplaceWithReader(dst *ast.File, src io.Reader) error {
func (p *Path) ReplaceWithFile(dst *ast.File, src *ast.File) error {
for _, doc := range src.Docs {
if err := p.ReplaceWithNode(dst, doc); err != nil {
return errors.Wrapf(err, "failed to replace file by path ( %s )", p.node)
return err
}
}
return nil
@@ -343,7 +356,7 @@ func (p *Path) ReplaceWithNode(dst *ast.File, node ast.Node) error {
node = node.(*ast.DocumentNode).Body
}
if err := p.node.replace(doc.Body, node); err != nil {
return errors.Wrapf(err, "failed to replace node by path ( %s )", p.node)
return err
}
}
return nil
@@ -472,7 +485,7 @@ func (n *rootNode) filter(node ast.Node) (ast.Node, error) {
}
filtered, err := n.child.filter(node)
if err != nil {
return nil, errors.Wrapf(err, "failed to filter")
return nil, err
}
return filtered, nil
}
@@ -482,7 +495,7 @@ func (n *rootNode) replace(node ast.Node, target ast.Node) error {
return nil
}
if err := n.child.replace(node, target); err != nil {
return errors.Wrapf(err, "failed to replace")
return err
}
return nil
}
@@ -514,7 +527,7 @@ func (n *selectorNode) filter(node ast.Node) (ast.Node, error) {
var err error
key, err = strconv.Unquote(key)
if err != nil {
return nil, errors.Wrapf(err, "failed to unquote")
return nil, err
}
case '\'':
if len(key) > 1 && key[len(key)-1] == '\'' {
@@ -528,13 +541,13 @@ func (n *selectorNode) filter(node ast.Node) (ast.Node, error) {
}
filtered, err := n.child.filter(value.Value)
if err != nil {
return nil, errors.Wrapf(err, "failed to filter")
return nil, err
}
return filtered, nil
}
}
case ast.MappingValueType:
value := node.(*ast.MappingValueNode)
value, _ := node.(*ast.MappingValueNode)
key := value.Key.GetToken().Value
if key == selector {
if n.child == nil {
@@ -542,12 +555,12 @@ func (n *selectorNode) filter(node ast.Node) (ast.Node, error) {
}
filtered, err := n.child.filter(value.Value)
if err != nil {
return nil, errors.Wrapf(err, "failed to filter")
return nil, err
}
return filtered, nil
}
default:
return nil, errors.Wrapf(ErrInvalidQuery, "expected node type is map or map value. but got %s", node.Type())
return nil, fmt.Errorf("expected node type is map or map value. but got %s: %w", node.Type(), ErrInvalidQuery)
}
return nil, nil
}
@@ -559,11 +572,11 @@ func (n *selectorNode) replaceMapValue(value *ast.MappingValueNode, target ast.N
}
if n.child == nil {
if err := value.Replace(target); err != nil {
return errors.Wrapf(err, "failed to replace")
return err
}
} else {
if err := n.child.replace(value.Value, target); err != nil {
return errors.Wrapf(err, "failed to replace")
return err
}
}
return nil
@@ -574,16 +587,16 @@ func (n *selectorNode) replace(node ast.Node, target ast.Node) error {
case ast.MappingType:
for _, value := range node.(*ast.MappingNode).Values {
if err := n.replaceMapValue(value, target); err != nil {
return errors.Wrapf(err, "failed to replace map value")
return err
}
}
case ast.MappingValueType:
value := node.(*ast.MappingValueNode)
value, _ := node.(*ast.MappingValueNode)
if err := n.replaceMapValue(value, target); err != nil {
return errors.Wrapf(err, "failed to replace map value")
return err
}
default:
return errors.Wrapf(ErrInvalidQuery, "expected node type is map or map value. but got %s", node.Type())
return fmt.Errorf("expected node type is map or map value. but got %s: %w", node.Type(), ErrInvalidQuery)
}
return nil
}
@@ -612,11 +625,11 @@ func newIndexNode(selector uint) *indexNode {
func (n *indexNode) filter(node ast.Node) (ast.Node, error) {
if node.Type() != ast.SequenceType {
return nil, errors.Wrapf(ErrInvalidQuery, "expected sequence type node. but got %s", node.Type())
return nil, fmt.Errorf("expected sequence type node. but got %s: %w", node.Type(), ErrInvalidQuery)
}
sequence := node.(*ast.SequenceNode)
sequence, _ := node.(*ast.SequenceNode)
if n.selector >= uint(len(sequence.Values)) {
return nil, errors.Wrapf(ErrInvalidQuery, "expected index is %d. but got sequences has %d items", n.selector, sequence.Values)
return nil, fmt.Errorf("expected index is %d. but got sequences has %d items: %w", n.selector, len(sequence.Values), ErrInvalidQuery)
}
value := sequence.Values[n.selector]
if n.child == nil {
@@ -624,27 +637,27 @@ func (n *indexNode) filter(node ast.Node) (ast.Node, error) {
}
filtered, err := n.child.filter(value)
if err != nil {
return nil, errors.Wrapf(err, "failed to filter")
return nil, err
}
return filtered, nil
}
func (n *indexNode) replace(node ast.Node, target ast.Node) error {
if node.Type() != ast.SequenceType {
return errors.Wrapf(ErrInvalidQuery, "expected sequence type node. but got %s", node.Type())
return fmt.Errorf("expected sequence type node. but got %s: %w", node.Type(), ErrInvalidQuery)
}
sequence := node.(*ast.SequenceNode)
sequence, _ := node.(*ast.SequenceNode)
if n.selector >= uint(len(sequence.Values)) {
return errors.Wrapf(ErrInvalidQuery, "expected index is %d. but got sequences has %d items", n.selector, sequence.Values)
return fmt.Errorf("expected index is %d. but got sequences has %d items: %w", n.selector, len(sequence.Values), ErrInvalidQuery)
}
if n.child == nil {
if err := sequence.Replace(int(n.selector), target); err != nil {
return errors.Wrapf(err, "failed to replace")
return err
}
return nil
}
if err := n.child.replace(sequence.Values[n.selector], target); err != nil {
return errors.Wrapf(err, "failed to replace")
return err
}
return nil
}
@@ -677,9 +690,9 @@ func (n *indexAllNode) String() string {
func (n *indexAllNode) filter(node ast.Node) (ast.Node, error) {
if node.Type() != ast.SequenceType {
return nil, errors.Wrapf(ErrInvalidQuery, "expected sequence type node. but got %s", node.Type())
return nil, fmt.Errorf("expected sequence type node. but got %s: %w", node.Type(), ErrInvalidQuery)
}
sequence := node.(*ast.SequenceNode)
sequence, _ := node.(*ast.SequenceNode)
if n.child == nil {
return sequence, nil
}
@@ -688,7 +701,7 @@ func (n *indexAllNode) filter(node ast.Node) (ast.Node, error) {
for _, value := range sequence.Values {
filtered, err := n.child.filter(value)
if err != nil {
return nil, errors.Wrapf(err, "failed to filter")
return nil, err
}
out.Values = append(out.Values, filtered)
}
@@ -697,20 +710,20 @@ func (n *indexAllNode) filter(node ast.Node) (ast.Node, error) {
func (n *indexAllNode) replace(node ast.Node, target ast.Node) error {
if node.Type() != ast.SequenceType {
return errors.Wrapf(ErrInvalidQuery, "expected sequence type node. but got %s", node.Type())
return fmt.Errorf("expected sequence type node. but got %s: %w", node.Type(), ErrInvalidQuery)
}
sequence := node.(*ast.SequenceNode)
sequence, _ := node.(*ast.SequenceNode)
if n.child == nil {
for idx := range sequence.Values {
if err := sequence.Replace(idx, target); err != nil {
return errors.Wrapf(err, "failed to replace")
return err
}
}
return nil
}
for _, value := range sequence.Values {
if err := n.child.replace(value, target); err != nil {
return errors.Wrapf(err, "failed to replace")
return err
}
}
return nil
@@ -743,7 +756,7 @@ func (n *recursiveNode) filterNode(node ast.Node) (*ast.SequenceNode, error) {
for _, value := range typedNode.Values {
seq, err := n.filterNode(value)
if err != nil {
return nil, errors.Wrapf(err, "failed to filter")
return nil, err
}
sequence.Values = append(sequence.Values, seq.Values...)
}
@@ -754,14 +767,14 @@ func (n *recursiveNode) filterNode(node ast.Node) (*ast.SequenceNode, error) {
}
seq, err := n.filterNode(typedNode.Value)
if err != nil {
return nil, errors.Wrapf(err, "failed to filter")
return nil, err
}
sequence.Values = append(sequence.Values, seq.Values...)
case *ast.SequenceNode:
for _, value := range typedNode.Values {
seq, err := n.filterNode(value)
if err != nil {
return nil, errors.Wrapf(err, "failed to filter")
return nil, err
}
sequence.Values = append(sequence.Values, seq.Values...)
}
@@ -772,7 +785,7 @@ func (n *recursiveNode) filterNode(node ast.Node) (*ast.SequenceNode, error) {
func (n *recursiveNode) filter(node ast.Node) (ast.Node, error) {
sequence, err := n.filterNode(node)
if err != nil {
return nil, errors.Wrapf(err, "failed to filter")
return nil, err
}
sequence.Start = node.GetToken()
return sequence, nil
@@ -783,23 +796,23 @@ func (n *recursiveNode) replaceNode(node ast.Node, target ast.Node) error {
case *ast.MappingNode:
for _, value := range typedNode.Values {
if err := n.replaceNode(value, target); err != nil {
return errors.Wrapf(err, "failed to replace")
return err
}
}
case *ast.MappingValueNode:
key := typedNode.Key.GetToken().Value
if n.selector == key {
if err := typedNode.Replace(target); err != nil {
return errors.Wrapf(err, "failed to replace")
return err
}
}
if err := n.replaceNode(typedNode.Value, target); err != nil {
return errors.Wrapf(err, "failed to replace")
return err
}
case *ast.SequenceNode:
for _, value := range typedNode.Values {
if err := n.replaceNode(value, target); err != nil {
return errors.Wrapf(err, "failed to replace")
return err
}
}
}
@@ -808,7 +821,7 @@ func (n *recursiveNode) replaceNode(node ast.Node, target ast.Node) error {
func (n *recursiveNode) replace(node ast.Node, target ast.Node) error {
if err := n.replaceNode(node, target); err != nil {
return errors.Wrapf(err, "failed to replace")
return err
}
return nil
}

83
vendor/github.com/goccy/go-yaml/printer/color.go generated vendored Normal file
View File

@@ -0,0 +1,83 @@
// This source inspired by https://github.com/fatih/color.
package printer
import (
"fmt"
"strings"
)
type ColorAttribute int
const (
ColorReset ColorAttribute = iota
ColorBold
ColorFaint
ColorItalic
ColorUnderline
ColorBlinkSlow
ColorBlinkRapid
ColorReverseVideo
ColorConcealed
ColorCrossedOut
)
const (
ColorFgHiBlack ColorAttribute = iota + 90
ColorFgHiRed
ColorFgHiGreen
ColorFgHiYellow
ColorFgHiBlue
ColorFgHiMagenta
ColorFgHiCyan
ColorFgHiWhite
)
const (
ColorResetBold ColorAttribute = iota + 22
ColorResetItalic
ColorResetUnderline
ColorResetBlinking
ColorResetReversed
ColorResetConcealed
ColorResetCrossedOut
)
const escape = "\x1b"
var colorResetMap = map[ColorAttribute]ColorAttribute{
ColorBold: ColorResetBold,
ColorFaint: ColorResetBold,
ColorItalic: ColorResetItalic,
ColorUnderline: ColorResetUnderline,
ColorBlinkSlow: ColorResetBlinking,
ColorBlinkRapid: ColorResetBlinking,
ColorReverseVideo: ColorResetReversed,
ColorConcealed: ColorResetConcealed,
ColorCrossedOut: ColorResetCrossedOut,
}
func format(attrs ...ColorAttribute) string {
format := make([]string, 0, len(attrs))
for _, attr := range attrs {
format = append(format, fmt.Sprint(attr))
}
return fmt.Sprintf("%s[%sm", escape, strings.Join(format, ";"))
}
func unformat(attrs ...ColorAttribute) string {
format := make([]string, len(attrs))
for _, attr := range attrs {
v := fmt.Sprint(ColorReset)
reset, exists := colorResetMap[attr]
if exists {
v = fmt.Sprint(reset)
}
format = append(format, v)
}
return fmt.Sprintf("%s[%sm", escape, strings.Join(format, ";"))
}
func colorize(msg string, attrs ...ColorAttribute) string {
return format(attrs...) + msg + unformat(attrs...)
}

View File

@@ -5,7 +5,6 @@ import (
"math"
"strings"
"github.com/fatih/color"
"github.com/goccy/go-yaml/ast"
"github.com/goccy/go-yaml/token"
)
@@ -29,6 +28,7 @@ type Printer struct {
Bool PrintFunc
String PrintFunc
Number PrintFunc
Comment PrintFunc
}
func defaultLineNumberFormat(num int) string {
@@ -82,6 +82,11 @@ func (p *Printer) property(tk *token.Token) *Property {
return p.Number()
}
return prop
case token.CommentType:
if p.Comment != nil {
return p.Comment()
}
return prop
default:
}
return prop
@@ -144,47 +149,47 @@ func (p *Printer) PrintNode(node ast.Node) []byte {
return []byte(fmt.Sprintf("%+v\n", node))
}
const escape = "\x1b"
func format(attr color.Attribute) string {
return fmt.Sprintf("%s[%dm", escape, attr)
}
func (p *Printer) setDefaultColorSet() {
p.Bool = func() *Property {
return &Property{
Prefix: format(color.FgHiMagenta),
Suffix: format(color.Reset),
Prefix: format(ColorFgHiMagenta),
Suffix: format(ColorReset),
}
}
p.Number = func() *Property {
return &Property{
Prefix: format(color.FgHiMagenta),
Suffix: format(color.Reset),
Prefix: format(ColorFgHiMagenta),
Suffix: format(ColorReset),
}
}
p.MapKey = func() *Property {
return &Property{
Prefix: format(color.FgHiCyan),
Suffix: format(color.Reset),
Prefix: format(ColorFgHiCyan),
Suffix: format(ColorReset),
}
}
p.Anchor = func() *Property {
return &Property{
Prefix: format(color.FgHiYellow),
Suffix: format(color.Reset),
Prefix: format(ColorFgHiYellow),
Suffix: format(ColorReset),
}
}
p.Alias = func() *Property {
return &Property{
Prefix: format(color.FgHiYellow),
Suffix: format(color.Reset),
Prefix: format(ColorFgHiYellow),
Suffix: format(ColorReset),
}
}
p.String = func() *Property {
return &Property{
Prefix: format(color.FgHiGreen),
Suffix: format(color.Reset),
Prefix: format(ColorFgHiGreen),
Suffix: format(ColorReset),
}
}
p.Comment = func() *Property {
return &Property{
Prefix: format(ColorFgHiBlack),
Suffix: format(ColorReset),
}
}
}
@@ -192,9 +197,9 @@ func (p *Printer) setDefaultColorSet() {
func (p *Printer) PrintErrorMessage(msg string, isColored bool) string {
if isColored {
return fmt.Sprintf("%s%s%s",
format(color.FgHiRed),
format(ColorFgHiRed),
msg,
format(color.Reset),
format(ColorReset),
)
}
return msg
@@ -246,10 +251,7 @@ func (p *Printer) isNewLineLastChar(s string) bool {
}
func (p *Printer) printBeforeTokens(tk *token.Token, minLine, extLine int) token.Tokens {
for {
if tk.Prev == nil {
break
}
for tk.Prev != nil {
if tk.Prev.Position.Line < minLine {
break
}
@@ -317,8 +319,7 @@ func (p *Printer) setupErrorTokenFormat(annotateLine int, isColored bool) {
p.LineNumber = true
p.LineNumberFormat = func(num int) string {
if isColored {
fn := color.New(color.Bold, color.FgHiWhite).SprintFunc()
return fn(prefix(annotateLine, num))
return colorize(prefix(annotateLine, num), ColorBold, ColorFgHiWhite)
}
return prefix(annotateLine, num)
}

View File

@@ -1,13 +1,14 @@
package scanner
import (
"errors"
"strconv"
"strings"
"sync"
"github.com/goccy/go-yaml/token"
)
const whitespace = ' '
// Context context at scanning
type Context struct {
idx int
@@ -18,11 +19,20 @@ type Context struct {
buf []rune
obuf []rune
tokens token.Tokens
isRawFolded bool
isLiteral bool
isFolded bool
isSingleLine bool
literalOpt string
mstate *MultiLineState
}
type MultiLineState struct {
opt string
firstLineIndentColumn int
prevLineIndentColumn int
lineIndentColumn int
lastNotSpaceOnlyLineIndentColumn int
spaceOnlyIndentColumn int
foldedNewLine bool
isRawFolded bool
isLiteral bool
isFolded bool
}
var (
@@ -35,14 +45,13 @@ var (
func createContext() *Context {
return &Context{
idx: 0,
tokens: token.Tokens{},
isSingleLine: true,
idx: 0,
tokens: token.Tokens{},
}
}
func newContext(src []rune) *Context {
ctx := ctxPool.Get().(*Context)
ctx, _ := ctxPool.Get().(*Context)
ctx.reset(src)
return ctx
}
@@ -51,17 +60,18 @@ func (c *Context) release() {
ctxPool.Put(c)
}
func (c *Context) clear() {
c.resetBuffer()
c.mstate = nil
}
func (c *Context) reset(src []rune) {
c.idx = 0
c.size = len(src)
c.src = src
c.tokens = c.tokens[:0]
c.resetBuffer()
c.isRawFolded = false
c.isSingleLine = true
c.isLiteral = false
c.isFolded = false
c.literalOpt = ""
c.mstate = nil
}
func (c *Context) resetBuffer() {
@@ -71,15 +81,185 @@ func (c *Context) resetBuffer() {
c.notSpaceOrgCharPos = 0
}
func (c *Context) isSaveIndentMode() bool {
return c.isLiteral || c.isFolded || c.isRawFolded
func (c *Context) breakMultiLine() {
c.mstate = nil
}
func (c *Context) breakLiteral() {
c.isLiteral = false
c.isRawFolded = false
c.isFolded = false
c.literalOpt = ""
func (c *Context) getMultiLineState() *MultiLineState {
return c.mstate
}
func (c *Context) setLiteral(lastDelimColumn int, opt string) {
mstate := &MultiLineState{
isLiteral: true,
opt: opt,
}
indent := firstLineIndentColumnByOpt(opt)
if indent > 0 {
mstate.firstLineIndentColumn = lastDelimColumn + indent
}
c.mstate = mstate
}
func (c *Context) setFolded(lastDelimColumn int, opt string) {
mstate := &MultiLineState{
isFolded: true,
opt: opt,
}
indent := firstLineIndentColumnByOpt(opt)
if indent > 0 {
mstate.firstLineIndentColumn = lastDelimColumn + indent
}
c.mstate = mstate
}
func (c *Context) setRawFolded(column int) {
mstate := &MultiLineState{
isRawFolded: true,
}
mstate.updateIndentColumn(column)
c.mstate = mstate
}
func firstLineIndentColumnByOpt(opt string) int {
opt = strings.TrimPrefix(opt, "-")
opt = strings.TrimPrefix(opt, "+")
opt = strings.TrimSuffix(opt, "-")
opt = strings.TrimSuffix(opt, "+")
i, _ := strconv.ParseInt(opt, 10, 64)
return int(i)
}
func (s *MultiLineState) lastDelimColumn() int {
if s.firstLineIndentColumn == 0 {
return 0
}
return s.firstLineIndentColumn - 1
}
func (s *MultiLineState) updateIndentColumn(column int) {
if s.firstLineIndentColumn == 0 {
s.firstLineIndentColumn = column
}
if s.lineIndentColumn == 0 {
s.lineIndentColumn = column
}
}
func (s *MultiLineState) updateSpaceOnlyIndentColumn(column int) {
if s.firstLineIndentColumn != 0 {
return
}
s.spaceOnlyIndentColumn = column
}
func (s *MultiLineState) validateIndentAfterSpaceOnly(column int) error {
if s.firstLineIndentColumn != 0 {
return nil
}
if s.spaceOnlyIndentColumn > column {
return errors.New("invalid number of indent is specified after space only")
}
return nil
}
func (s *MultiLineState) validateIndentColumn() error {
if firstLineIndentColumnByOpt(s.opt) == 0 {
return nil
}
if s.firstLineIndentColumn > s.lineIndentColumn {
return errors.New("invalid number of indent is specified in the multi-line header")
}
return nil
}
func (s *MultiLineState) updateNewLineState() {
s.prevLineIndentColumn = s.lineIndentColumn
if s.lineIndentColumn != 0 {
s.lastNotSpaceOnlyLineIndentColumn = s.lineIndentColumn
}
s.foldedNewLine = true
s.lineIndentColumn = 0
}
func (s *MultiLineState) isIndentColumn(column int) bool {
if s.firstLineIndentColumn == 0 {
return column == 1
}
return s.firstLineIndentColumn > column
}
func (s *MultiLineState) addIndent(ctx *Context, column int) {
if s.firstLineIndentColumn == 0 {
return
}
// If the first line of the document has already been evaluated, the number is treated as the threshold, since the `firstLineIndentColumn` is a positive number.
if column < s.firstLineIndentColumn {
return
}
// `c.foldedNewLine` is a variable that is set to true for every newline.
if !s.isLiteral && s.foldedNewLine {
s.foldedNewLine = false
}
// Since addBuf ignore space character, add to the buffer directly.
ctx.buf = append(ctx.buf, ' ')
ctx.notSpaceCharPos = len(ctx.buf)
}
// updateNewLineInFolded if Folded or RawFolded context and the content on the current line starts at the same column as the previous line,
// treat the new-line-char as a space.
func (s *MultiLineState) updateNewLineInFolded(ctx *Context, column int) {
if s.isLiteral {
return
}
// Folded or RawFolded.
if !s.foldedNewLine {
return
}
var (
lastChar rune
prevLastChar rune
)
if len(ctx.buf) != 0 {
lastChar = ctx.buf[len(ctx.buf)-1]
}
if len(ctx.buf) > 1 {
prevLastChar = ctx.buf[len(ctx.buf)-2]
}
if s.lineIndentColumn == s.prevLineIndentColumn {
// ---
// >
// a
// b
if lastChar == '\n' {
ctx.buf[len(ctx.buf)-1] = ' '
}
} else if s.prevLineIndentColumn == 0 && s.lastNotSpaceOnlyLineIndentColumn == column {
// if previous line is indent-space and new-line-char only, prevLineIndentColumn is zero.
// In this case, last new-line-char is removed.
// ---
// >
// a
//
// b
if lastChar == '\n' && prevLastChar == '\n' {
ctx.buf = ctx.buf[:len(ctx.buf)-1]
ctx.notSpaceCharPos = len(ctx.buf)
}
}
s.foldedNewLine = false
}
func (s *MultiLineState) hasTrimAllEndNewlineOpt() bool {
return strings.HasPrefix(s.opt, "-") || strings.HasSuffix(s.opt, "-") || s.isRawFolded
}
func (s *MultiLineState) hasKeepAllEndNewlineOpt() bool {
return strings.HasPrefix(s.opt, "+") || strings.HasSuffix(s.opt, "+")
}
func (c *Context) addToken(tk *token.Token) {
@@ -90,7 +270,7 @@ func (c *Context) addToken(tk *token.Token) {
}
func (c *Context) addBuf(r rune) {
if len(c.buf) == 0 && r == ' ' {
if len(c.buf) == 0 && (r == ' ' || r == '\t') {
return
}
c.buf = append(c.buf, r)
@@ -99,6 +279,16 @@ func (c *Context) addBuf(r rune) {
}
}
func (c *Context) addBufWithTab(r rune) {
if len(c.buf) == 0 && r == ' ' {
return
}
c.buf = append(c.buf, r)
if r != ' ' {
c.notSpaceCharPos = len(c.buf)
}
}
func (c *Context) addOriginBuf(r rune) {
c.obuf = append(c.obuf, r)
if r != ' ' && r != '\t' {
@@ -106,7 +296,7 @@ func (c *Context) addOriginBuf(r rune) {
}
}
func (c *Context) removeRightSpaceFromBuf() int {
func (c *Context) removeRightSpaceFromBuf() {
trimmedBuf := c.obuf[:c.notSpaceOrgCharPos]
buflen := len(trimmedBuf)
diff := len(c.obuf) - buflen
@@ -114,11 +304,6 @@ func (c *Context) removeRightSpaceFromBuf() int {
c.obuf = c.obuf[:buflen]
c.buf = c.bufferedSrc()
}
return diff
}
func (c *Context) isDocument() bool {
return c.isLiteral || c.isFolded || c.isRawFolded
}
func (c *Context) isEOS() bool {
@@ -126,7 +311,7 @@ func (c *Context) isEOS() bool {
}
func (c *Context) isNextEOS() bool {
return len(c.src)-1 <= c.idx+1
return len(c.src) <= c.idx+1
}
func (c *Context) next() bool {
@@ -151,18 +336,6 @@ func (c *Context) currentChar() rune {
return rune(0)
}
func (c *Context) currentCharWithSkipWhitespace() rune {
idx := c.idx
for c.size > idx {
ch := c.src[idx]
if ch != whitespace {
return ch
}
idx++
}
return rune(0)
}
func (c *Context) nextChar() rune {
if c.size > c.idx+1 {
return c.src[c.idx+1]
@@ -186,25 +359,52 @@ func (c *Context) progress(num int) {
c.idx += num
}
func (c *Context) nextPos() int {
return c.idx + 1
}
func (c *Context) existsBuffer() bool {
return len(c.bufferedSrc()) != 0
}
func (c *Context) isMultiLine() bool {
return c.mstate != nil
}
func (c *Context) bufferedSrc() []rune {
src := c.buf[:c.notSpaceCharPos]
if c.isDocument() && c.literalOpt == "-" {
// remove end '\n' character and trailing empty lines
if c.isMultiLine() {
mstate := c.getMultiLineState()
// remove end '\n' character and trailing empty lines.
// https://yaml.org/spec/1.2.2/#8112-block-chomping-indicator
for {
if len(src) > 0 && src[len(src)-1] == '\n' {
src = src[:len(src)-1]
continue
if mstate.hasTrimAllEndNewlineOpt() {
// If the '-' flag is specified, all trailing newline characters will be removed.
src = []rune(strings.TrimRight(string(src), "\n"))
} else if !mstate.hasKeepAllEndNewlineOpt() {
// Normally, all but one of the trailing newline characters are removed.
var newLineCharCount int
for i := len(src) - 1; i >= 0; i-- {
if src[i] == '\n' {
newLineCharCount++
continue
}
break
}
break
removedNewLineCharCount := newLineCharCount - 1
for removedNewLineCharCount > 0 {
src = []rune(strings.TrimSuffix(string(src), "\n"))
removedNewLineCharCount--
}
}
// If the text ends with a space character, remove all of them.
if mstate.hasTrimAllEndNewlineOpt() {
src = []rune(strings.TrimRight(string(src), " "))
}
if string(src) == "\n" {
// If the content consists only of a newline,
// it can be considered as the document ending without any specified value,
// so it is treated as an empty string.
src = []rune{}
}
if mstate.hasKeepAllEndNewlineOpt() && len(src) == 0 {
src = []rune{'\n'}
}
}
return src
@@ -216,18 +416,34 @@ func (c *Context) bufferedToken(pos *token.Position) *token.Token {
}
source := c.bufferedSrc()
if len(source) == 0 {
c.buf = c.buf[:0] // clear value's buffer only.
return nil
}
var tk *token.Token
if c.isDocument() {
if c.isMultiLine() {
tk = token.String(string(source), string(c.obuf), pos)
} else {
tk = token.New(string(source), string(c.obuf), pos)
}
c.setTokenTypeByPrevTag(tk)
c.resetBuffer()
return tk
}
func (c *Context) setTokenTypeByPrevTag(tk *token.Token) {
lastTk := c.lastToken()
if lastTk == nil {
return
}
if lastTk.Type != token.TagType {
return
}
tag := token.ReservedTagKeyword(lastTk.Value)
if _, exists := token.ReservedTagKeywordMap[tag]; !exists {
tk.Type = token.StringType
}
}
func (c *Context) lastToken() *token.Token {
if len(c.tokens) != 0 {
return c.tokens[len(c.tokens)-1]

17
vendor/github.com/goccy/go-yaml/scanner/error.go generated vendored Normal file
View File

@@ -0,0 +1,17 @@
package scanner
import "github.com/goccy/go-yaml/token"
type InvalidTokenError struct {
Token *token.Token
}
func (e *InvalidTokenError) Error() string {
return e.Token.Error
}
func ErrInvalidToken(tk *token.Token) *InvalidTokenError {
return &InvalidTokenError{
Token: tk,
}
}

View File

File diff suppressed because it is too large Load Diff

View File

@@ -53,8 +53,18 @@ func appendQuotedWith(buf []byte, s string, quote byte) []byte {
func appendEscapedRune(buf []byte, r rune, quote byte) []byte {
var runeTmp [utf8.UTFMax]byte
if r == rune(quote) || r == '\\' { // always backslashed
buf = append(buf, '\\')
// goccy/go-yaml patch on top of the standard library's appendEscapedRune function.
//
// We use this to implement the YAML single-quoted string, where the only escape sequence is '', which represents a single quote.
// The below snippet from the standard library is for escaping e.g. \ with \\, which is not what we want for the single-quoted string.
//
// if r == rune(quote) || r == '\\' { // always backslashed
// buf = append(buf, '\\')
// buf = append(buf, byte(r))
// return buf
// }
if r == rune(quote) {
buf = append(buf, byte(r))
buf = append(buf, byte(r))
return buf
}

View File

@@ -1,10 +1,9 @@
package yaml
import (
"fmt"
"reflect"
"strings"
"golang.org/x/xerrors"
)
const (
@@ -21,6 +20,7 @@ type StructField struct {
IsAutoAnchor bool
IsAutoAlias bool
IsOmitEmpty bool
IsOmitZero bool
IsFlow bool
IsInline bool
}
@@ -45,7 +45,7 @@ func structField(field reflect.StructField) *StructField {
fieldName = options[0]
}
}
structField := &StructField{
sf := &StructField{
FieldName: field.Name,
RenderName: fieldName,
}
@@ -53,30 +53,32 @@ func structField(field reflect.StructField) *StructField {
for _, opt := range options[1:] {
switch {
case opt == "omitempty":
structField.IsOmitEmpty = true
sf.IsOmitEmpty = true
case opt == "omitzero":
sf.IsOmitZero = true
case opt == "flow":
structField.IsFlow = true
sf.IsFlow = true
case opt == "inline":
structField.IsInline = true
sf.IsInline = true
case strings.HasPrefix(opt, "anchor"):
anchor := strings.Split(opt, "=")
if len(anchor) > 1 {
structField.AnchorName = anchor[1]
sf.AnchorName = anchor[1]
} else {
structField.IsAutoAnchor = true
sf.IsAutoAnchor = true
}
case strings.HasPrefix(opt, "alias"):
alias := strings.Split(opt, "=")
if len(alias) > 1 {
structField.AliasName = alias[1]
sf.AliasName = alias[1]
} else {
structField.IsAutoAlias = true
sf.IsAutoAlias = true
}
default:
}
}
}
return structField
return sf
}
func isIgnoredStructField(field reflect.StructField) bool {
@@ -84,11 +86,7 @@ func isIgnoredStructField(field reflect.StructField) bool {
// private field
return true
}
tag := getTag(field)
if tag == "-" {
return true
}
return false
return getTag(field) == "-"
}
type StructFieldMap map[string]*StructField
@@ -112,19 +110,19 @@ func (m StructFieldMap) hasMergeProperty() bool {
}
func structFieldMap(structType reflect.Type) (StructFieldMap, error) {
structFieldMap := StructFieldMap{}
fieldMap := StructFieldMap{}
renderNameMap := map[string]struct{}{}
for i := 0; i < structType.NumField(); i++ {
field := structType.Field(i)
if isIgnoredStructField(field) {
continue
}
structField := structField(field)
if _, exists := renderNameMap[structField.RenderName]; exists {
return nil, xerrors.Errorf("duplicated struct field name %s", structField.RenderName)
sf := structField(field)
if _, exists := renderNameMap[sf.RenderName]; exists {
return nil, fmt.Errorf("duplicated struct field name %s", sf.RenderName)
}
structFieldMap[structField.FieldName] = structField
renderNameMap[structField.RenderName] = struct{}{}
fieldMap[sf.FieldName] = sf
renderNameMap[sf.RenderName] = struct{}{}
}
return structFieldMap, nil
return fieldMap, nil
}

View File

@@ -1,8 +1,11 @@
package token
import (
"errors"
"fmt"
"strconv"
"strings"
"time"
)
// Character type for character
@@ -99,6 +102,10 @@ const (
SpaceType
// NullType type for Null token
NullType
// ImplicitNullType type for implicit Null token.
// This is used when explicit keywords such as null or ~ are not specified.
// It is distinguished during encoding and output as an empty string.
ImplicitNullType
// InfinityType type for Infinity token
InfinityType
// NanType type for Nan token
@@ -117,6 +124,8 @@ const (
StringType
// BoolType type for Bool token
BoolType
// InvalidType type for invalid token
InvalidType
)
// String type identifier to text
@@ -182,10 +191,14 @@ func (t Type) String() string {
return "Float"
case NullType:
return "Null"
case ImplicitNullType:
return "ImplicitNull"
case InfinityType:
return "Infinity"
case NanType:
return "Nan"
case InvalidType:
return "Invalid"
}
return ""
}
@@ -202,6 +215,8 @@ const (
CharacterTypeMiscellaneous
// CharacterTypeEscaped type of escaped character
CharacterTypeEscaped
// CharacterTypeInvalid type for a invalid token.
CharacterTypeInvalid
)
// String character type identifier to text
@@ -210,7 +225,7 @@ func (c CharacterType) String() string {
case CharacterTypeIndicator:
return "Indicator"
case CharacterTypeWhiteSpace:
return "WhiteSpcae"
return "WhiteSpace"
case CharacterTypeMiscellaneous:
return "Miscellaneous"
case CharacterTypeEscaped:
@@ -339,9 +354,12 @@ func reservedKeywordToken(typ Type, value, org string, pos *Position) *Token {
func init() {
for _, keyword := range reservedNullKeywords {
reservedKeywordMap[keyword] = func(value, org string, pos *Position) *Token {
f := func(value, org string, pos *Position) *Token {
return reservedKeywordToken(NullType, value, org, pos)
}
reservedKeywordMap[keyword] = f
reservedEncKeywordMap[keyword] = f
}
for _, keyword := range reservedBoolKeywords {
f := func(value, org string, pos *Position) *Token {
@@ -391,6 +409,10 @@ const (
SetTag ReservedTagKeyword = "!!set"
// TimestampTag `!!timestamp` tag
TimestampTag ReservedTagKeyword = "!!timestamp"
// BooleanTag `!!bool` tag
BooleanTag ReservedTagKeyword = "!!bool"
// MergeTag `!!merge` tag
MergeTag ReservedTagKeyword = "!!merge"
)
var (
@@ -496,121 +518,163 @@ var (
Position: pos,
}
},
BooleanTag: func(value, org string, pos *Position) *Token {
return &Token{
Type: TagType,
CharacterType: CharacterTypeIndicator,
Indicator: NodePropertyIndicator,
Value: value,
Origin: org,
Position: pos,
}
},
MergeTag: func(value, org string, pos *Position) *Token {
return &Token{
Type: TagType,
CharacterType: CharacterTypeIndicator,
Indicator: NodePropertyIndicator,
Value: value,
Origin: org,
Position: pos,
}
},
}
)
type numType int
type NumberType string
const (
numTypeNone numType = iota
numTypeBinary
numTypeOctet
numTypeHex
numTypeFloat
NumberTypeDecimal NumberType = "decimal"
NumberTypeBinary NumberType = "binary"
NumberTypeOctet NumberType = "octet"
NumberTypeHex NumberType = "hex"
NumberTypeFloat NumberType = "float"
)
type numStat struct {
isNum bool
typ numType
type NumberValue struct {
Type NumberType
Value any
Text string
}
func getNumberStat(str string) *numStat {
stat := &numStat{}
if str == "" {
return stat
func ToNumber(value string) *NumberValue {
num, err := toNumber(value)
if err != nil {
return nil
}
if str == "-" || str == "." || str == "+" || str == "_" {
return stat
}
if str[0] == '_' {
return stat
}
dotFound := false
isNegative := false
isExponent := false
if str[0] == '-' {
isNegative = true
}
for idx, c := range str {
switch c {
case 'x':
if (isNegative && idx == 2) || (!isNegative && idx == 1) {
continue
}
case 'o':
if (isNegative && idx == 2) || (!isNegative && idx == 1) {
continue
}
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
continue
case 'a', 'b', 'c', 'd', 'e', 'f', 'A', 'B', 'C', 'D', 'E', 'F':
if (len(str) > 2 && str[0] == '0' && str[1] == 'x') ||
(len(str) > 3 && isNegative && str[1] == '0' && str[2] == 'x') {
// hex number
continue
}
if c == 'b' && ((isNegative && idx == 2) || (!isNegative && idx == 1)) {
// binary number
continue
}
if (c == 'e' || c == 'E') && dotFound {
// exponent
isExponent = true
continue
}
case '.':
if dotFound {
// multiple dot
return stat
}
dotFound = true
continue
case '-':
if idx == 0 || isExponent {
continue
}
case '+':
if idx == 0 || isExponent {
continue
}
case '_':
continue
}
return stat
}
stat.isNum = true
switch {
case dotFound:
stat.typ = numTypeFloat
case strings.HasPrefix(str, "0b") || strings.HasPrefix(str, "-0b"):
stat.typ = numTypeBinary
case strings.HasPrefix(str, "0x") || strings.HasPrefix(str, "-0x"):
stat.typ = numTypeHex
case strings.HasPrefix(str, "0o") || strings.HasPrefix(str, "-0o"):
stat.typ = numTypeOctet
case (len(str) > 1 && str[0] == '0') || (len(str) > 1 && str[0] == '-' && str[1] == '0'):
stat.typ = numTypeOctet
}
return stat
return num
}
func looksLikeTimeValue(value string) bool {
for i, c := range value {
switch c {
case ':', '1', '2', '3', '4', '5', '6', '7', '8', '9':
continue
case '0':
if i == 0 {
return false
}
continue
func isNumber(value string) bool {
num, err := toNumber(value)
if err != nil {
var numErr *strconv.NumError
if errors.As(err, &numErr) && errors.Is(numErr.Err, strconv.ErrRange) {
return true
}
return false
}
return true
return num != nil
}
// IsNeedQuoted whether need quote for passed string or not
func toNumber(value string) (*NumberValue, error) {
if len(value) == 0 {
return nil, nil
}
if strings.HasPrefix(value, "_") {
return nil, nil
}
dotCount := strings.Count(value, ".")
if dotCount > 1 {
return nil, nil
}
isNegative := strings.HasPrefix(value, "-")
normalized := strings.ReplaceAll(strings.TrimPrefix(strings.TrimPrefix(value, "+"), "-"), "_", "")
var (
typ NumberType
base int
)
switch {
case strings.HasPrefix(normalized, "0x"):
normalized = strings.TrimPrefix(normalized, "0x")
base = 16
typ = NumberTypeHex
case strings.HasPrefix(normalized, "0o"):
normalized = strings.TrimPrefix(normalized, "0o")
base = 8
typ = NumberTypeOctet
case strings.HasPrefix(normalized, "0b"):
normalized = strings.TrimPrefix(normalized, "0b")
base = 2
typ = NumberTypeBinary
case strings.HasPrefix(normalized, "0") && len(normalized) > 1 && dotCount == 0:
base = 8
typ = NumberTypeOctet
case dotCount == 1:
typ = NumberTypeFloat
default:
typ = NumberTypeDecimal
base = 10
}
text := normalized
if isNegative {
text = "-" + text
}
var v any
if typ == NumberTypeFloat {
f, err := strconv.ParseFloat(text, 64)
if err != nil {
return nil, err
}
v = f
} else if isNegative {
i, err := strconv.ParseInt(text, base, 64)
if err != nil {
return nil, err
}
v = i
} else {
u, err := strconv.ParseUint(text, base, 64)
if err != nil {
return nil, err
}
v = u
}
return &NumberValue{
Type: typ,
Value: v,
Text: text,
}, nil
}
// This is a subset of the formats permitted by the regular expression
// defined at http://yaml.org/type/timestamp.html. Note that time.Parse
// cannot handle: "2001-12-14 21:59:43.10 -5" from the examples.
var timestampFormats = []string{
time.RFC3339Nano,
"2006-01-02t15:04:05.999999999Z07:00", // RFC3339Nano with lower-case "t".
time.DateTime,
time.DateOnly,
// Not in examples, but to preserve backward compatibility by quoting time values.
"15:4",
}
func isTimestamp(value string) bool {
for _, format := range timestampFormats {
if _, err := time.Parse(format, value); err == nil {
return true
}
}
return false
}
// IsNeedQuoted checks whether the value needs quote for passed string or not
func IsNeedQuoted(value string) bool {
if value == "" {
return true
@@ -618,7 +682,10 @@ func IsNeedQuoted(value string) bool {
if _, exists := reservedEncKeywordMap[value]; exists {
return true
}
if stat := getNumberStat(value); stat.isNum {
if isNumber(value) {
return true
}
if value == "-" {
return true
}
first := value[0]
@@ -631,14 +698,14 @@ func IsNeedQuoted(value string) bool {
case ':', ' ':
return true
}
if looksLikeTimeValue(value) {
if isTimestamp(value) {
return true
}
for i, c := range value {
switch c {
case '#', '\\':
return true
case ':':
case ':', '-':
if i+1 < len(value) && value[i+1] == ' ' {
return true
}
@@ -663,13 +730,13 @@ func LiteralBlockHeader(value string) string {
}
}
// New create reserved keyword token or number token and other string token
// New create reserved keyword token or number token and other string token.
func New(value string, org string, pos *Position) *Token {
fn := reservedKeywordMap[value]
if fn != nil {
return fn(value, org, pos)
}
if stat := getNumberStat(value); stat.isNum {
if num := ToNumber(value); num != nil {
tk := &Token{
Type: IntegerType,
CharacterType: CharacterTypeMiscellaneous,
@@ -678,14 +745,14 @@ func New(value string, org string, pos *Position) *Token {
Origin: org,
Position: pos,
}
switch stat.typ {
case numTypeFloat:
switch num.Type {
case NumberTypeFloat:
tk.Type = FloatType
case numTypeBinary:
case NumberTypeBinary:
tk.Type = BinaryIntegerType
case numTypeOctet:
case NumberTypeOctet:
tk.Type = OctetIntegerType
case numTypeHex:
case NumberTypeHex:
tk.Type = HexIntegerType
}
return tk
@@ -709,14 +776,24 @@ func (p *Position) String() string {
// Token type for token
type Token struct {
Type Type
// Type is a token type.
Type Type
// CharacterType is a character type.
CharacterType CharacterType
Indicator Indicator
Value string
Origin string
Position *Position
Next *Token
Prev *Token
// Indicator is a indicator type.
Indicator Indicator
// Value is a string extracted with only meaningful characters, with spaces and such removed.
Value string
// Origin is a string that stores the original text as-is.
Origin string
// Error keeps error message for InvalidToken.
Error string
// Position is a token position.
Position *Position
// Next is a next token reference.
Next *Token
// Prev is a previous token reference.
Prev *Token
}
// PreviousType previous token type
@@ -756,9 +833,26 @@ func (t *Token) Clone() *Token {
return &copied
}
// Dump outputs token information to stdout for debugging.
func (t *Token) Dump() {
fmt.Printf(
"[TYPE]:%q [CHARTYPE]:%q [INDICATOR]:%q [VALUE]:%q [ORG]:%q [POS(line:column:level:offset)]: %d:%d:%d:%d\n",
t.Type, t.CharacterType, t.Indicator, t.Value, t.Origin, t.Position.Line, t.Position.Column, t.Position.IndentLevel, t.Position.Offset,
)
}
// Tokens type of token collection
type Tokens []*Token
func (t Tokens) InvalidToken() *Token {
for _, tt := range t {
if tt.Type == InvalidType {
return tt
}
}
return nil
}
func (t *Tokens) add(tk *Token) {
tokens := *t
if len(tokens) == 0 {
@@ -782,7 +876,8 @@ func (t *Tokens) Add(tks ...*Token) {
// Dump dump all token structures for debugging
func (t Tokens) Dump() {
for _, tk := range t {
fmt.Printf("- %+v\n", tk)
fmt.Print("- ")
tk.Dump()
}
}
@@ -1054,6 +1149,18 @@ func DocumentEnd(org string, pos *Position) *Token {
}
}
func Invalid(err string, org string, pos *Position) *Token {
return &Token{
Type: InvalidType,
CharacterType: CharacterTypeInvalid,
Indicator: NotIndicator,
Value: org,
Origin: org,
Error: err,
Position: pos,
}
}
// DetectLineBreakCharacter detect line break character in only one inside scalar content scope.
func DetectLineBreakCharacter(src string) string {
nc := strings.Count(src, "\n")

View File

@@ -9,7 +9,6 @@ import (
"github.com/goccy/go-yaml/ast"
"github.com/goccy/go-yaml/internal/errors"
"golang.org/x/xerrors"
)
// BytesMarshaler interface may be implemented by types to customize their
@@ -58,6 +57,16 @@ type InterfaceUnmarshalerContext interface {
UnmarshalYAML(context.Context, func(interface{}) error) error
}
// NodeUnmarshaler interface is similar to BytesUnmarshaler but provide related AST node instead of raw YAML source.
type NodeUnmarshaler interface {
UnmarshalYAML(ast.Node) error
}
// NodeUnmarshalerContext interface is similar to BytesUnmarshaler but provide related AST node instead of raw YAML source.
type NodeUnmarshalerContext interface {
UnmarshalYAML(context.Context, ast.Node) error
}
// MapItem is an item in a MapSlice.
type MapItem struct {
Key, Value interface{}
@@ -80,11 +89,11 @@ func (s MapSlice) ToMap() map[interface{}]interface{} {
// of the generated document will reflect the structure of the value itself.
// Maps and pointers (to struct, string, int, etc) are accepted as the in value.
//
// Struct fields are only marshalled if they are exported (have an upper case
// first letter), and are marshalled using the field name lowercased as the
// Struct fields are only marshaled if they are exported (have an upper case
// first letter), and are marshaled using the field name lowercased as the
// default key. Custom keys may be defined via the "yaml" name in the field
// tag: the content preceding the first comma is used as the key, and the
// following comma-separated options are used to tweak the marshalling process.
// following comma-separated options are used to tweak the marshaling process.
// Conflicting names result in a runtime error.
//
// The field tag format accepted is:
@@ -99,6 +108,13 @@ func (s MapSlice) ToMap() map[interface{}]interface{} {
// fields are zero, unless they implement an IsZero
// method (see the IsZeroer interface type), in which
// case the field will be included if that method returns true.
// Note that this definition is slightly different from the Go's
// encoding/json 'omitempty' definition. It combines some elements
// of 'omitempty' and 'omitzero'. See https://github.com/goccy/go-yaml/issues/695.
//
// omitzero The omitzero tag behaves in the same way as the interpretation of the omitzero tag in the encoding/json library.
// 1) If the field type has an "IsZero() bool" method, that will be used to determine whether the value is zero.
// 2) Otherwise, the value is zero if it is the zero value for its type.
//
// flow Marshal using a flow style (useful for structs,
// sequences and maps).
@@ -138,7 +154,7 @@ func MarshalWithOptions(v interface{}, opts ...EncodeOption) ([]byte, error) {
func MarshalContext(ctx context.Context, v interface{}, opts ...EncodeOption) ([]byte, error) {
var buf bytes.Buffer
if err := NewEncoder(&buf, opts...).EncodeContext(ctx, v); err != nil {
return nil, errors.Wrapf(err, "failed to marshal")
return nil, err
}
return buf.Bytes(), nil
}
@@ -148,7 +164,7 @@ func ValueToNode(v interface{}, opts ...EncodeOption) (ast.Node, error) {
var buf bytes.Buffer
node, err := NewEncoder(&buf, opts...).EncodeToNode(v)
if err != nil {
return nil, errors.Wrapf(err, "failed to convert value to node")
return nil, err
}
return node, nil
}
@@ -161,7 +177,7 @@ func ValueToNode(v interface{}, opts ...EncodeOption) (ast.Node, error) {
// lowercased as the default key. Custom keys may be defined via the
// "yaml" name in the field tag: the content preceding the first comma
// is used as the key, and the following comma-separated options are
// used to tweak the marshalling process (see Marshal).
// used to tweak the marshaling process (see Marshal).
// Conflicting names result in a runtime error.
//
// For example:
@@ -192,7 +208,7 @@ func UnmarshalContext(ctx context.Context, data []byte, v interface{}, opts ...D
if err == io.EOF {
return nil
}
return errors.Wrapf(err, "failed to unmarshal")
return err
}
return nil
}
@@ -201,7 +217,7 @@ func UnmarshalContext(ctx context.Context, data []byte, v interface{}, opts ...D
func NodeToValue(node ast.Node, v interface{}, opts ...DecodeOption) error {
var buf bytes.Buffer
if err := NewDecoder(&buf, opts...).DecodeFromNode(node, v); err != nil {
return errors.Wrapf(err, "failed to convert node to value")
return err
}
return nil
}
@@ -213,11 +229,9 @@ func NodeToValue(node ast.Node, v interface{}, opts ...DecodeOption) error {
// If the third argument `inclSource` is true, the error message will
// contain snippets of the YAML source that was used.
func FormatError(e error, colored, inclSource bool) string {
var pp errors.PrettyPrinter
if xerrors.As(e, &pp) {
var buf bytes.Buffer
pp.PrettyPrint(&errors.Sink{&buf}, colored, inclSource)
return buf.String()
var yamlErr Error
if errors.As(e, &yamlErr) {
return yamlErr.FormatError(colored, inclSource)
}
return e.Error()
@@ -227,11 +241,11 @@ func FormatError(e error, colored, inclSource bool) string {
func YAMLToJSON(bytes []byte) ([]byte, error) {
var v interface{}
if err := UnmarshalWithOptions(bytes, &v, UseOrderedMap()); err != nil {
return nil, errors.Wrapf(err, "failed to unmarshal")
return nil, err
}
out, err := MarshalWithOptions(v, JSON())
if err != nil {
return nil, errors.Wrapf(err, "failed to marshal with json option")
return nil, err
}
return out, nil
}
@@ -240,11 +254,11 @@ func YAMLToJSON(bytes []byte) ([]byte, error) {
func JSONToYAML(bytes []byte) ([]byte, error) {
var v interface{}
if err := UnmarshalWithOptions(bytes, &v, UseOrderedMap()); err != nil {
return nil, errors.Wrapf(err, "failed to unmarshal from json bytes")
return nil, err
}
out, err := Marshal(v)
if err != nil {
return nil, errors.Wrapf(err, "failed to marshal")
return nil, err
}
return out, nil
}
@@ -252,8 +266,8 @@ func JSONToYAML(bytes []byte) ([]byte, error) {
var (
globalCustomMarshalerMu sync.Mutex
globalCustomUnmarshalerMu sync.Mutex
globalCustomMarshalerMap = map[reflect.Type]func(interface{}) ([]byte, error){}
globalCustomUnmarshalerMap = map[reflect.Type]func(interface{}, []byte) error{}
globalCustomMarshalerMap = map[reflect.Type]func(context.Context, interface{}) ([]byte, error){}
globalCustomUnmarshalerMap = map[reflect.Type]func(context.Context, interface{}, []byte) error{}
)
// RegisterCustomMarshaler overrides any encoding process for the type specified in generics.
@@ -267,11 +281,23 @@ func RegisterCustomMarshaler[T any](marshaler func(T) ([]byte, error)) {
defer globalCustomMarshalerMu.Unlock()
var typ T
globalCustomMarshalerMap[reflect.TypeOf(typ)] = func(v interface{}) ([]byte, error) {
globalCustomMarshalerMap[reflect.TypeOf(typ)] = func(ctx context.Context, v interface{}) ([]byte, error) {
return marshaler(v.(T))
}
}
// RegisterCustomMarshalerContext overrides any encoding process for the type specified in generics.
// Similar to RegisterCustomMarshalerContext, but allows passing a context to the unmarshaler function.
func RegisterCustomMarshalerContext[T any](marshaler func(context.Context, T) ([]byte, error)) {
globalCustomMarshalerMu.Lock()
defer globalCustomMarshalerMu.Unlock()
var typ T
globalCustomMarshalerMap[reflect.TypeOf(typ)] = func(ctx context.Context, v interface{}) ([]byte, error) {
return marshaler(ctx, v.(T))
}
}
// RegisterCustomUnmarshaler overrides any decoding process for the type specified in generics.
// If you want to switch the behavior for each decoder, use `CustomUnmarshaler` defined as DecodeOption.
//
@@ -282,7 +308,19 @@ func RegisterCustomUnmarshaler[T any](unmarshaler func(*T, []byte) error) {
defer globalCustomUnmarshalerMu.Unlock()
var typ *T
globalCustomUnmarshalerMap[reflect.TypeOf(typ)] = func(v interface{}, b []byte) error {
globalCustomUnmarshalerMap[reflect.TypeOf(typ)] = func(ctx context.Context, v interface{}, b []byte) error {
return unmarshaler(v.(*T), b)
}
}
// RegisterCustomUnmarshalerContext overrides any decoding process for the type specified in generics.
// Similar to RegisterCustomUnmarshalerContext, but allows passing a context to the unmarshaler function.
func RegisterCustomUnmarshalerContext[T any](unmarshaler func(context.Context, *T, []byte) error) {
globalCustomUnmarshalerMu.Lock()
defer globalCustomUnmarshalerMu.Unlock()
var typ *T
globalCustomUnmarshalerMap[reflect.TypeOf(typ)] = func(ctx context.Context, v interface{}, b []byte) error {
return unmarshaler(ctx, v.(*T), b)
}
}

View File

@@ -1,3 +1,9 @@
## 2.26.0
### Features
Ginkgo can now generate json-formatted reports that are compatible with the `go test` json format. Use `ginkgo --gojson-report=report.go.json`. This is not intended to be a replacement for Ginkgo's native json format which is more information rich and better models Ginkgo's test structure semantics.
## 2.25.3
### Fixes

View File

@@ -113,3 +113,13 @@ Ginkgo is MIT-Licensed
## Contributing
See [CONTRIBUTING.md](CONTRIBUTING.md)
## Sponsors
Sponsors commit to a [sponsorship](https://github.com/sponsors/onsi) for a year. If you're an organization that makes use of Ginkgo please consider becoming a sponsor!
<p style="font-size:21px; color:black;">Browser testing via
<a href="https://www.lambdatest.com/" target="_blank">
<img src="https://www.lambdatest.com/blue-logo.png" style="vertical-align: middle;" width="250" height="45" />
</a>
</p>

View File

@@ -90,6 +90,9 @@ func FinalizeProfilesAndReportsForSuites(suites TestSuites, cliConfig types.CLIC
if reporterConfig.JSONReport != "" {
reportFormats = append(reportFormats, reportFormat{ReportName: reporterConfig.JSONReport, GenerateFunc: reporters.GenerateJSONReport, MergeFunc: reporters.MergeAndCleanupJSONReports})
}
if reporterConfig.GoJSONReport != "" {
reportFormats = append(reportFormats, reportFormat{ReportName: reporterConfig.GoJSONReport, GenerateFunc: reporters.GenerateGoTestJSONReport, MergeFunc: reporters.MergeAndCleanupGoTestJSONReports})
}
if reporterConfig.JUnitReport != "" {
reportFormats = append(reportFormats, reportFormat{ReportName: reporterConfig.JUnitReport, GenerateFunc: reporters.GenerateJUnitReport, MergeFunc: reporters.MergeAndCleanupJUnitReports})
}

View File

@@ -107,6 +107,9 @@ func runSerial(suite TestSuite, ginkgoConfig types.SuiteConfig, reporterConfig t
if reporterConfig.JSONReport != "" {
reporterConfig.JSONReport = AbsPathForGeneratedAsset(reporterConfig.JSONReport, suite, cliConfig, 0)
}
if reporterConfig.GoJSONReport != "" {
reporterConfig.GoJSONReport = AbsPathForGeneratedAsset(reporterConfig.GoJSONReport, suite, cliConfig, 0)
}
if reporterConfig.JUnitReport != "" {
reporterConfig.JUnitReport = AbsPathForGeneratedAsset(reporterConfig.JUnitReport, suite, cliConfig, 0)
}
@@ -179,6 +182,9 @@ func runParallel(suite TestSuite, ginkgoConfig types.SuiteConfig, reporterConfig
if reporterConfig.JSONReport != "" {
reporterConfig.JSONReport = AbsPathForGeneratedAsset(reporterConfig.JSONReport, suite, cliConfig, 0)
}
if reporterConfig.GoJSONReport != "" {
reporterConfig.GoJSONReport = AbsPathForGeneratedAsset(reporterConfig.GoJSONReport, suite, cliConfig, 0)
}
if reporterConfig.JUnitReport != "" {
reporterConfig.JUnitReport = AbsPathForGeneratedAsset(reporterConfig.JUnitReport, suite, cliConfig, 0)
}

View File

@@ -3,7 +3,6 @@ package main
import (
"fmt"
"os"
_ "go.uber.org/automaxprocs"
"github.com/onsi/ginkgo/v2/ginkgo/build"
"github.com/onsi/ginkgo/v2/ginkgo/command"
"github.com/onsi/ginkgo/v2/ginkgo/generators"

8
vendor/github.com/onsi/ginkgo/v2/ginkgo/maxprocs.go generated vendored Normal file
View File

@@ -0,0 +1,8 @@
//go:build !go1.25
// +build !go1.25
package main
import (
_ "go.uber.org/automaxprocs"
)

View File

@@ -0,0 +1,158 @@
package reporters
import (
"errors"
"fmt"
"strings"
"time"
"github.com/onsi/ginkgo/v2/types"
"golang.org/x/tools/go/packages"
)
func ptr[T any](in T) *T {
return &in
}
type encoder interface {
Encode(v any) error
}
// gojsonEvent matches the format from go internals
// https://github.com/golang/go/blob/master/src/cmd/internal/test2json/test2json.go#L31-L41
// https://pkg.go.dev/cmd/test2json
type gojsonEvent struct {
Time *time.Time `json:",omitempty"`
Action GoJSONAction
Package string `json:",omitempty"`
Test string `json:",omitempty"`
Elapsed *float64 `json:",omitempty"`
Output *string `json:",omitempty"`
FailedBuild string `json:",omitempty"`
}
type GoJSONAction string
const (
// start - the test binary is about to be executed
GoJSONStart GoJSONAction = "start"
// run - the test has started running
GoJSONRun GoJSONAction = "run"
// pause - the test has been paused
GoJSONPause GoJSONAction = "pause"
// cont - the test has continued running
GoJSONCont GoJSONAction = "cont"
// pass - the test passed
GoJSONPass GoJSONAction = "pass"
// bench - the benchmark printed log output but did not fail
GoJSONBench GoJSONAction = "bench"
// fail - the test or benchmark failed
GoJSONFail GoJSONAction = "fail"
// output - the test printed output
GoJSONOutput GoJSONAction = "output"
// skip - the test was skipped or the package contained no tests
GoJSONSkip GoJSONAction = "skip"
)
func goJSONActionFromSpecState(state types.SpecState) GoJSONAction {
switch state {
case types.SpecStateInvalid:
return GoJSONFail
case types.SpecStatePending:
return GoJSONSkip
case types.SpecStateSkipped:
return GoJSONSkip
case types.SpecStatePassed:
return GoJSONPass
case types.SpecStateFailed:
return GoJSONFail
case types.SpecStateAborted:
return GoJSONFail
case types.SpecStatePanicked:
return GoJSONFail
case types.SpecStateInterrupted:
return GoJSONFail
case types.SpecStateTimedout:
return GoJSONFail
default:
panic("unexpected state should not happen")
}
}
// gojsonReport wraps types.Report and calcualtes extra fields requires by gojson
type gojsonReport struct {
o types.Report
// Extra calculated fields
goPkg string
elapsed float64
}
func newReport(in types.Report) *gojsonReport {
return &gojsonReport{
o: in,
}
}
func (r *gojsonReport) Fill() error {
// NOTE: could the types.Report include the go package name?
goPkg, err := suitePathToPkg(r.o.SuitePath)
if err != nil {
return err
}
r.goPkg = goPkg
r.elapsed = r.o.RunTime.Seconds()
return nil
}
// gojsonSpecReport wraps types.SpecReport and calculates extra fields required by gojson
type gojsonSpecReport struct {
o types.SpecReport
// extra calculated fields
testName string
elapsed float64
action GoJSONAction
}
func newSpecReport(in types.SpecReport) *gojsonSpecReport {
return &gojsonSpecReport{
o: in,
}
}
func (sr *gojsonSpecReport) Fill() error {
sr.elapsed = sr.o.RunTime.Seconds()
sr.testName = createTestName(sr.o)
sr.action = goJSONActionFromSpecState(sr.o.State)
return nil
}
func suitePathToPkg(dir string) (string, error) {
cfg := &packages.Config{
Mode: packages.NeedFiles | packages.NeedSyntax,
}
pkgs, err := packages.Load(cfg, dir)
if err != nil {
return "", err
}
if len(pkgs) != 1 {
return "", errors.New("error")
}
return pkgs[0].ID, nil
}
func createTestName(spec types.SpecReport) string {
name := fmt.Sprintf("[%s]", spec.LeafNodeType)
if spec.FullText() != "" {
name = name + " " + spec.FullText()
}
labels := spec.Labels()
if len(labels) > 0 {
name = name + " [" + strings.Join(labels, ", ") + "]"
}
semVerConstraints := spec.SemVerConstraints()
if len(semVerConstraints) > 0 {
name = name + " [" + strings.Join(semVerConstraints, ", ") + "]"
}
name = strings.TrimSpace(name)
return name
}

View File

@@ -0,0 +1,111 @@
package reporters
type GoJSONEventWriter struct {
enc encoder
specSystemErrFn specSystemExtractFn
specSystemOutFn specSystemExtractFn
}
func NewGoJSONEventWriter(enc encoder, errFn specSystemExtractFn, outFn specSystemExtractFn) *GoJSONEventWriter {
return &GoJSONEventWriter{
enc: enc,
specSystemErrFn: errFn,
specSystemOutFn: outFn,
}
}
func (r *GoJSONEventWriter) writeEvent(e *gojsonEvent) error {
return r.enc.Encode(e)
}
func (r *GoJSONEventWriter) WriteSuiteStart(report *gojsonReport) error {
e := &gojsonEvent{
Time: &report.o.StartTime,
Action: GoJSONStart,
Package: report.goPkg,
Output: nil,
FailedBuild: "",
}
return r.writeEvent(e)
}
func (r *GoJSONEventWriter) WriteSuiteResult(report *gojsonReport) error {
var action GoJSONAction
switch {
case report.o.PreRunStats.SpecsThatWillRun == 0:
action = GoJSONSkip
case report.o.SuiteSucceeded:
action = GoJSONPass
default:
action = GoJSONFail
}
e := &gojsonEvent{
Time: &report.o.EndTime,
Action: action,
Package: report.goPkg,
Output: nil,
FailedBuild: "",
Elapsed: ptr(report.elapsed),
}
return r.writeEvent(e)
}
func (r *GoJSONEventWriter) WriteSpecStart(report *gojsonReport, specReport *gojsonSpecReport) error {
e := &gojsonEvent{
Time: &specReport.o.StartTime,
Action: GoJSONRun,
Test: specReport.testName,
Package: report.goPkg,
Output: nil,
FailedBuild: "",
}
return r.writeEvent(e)
}
func (r *GoJSONEventWriter) WriteSpecOut(report *gojsonReport, specReport *gojsonSpecReport) error {
events := []*gojsonEvent{}
stdErr := r.specSystemErrFn(specReport.o)
if stdErr != "" {
events = append(events, &gojsonEvent{
Time: &specReport.o.EndTime,
Action: GoJSONOutput,
Test: specReport.testName,
Package: report.goPkg,
Output: ptr(stdErr),
FailedBuild: "",
})
}
stdOut := r.specSystemOutFn(specReport.o)
if stdOut != "" {
events = append(events, &gojsonEvent{
Time: &specReport.o.EndTime,
Action: GoJSONOutput,
Test: specReport.testName,
Package: report.goPkg,
Output: ptr(stdOut),
FailedBuild: "",
})
}
for _, ev := range events {
err := r.writeEvent(ev)
if err != nil {
return err
}
}
return nil
}
func (r *GoJSONEventWriter) WriteSpecResult(report *gojsonReport, specReport *gojsonSpecReport) error {
e := &gojsonEvent{
Time: &specReport.o.EndTime,
Action: specReport.action,
Test: specReport.testName,
Package: report.goPkg,
Elapsed: ptr(specReport.elapsed),
Output: nil,
FailedBuild: "",
}
return r.writeEvent(e)
}

View File

@@ -0,0 +1,45 @@
package reporters
import (
"github.com/onsi/ginkgo/v2/types"
)
type GoJSONReporter struct {
ev *GoJSONEventWriter
}
type specSystemExtractFn func (spec types.SpecReport) string
func NewGoJSONReporter(enc encoder, errFn specSystemExtractFn, outFn specSystemExtractFn) *GoJSONReporter {
return &GoJSONReporter{
ev: NewGoJSONEventWriter(enc, errFn, outFn),
}
}
func (r *GoJSONReporter) Write(originalReport types.Report) error {
// suite start events
report := newReport(originalReport)
err := report.Fill()
if err != nil {
return err
}
r.ev.WriteSuiteStart(report)
for _, originalSpecReport := range originalReport.SpecReports {
specReport := newSpecReport(originalSpecReport)
err := specReport.Fill()
if err != nil {
return err
}
if specReport.o.LeafNodeType == types.NodeTypeIt {
// handle any It leaf node as a spec
r.ev.WriteSpecStart(report, specReport)
r.ev.WriteSpecOut(report, specReport)
r.ev.WriteSpecResult(report, specReport)
} else {
// handle any other leaf node as generic output
r.ev.WriteSpecOut(report, specReport)
}
}
r.ev.WriteSuiteResult(report)
return nil
}

View File

@@ -0,0 +1,61 @@
package reporters
import (
"encoding/json"
"fmt"
"os"
"path"
"github.com/onsi/ginkgo/v2/internal/reporters"
"github.com/onsi/ginkgo/v2/types"
)
// GenerateGoTestJSONReport produces a JSON-formatted in the test2json format used by `go test -json`
func GenerateGoTestJSONReport(report types.Report, destination string) error {
// walk report and generate test2json-compatible objects
// JSON-encode the objects into filename
if err := os.MkdirAll(path.Dir(destination), 0770); err != nil {
return err
}
f, err := os.Create(destination)
if err != nil {
return err
}
defer f.Close()
enc := json.NewEncoder(f)
r := reporters.NewGoJSONReporter(
enc,
systemErrForUnstructuredReporters,
systemOutForUnstructuredReporters,
)
return r.Write(report)
}
// MergeJSONReports produces a single JSON-formatted report at the passed in destination by merging the JSON-formatted reports provided in sources
// It skips over reports that fail to decode but reports on them via the returned messages []string
func MergeAndCleanupGoTestJSONReports(sources []string, destination string) ([]string, error) {
messages := []string{}
if err := os.MkdirAll(path.Dir(destination), 0770); err != nil {
return messages, err
}
f, err := os.Create(destination)
if err != nil {
return messages, err
}
defer f.Close()
for _, source := range sources {
data, err := os.ReadFile(source)
if err != nil {
messages = append(messages, fmt.Sprintf("Could not open %s:\n%s", source, err.Error()))
continue
}
_, err = f.Write(data)
if err != nil {
messages = append(messages, fmt.Sprintf("Could not write to %s:\n%s", destination, err.Error()))
continue
}
os.Remove(source)
}
return messages, nil
}

View File

@@ -165,7 +165,7 @@ ReportAfterSuite nodes must be created at the top-level (i.e. not nested in a Co
When running in parallel, Ginkgo ensures that only one of the parallel nodes runs the ReportAfterSuite and that it is passed a report that is aggregated across
all parallel nodes
In addition to using ReportAfterSuite to programmatically generate suite reports, you can also generate JSON, JUnit, and Teamcity formatted reports using the --json-report, --junit-report, and --teamcity-report ginkgo CLI flags.
In addition to using ReportAfterSuite to programmatically generate suite reports, you can also generate JSON, GoJSON, JUnit, and Teamcity formatted reports using the --json-report, --gojson-report, --junit-report, and --teamcity-report ginkgo CLI flags.
You cannot nest any other Ginkgo nodes within a ReportAfterSuite node's closure.
You can learn more about ReportAfterSuite here: https://onsi.github.io/ginkgo/#generating-reports-programmatically
@@ -188,6 +188,12 @@ func registerReportAfterSuiteNodeForAutogeneratedReports(reporterConfig types.Re
Fail(fmt.Sprintf("Failed to generate JSON report:\n%s", err.Error()))
}
}
if reporterConfig.GoJSONReport != "" {
err := reporters.GenerateGoTestJSONReport(report, reporterConfig.GoJSONReport)
if err != nil {
Fail(fmt.Sprintf("Failed to generate Go JSON report:\n%s", err.Error()))
}
}
if reporterConfig.JUnitReport != "" {
err := reporters.GenerateJUnitReport(report, reporterConfig.JUnitReport)
if err != nil {
@@ -206,6 +212,9 @@ func registerReportAfterSuiteNodeForAutogeneratedReports(reporterConfig types.Re
if reporterConfig.JSONReport != "" {
flags = append(flags, "--json-report")
}
if reporterConfig.GoJSONReport != "" {
flags = append(flags, "--gojson-report")
}
if reporterConfig.JUnitReport != "" {
flags = append(flags, "--junit-report")
}

View File

@@ -96,6 +96,7 @@ type ReporterConfig struct {
ForceNewlines bool
JSONReport string
GoJSONReport string
JUnitReport string
TeamcityReport string
}
@@ -112,7 +113,7 @@ func (rc ReporterConfig) Verbosity() VerbosityLevel {
}
func (rc ReporterConfig) WillGenerateReport() bool {
return rc.JSONReport != "" || rc.JUnitReport != "" || rc.TeamcityReport != ""
return rc.JSONReport != "" || rc.GoJSONReport != "" || rc.JUnitReport != "" || rc.TeamcityReport != ""
}
func NewDefaultReporterConfig() ReporterConfig {
@@ -359,6 +360,8 @@ var ReporterConfigFlags = GinkgoFlags{
{KeyPath: "R.JSONReport", Name: "json-report", UsageArgument: "filename.json", SectionKey: "output",
Usage: "If set, Ginkgo will generate a JSON-formatted test report at the specified location."},
{KeyPath: "R.GoJSONReport", Name: "gojson-report", UsageArgument: "filename.json", SectionKey: "output",
Usage: "If set, Ginkgo will generate a Go JSON-formatted test report at the specified location."},
{KeyPath: "R.JUnitReport", Name: "junit-report", UsageArgument: "filename.xml", SectionKey: "output", DeprecatedName: "reportFile", DeprecatedDocLink: "improved-reporting-infrastructure",
Usage: "If set, Ginkgo will generate a conformant junit test report in the specified file."},
{KeyPath: "R.TeamcityReport", Name: "teamcity-report", UsageArgument: "filename", SectionKey: "output",

View File

@@ -1,3 +1,3 @@
package types
const VERSION = "2.25.3"
const VERSION = "2.26.0"

27
vendor/golang.org/x/xerrors/LICENSE generated vendored
View File

@@ -1,27 +0,0 @@
Copyright (c) 2019 The Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

22
vendor/golang.org/x/xerrors/PATENTS generated vendored
View File

@@ -1,22 +0,0 @@
Additional IP Rights Grant (Patents)
"This implementation" means the copyrightable works distributed by
Google as part of the Go project.
Google hereby grants to You a perpetual, worldwide, non-exclusive,
no-charge, royalty-free, irrevocable (except as stated in this section)
patent license to make, have made, use, offer to sell, sell, import,
transfer and otherwise run, modify and propagate the contents of this
implementation of Go, where such license applies only to those patent
claims, both currently owned or controlled by Google and acquired in
the future, licensable by Google that are necessarily infringed by this
implementation of Go. This grant does not include claims that would be
infringed only as a consequence of further modification of this
implementation. If you or your agent or exclusive licensee institute or
order or agree to the institution of patent litigation against any
entity (including a cross-claim or counterclaim in a lawsuit) alleging
that this implementation of Go or any code incorporated within this
implementation of Go constitutes direct or contributory patent
infringement, or inducement of patent infringement, then any patent
rights granted to you under this License for this implementation of Go
shall terminate as of the date such litigation is filed.

2
vendor/golang.org/x/xerrors/README generated vendored
View File

@@ -1,2 +0,0 @@
This repository holds the transition packages for the new Go 1.13 error values.
See golang.org/design/29934-error-values.

View File

@@ -1,193 +0,0 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package xerrors
import (
"bytes"
"fmt"
"io"
"reflect"
"strconv"
)
// FormatError calls the FormatError method of f with an errors.Printer
// configured according to s and verb, and writes the result to s.
func FormatError(f Formatter, s fmt.State, verb rune) {
// Assuming this function is only called from the Format method, and given
// that FormatError takes precedence over Format, it cannot be called from
// any package that supports errors.Formatter. It is therefore safe to
// disregard that State may be a specific printer implementation and use one
// of our choice instead.
// limitations: does not support printing error as Go struct.
var (
sep = " " // separator before next error
p = &state{State: s}
direct = true
)
var err error = f
switch verb {
// Note that this switch must match the preference order
// for ordinary string printing (%#v before %+v, and so on).
case 'v':
if s.Flag('#') {
if stringer, ok := err.(fmt.GoStringer); ok {
io.WriteString(&p.buf, stringer.GoString())
goto exit
}
// proceed as if it were %v
} else if s.Flag('+') {
p.printDetail = true
sep = "\n - "
}
case 's':
case 'q', 'x', 'X':
// Use an intermediate buffer in the rare cases that precision,
// truncation, or one of the alternative verbs (q, x, and X) are
// specified.
direct = false
default:
p.buf.WriteString("%!")
p.buf.WriteRune(verb)
p.buf.WriteByte('(')
switch {
case err != nil:
p.buf.WriteString(reflect.TypeOf(f).String())
default:
p.buf.WriteString("<nil>")
}
p.buf.WriteByte(')')
io.Copy(s, &p.buf)
return
}
loop:
for {
switch v := err.(type) {
case Formatter:
err = v.FormatError((*printer)(p))
case fmt.Formatter:
v.Format(p, 'v')
break loop
default:
io.WriteString(&p.buf, v.Error())
break loop
}
if err == nil {
break
}
if p.needColon || !p.printDetail {
p.buf.WriteByte(':')
p.needColon = false
}
p.buf.WriteString(sep)
p.inDetail = false
p.needNewline = false
}
exit:
width, okW := s.Width()
prec, okP := s.Precision()
if !direct || (okW && width > 0) || okP {
// Construct format string from State s.
format := []byte{'%'}
if s.Flag('-') {
format = append(format, '-')
}
if s.Flag('+') {
format = append(format, '+')
}
if s.Flag(' ') {
format = append(format, ' ')
}
if okW {
format = strconv.AppendInt(format, int64(width), 10)
}
if okP {
format = append(format, '.')
format = strconv.AppendInt(format, int64(prec), 10)
}
format = append(format, string(verb)...)
fmt.Fprintf(s, string(format), p.buf.String())
} else {
io.Copy(s, &p.buf)
}
}
var detailSep = []byte("\n ")
// state tracks error printing state. It implements fmt.State.
type state struct {
fmt.State
buf bytes.Buffer
printDetail bool
inDetail bool
needColon bool
needNewline bool
}
func (s *state) Write(b []byte) (n int, err error) {
if s.printDetail {
if len(b) == 0 {
return 0, nil
}
if s.inDetail && s.needColon {
s.needNewline = true
if b[0] == '\n' {
b = b[1:]
}
}
k := 0
for i, c := range b {
if s.needNewline {
if s.inDetail && s.needColon {
s.buf.WriteByte(':')
s.needColon = false
}
s.buf.Write(detailSep)
s.needNewline = false
}
if c == '\n' {
s.buf.Write(b[k:i])
k = i + 1
s.needNewline = true
}
}
s.buf.Write(b[k:])
if !s.inDetail {
s.needColon = true
}
} else if !s.inDetail {
s.buf.Write(b)
}
return len(b), nil
}
// printer wraps a state to implement an xerrors.Printer.
type printer state
func (s *printer) Print(args ...interface{}) {
if !s.inDetail || s.printDetail {
fmt.Fprint((*state)(s), args...)
}
}
func (s *printer) Printf(format string, args ...interface{}) {
if !s.inDetail || s.printDetail {
fmt.Fprintf((*state)(s), format, args...)
}
}
func (s *printer) Detail() bool {
s.inDetail = true
return s.printDetail
}

View File

@@ -1 +0,0 @@
issuerepo: golang/go

23
vendor/golang.org/x/xerrors/doc.go generated vendored
View File

@@ -1,23 +0,0 @@
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package xerrors implements functions to manipulate errors.
//
// This package is based on the Go 2 proposal for error values:
//
// https://golang.org/design/29934-error-values
//
// These functions were incorporated into the standard library's errors package
// in Go 1.13:
// - Is
// - As
// - Unwrap
//
// Also, Errorf's %w verb was incorporated into fmt.Errorf.
//
// Use this package to get equivalent behavior in all supported Go versions.
//
// No other features of this package were included in Go 1.13, and at present
// there are no plans to include any of them.
package xerrors // import "golang.org/x/xerrors"

View File

@@ -1,33 +0,0 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package xerrors
import "fmt"
// errorString is a trivial implementation of error.
type errorString struct {
s string
frame Frame
}
// New returns an error that formats as the given text.
//
// The returned error contains a Frame set to the caller's location and
// implements Formatter to show this information when printed with details.
func New(text string) error {
return &errorString{text, Caller(1)}
}
func (e *errorString) Error() string {
return e.s
}
func (e *errorString) Format(s fmt.State, v rune) { FormatError(e, s, v) }
func (e *errorString) FormatError(p Printer) (next error) {
p.Print(e.s)
e.frame.Format(p)
return nil
}

190
vendor/golang.org/x/xerrors/fmt.go generated vendored
View File

@@ -1,190 +0,0 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package xerrors
import (
"fmt"
"strings"
"unicode"
"unicode/utf8"
"golang.org/x/xerrors/internal"
)
const percentBangString = "%!"
// Errorf formats according to a format specifier and returns the string as a
// value that satisfies error.
//
// The returned error includes the file and line number of the caller when
// formatted with additional detail enabled. If the last argument is an error
// the returned error's Format method will return it if the format string ends
// with ": %s", ": %v", or ": %w". If the last argument is an error and the
// format string ends with ": %w", the returned error implements an Unwrap
// method returning it.
//
// If the format specifier includes a %w verb with an error operand in a
// position other than at the end, the returned error will still implement an
// Unwrap method returning the operand, but the error's Format method will not
// return the wrapped error.
//
// It is invalid to include more than one %w verb or to supply it with an
// operand that does not implement the error interface. The %w verb is otherwise
// a synonym for %v.
//
// Note that as of Go 1.13, the fmt.Errorf function will do error formatting,
// but it will not capture a stack backtrace.
func Errorf(format string, a ...interface{}) error {
format = formatPlusW(format)
// Support a ": %[wsv]" suffix, which works well with xerrors.Formatter.
wrap := strings.HasSuffix(format, ": %w")
idx, format2, ok := parsePercentW(format)
percentWElsewhere := !wrap && idx >= 0
if !percentWElsewhere && (wrap || strings.HasSuffix(format, ": %s") || strings.HasSuffix(format, ": %v")) {
err := errorAt(a, len(a)-1)
if err == nil {
return &noWrapError{fmt.Sprintf(format, a...), nil, Caller(1)}
}
// TODO: this is not entirely correct. The error value could be
// printed elsewhere in format if it mixes numbered with unnumbered
// substitutions. With relatively small changes to doPrintf we can
// have it optionally ignore extra arguments and pass the argument
// list in its entirety.
msg := fmt.Sprintf(format[:len(format)-len(": %s")], a[:len(a)-1]...)
frame := Frame{}
if internal.EnableTrace {
frame = Caller(1)
}
if wrap {
return &wrapError{msg, err, frame}
}
return &noWrapError{msg, err, frame}
}
// Support %w anywhere.
// TODO: don't repeat the wrapped error's message when %w occurs in the middle.
msg := fmt.Sprintf(format2, a...)
if idx < 0 {
return &noWrapError{msg, nil, Caller(1)}
}
err := errorAt(a, idx)
if !ok || err == nil {
// Too many %ws or argument of %w is not an error. Approximate the Go
// 1.13 fmt.Errorf message.
return &noWrapError{fmt.Sprintf("%sw(%s)", percentBangString, msg), nil, Caller(1)}
}
frame := Frame{}
if internal.EnableTrace {
frame = Caller(1)
}
return &wrapError{msg, err, frame}
}
func errorAt(args []interface{}, i int) error {
if i < 0 || i >= len(args) {
return nil
}
err, ok := args[i].(error)
if !ok {
return nil
}
return err
}
// formatPlusW is used to avoid the vet check that will barf at %w.
func formatPlusW(s string) string {
return s
}
// Return the index of the only %w in format, or -1 if none.
// Also return a rewritten format string with %w replaced by %v, and
// false if there is more than one %w.
// TODO: handle "%[N]w".
func parsePercentW(format string) (idx int, newFormat string, ok bool) {
// Loosely copied from golang.org/x/tools/go/analysis/passes/printf/printf.go.
idx = -1
ok = true
n := 0
sz := 0
var isW bool
for i := 0; i < len(format); i += sz {
if format[i] != '%' {
sz = 1
continue
}
// "%%" is not a format directive.
if i+1 < len(format) && format[i+1] == '%' {
sz = 2
continue
}
sz, isW = parsePrintfVerb(format[i:])
if isW {
if idx >= 0 {
ok = false
} else {
idx = n
}
// "Replace" the last character, the 'w', with a 'v'.
p := i + sz - 1
format = format[:p] + "v" + format[p+1:]
}
n++
}
return idx, format, ok
}
// Parse the printf verb starting with a % at s[0].
// Return how many bytes it occupies and whether the verb is 'w'.
func parsePrintfVerb(s string) (int, bool) {
// Assume only that the directive is a sequence of non-letters followed by a single letter.
sz := 0
var r rune
for i := 1; i < len(s); i += sz {
r, sz = utf8.DecodeRuneInString(s[i:])
if unicode.IsLetter(r) {
return i + sz, r == 'w'
}
}
return len(s), false
}
type noWrapError struct {
msg string
err error
frame Frame
}
func (e *noWrapError) Error() string {
return fmt.Sprint(e)
}
func (e *noWrapError) Format(s fmt.State, v rune) { FormatError(e, s, v) }
func (e *noWrapError) FormatError(p Printer) (next error) {
p.Print(e.msg)
e.frame.Format(p)
return e.err
}
type wrapError struct {
msg string
err error
frame Frame
}
func (e *wrapError) Error() string {
return fmt.Sprint(e)
}
func (e *wrapError) Format(s fmt.State, v rune) { FormatError(e, s, v) }
func (e *wrapError) FormatError(p Printer) (next error) {
p.Print(e.msg)
e.frame.Format(p)
return e.err
}
func (e *wrapError) Unwrap() error {
return e.err
}

View File

@@ -1,34 +0,0 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package xerrors
// A Formatter formats error messages.
type Formatter interface {
error
// FormatError prints the receiver's first error and returns the next error in
// the error chain, if any.
FormatError(p Printer) (next error)
}
// A Printer formats error messages.
//
// The most common implementation of Printer is the one provided by package fmt
// during Printf (as of Go 1.13). Localization packages such as golang.org/x/text/message
// typically provide their own implementations.
type Printer interface {
// Print appends args to the message output.
Print(args ...interface{})
// Printf writes a formatted string.
Printf(format string, args ...interface{})
// Detail reports whether error detail is requested.
// After the first call to Detail, all text written to the Printer
// is formatted as additional detail, or ignored when
// detail has not been requested.
// If Detail returns false, the caller can avoid printing the detail at all.
Detail() bool
}

56
vendor/golang.org/x/xerrors/frame.go generated vendored
View File

@@ -1,56 +0,0 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package xerrors
import (
"runtime"
)
// A Frame contains part of a call stack.
type Frame struct {
// Make room for three PCs: the one we were asked for, what it called,
// and possibly a PC for skipPleaseUseCallersFrames. See:
// https://go.googlesource.com/go/+/032678e0fb/src/runtime/extern.go#169
frames [3]uintptr
}
// Caller returns a Frame that describes a frame on the caller's stack.
// The argument skip is the number of frames to skip over.
// Caller(0) returns the frame for the caller of Caller.
func Caller(skip int) Frame {
var s Frame
runtime.Callers(skip+1, s.frames[:])
return s
}
// location reports the file, line, and function of a frame.
//
// The returned function may be "" even if file and line are not.
func (f Frame) location() (function, file string, line int) {
frames := runtime.CallersFrames(f.frames[:])
if _, ok := frames.Next(); !ok {
return "", "", 0
}
fr, ok := frames.Next()
if !ok {
return "", "", 0
}
return fr.Function, fr.File, fr.Line
}
// Format prints the stack as error detail.
// It should be called from an error's Format implementation
// after printing any other error detail.
func (f Frame) Format(p Printer) {
if p.Detail() {
function, file, line := f.location()
if function != "" {
p.Printf("%s\n ", function)
}
if file != "" {
p.Printf("%s:%d\n", file, line)
}
}
}

View File

@@ -1,8 +0,0 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package internal
// EnableTrace indicates whether stack information should be recorded in errors.
var EnableTrace = true

112
vendor/golang.org/x/xerrors/wrap.go generated vendored
View File

@@ -1,112 +0,0 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package xerrors
import (
"reflect"
)
// A Wrapper provides context around another error.
type Wrapper interface {
// Unwrap returns the next error in the error chain.
// If there is no next error, Unwrap returns nil.
Unwrap() error
}
// Opaque returns an error with the same error formatting as err
// but that does not match err and cannot be unwrapped.
func Opaque(err error) error {
return noWrapper{err}
}
type noWrapper struct {
error
}
func (e noWrapper) FormatError(p Printer) (next error) {
if f, ok := e.error.(Formatter); ok {
return f.FormatError(p)
}
p.Print(e.error)
return nil
}
// Unwrap returns the result of calling the Unwrap method on err, if err implements
// Unwrap. Otherwise, Unwrap returns nil.
//
// Deprecated: As of Go 1.13, use errors.Unwrap instead.
func Unwrap(err error) error {
u, ok := err.(Wrapper)
if !ok {
return nil
}
return u.Unwrap()
}
// Is reports whether any error in err's chain matches target.
//
// An error is considered to match a target if it is equal to that target or if
// it implements a method Is(error) bool such that Is(target) returns true.
//
// Deprecated: As of Go 1.13, use errors.Is instead.
func Is(err, target error) bool {
if target == nil {
return err == target
}
isComparable := reflect.TypeOf(target).Comparable()
for {
if isComparable && err == target {
return true
}
if x, ok := err.(interface{ Is(error) bool }); ok && x.Is(target) {
return true
}
// TODO: consider supporing target.Is(err). This would allow
// user-definable predicates, but also may allow for coping with sloppy
// APIs, thereby making it easier to get away with them.
if err = Unwrap(err); err == nil {
return false
}
}
}
// As finds the first error in err's chain that matches the type to which target
// points, and if so, sets the target to its value and returns true. An error
// matches a type if it is assignable to the target type, or if it has a method
// As(interface{}) bool such that As(target) returns true. As will panic if target
// is not a non-nil pointer to a type which implements error or is of interface type.
//
// The As method should set the target to its value and return true if err
// matches the type to which target points.
//
// Deprecated: As of Go 1.13, use errors.As instead.
func As(err error, target interface{}) bool {
if target == nil {
panic("errors: target cannot be nil")
}
val := reflect.ValueOf(target)
typ := val.Type()
if typ.Kind() != reflect.Ptr || val.IsNil() {
panic("errors: target must be a non-nil pointer")
}
if e := typ.Elem(); e.Kind() != reflect.Interface && !e.Implements(errorType) {
panic("errors: *target must be interface or implement error")
}
targetType := typ.Elem()
for err != nil {
if reflect.TypeOf(err).AssignableTo(targetType) {
val.Elem().Set(reflect.ValueOf(err))
return true
}
if x, ok := err.(interface{ As(interface{}) bool }); ok && x.As(target) {
return true
}
err = Unwrap(err)
}
return false
}
var errorType = reflect.TypeOf((*error)(nil)).Elem()

12
vendor/modules.txt vendored
View File

@@ -679,11 +679,12 @@ github.com/goccy/go-json/internal/encoder/vm_color_indent
github.com/goccy/go-json/internal/encoder/vm_indent
github.com/goccy/go-json/internal/errors
github.com/goccy/go-json/internal/runtime
# github.com/goccy/go-yaml v1.12.0
## explicit; go 1.19
# github.com/goccy/go-yaml v1.18.0
## explicit; go 1.21.0
github.com/goccy/go-yaml
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/lexer
github.com/goccy/go-yaml/parser
github.com/goccy/go-yaml/printer
@@ -1199,7 +1200,7 @@ github.com/onsi/ginkgo/reporters/stenographer
github.com/onsi/ginkgo/reporters/stenographer/support/go-colorable
github.com/onsi/ginkgo/reporters/stenographer/support/go-isatty
github.com/onsi/ginkgo/types
# github.com/onsi/ginkgo/v2 v2.25.3
# github.com/onsi/ginkgo/v2 v2.26.0
## explicit; go 1.23.0
github.com/onsi/ginkgo/v2
github.com/onsi/ginkgo/v2/config
@@ -1218,6 +1219,7 @@ github.com/onsi/ginkgo/v2/internal
github.com/onsi/ginkgo/v2/internal/global
github.com/onsi/ginkgo/v2/internal/interrupt_handler
github.com/onsi/ginkgo/v2/internal/parallel_support
github.com/onsi/ginkgo/v2/internal/reporters
github.com/onsi/ginkgo/v2/internal/testingtproxy
github.com/onsi/ginkgo/v2/reporters
github.com/onsi/ginkgo/v2/types
@@ -2498,10 +2500,6 @@ golang.org/x/tools/internal/stdlib
golang.org/x/tools/internal/typeparams
golang.org/x/tools/internal/typesinternal
golang.org/x/tools/internal/versions
# golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2
## explicit; go 1.17
golang.org/x/xerrors
golang.org/x/xerrors/internal
# google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb
## explicit; go 1.23.0
google.golang.org/genproto/protobuf/field_mask