support multiple commands in sesh
This commit is contained in:
+5
-2
@@ -2,11 +2,15 @@ module github.com/roodletoof/sesh
|
||||
|
||||
go 1.25.6
|
||||
|
||||
require (
|
||||
github.com/goccy/go-yaml v1.19.2
|
||||
github.com/ktr0731/go-fuzzyfinder v0.9.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/gdamore/encoding v1.0.1 // indirect
|
||||
github.com/gdamore/tcell/v2 v2.6.0 // indirect
|
||||
github.com/ktr0731/go-ansisgr v0.1.0 // indirect
|
||||
github.com/ktr0731/go-fuzzyfinder v0.9.0 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||
github.com/nsf/termbox-go v1.1.1 // indirect
|
||||
@@ -15,5 +19,4 @@ require (
|
||||
golang.org/x/sys v0.32.0 // indirect
|
||||
golang.org/x/term v0.31.0 // indirect
|
||||
golang.org/x/text v0.24.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
+6
-3
@@ -3,6 +3,12 @@ github.com/gdamore/encoding v1.0.1 h1:YzKZckdBL6jVt2Gc+5p82qhrGiqMdG/eNs6Wy0u3Uh
|
||||
github.com/gdamore/encoding v1.0.1/go.mod h1:0Z0cMFinngz9kS1QfMjCP8TY7em3bZYeeklsSDPivEo=
|
||||
github.com/gdamore/tcell/v2 v2.6.0 h1:OKbluoP9VYmJwZwq/iLb4BxwKcwGthaa1YNBJIyCySg=
|
||||
github.com/gdamore/tcell/v2 v2.6.0/go.mod h1:be9omFATkdr0D9qewWW3d+MEvl5dha+Etb5y65J2H8Y=
|
||||
github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
|
||||
github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/ktr0731/go-ansisgr v0.1.0 h1:fbuupput8739hQbEmZn1cEKjqQFwtCCZNznnF6ANo5w=
|
||||
github.com/ktr0731/go-ansisgr v0.1.0/go.mod h1:G9lxwgBwH0iey0Dw5YQd7n6PmQTwTuTM/X5Sgm/UrzE=
|
||||
github.com/ktr0731/go-fuzzyfinder v0.9.0 h1:JV8S118RABzRl3Lh/RsPhXReJWc2q0rbuipzXQH7L4c=
|
||||
@@ -58,6 +64,3 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
||||
+37
-10
@@ -1,6 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
@@ -9,14 +10,37 @@ import (
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/goccy/go-yaml"
|
||||
"github.com/ktr0731/go-fuzzyfinder"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
var _ yaml.InterfaceUnmarshaler = &Programs{}
|
||||
type Programs struct {
|
||||
programs []string
|
||||
}
|
||||
|
||||
// UnmarshalYAML implements [yaml.InterfaceUnmarshaler].
|
||||
func (p *Programs) UnmarshalYAML(f func(any) error) error {
|
||||
var singleStr string
|
||||
err1 := f(&singleStr)
|
||||
if err1 == nil {
|
||||
p.programs = []string{singleStr}
|
||||
return nil
|
||||
}
|
||||
err2 := f(&p.programs)
|
||||
if err2 != nil {
|
||||
return errors.Join(
|
||||
err1,
|
||||
err2,
|
||||
)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Window struct {
|
||||
Path string `yaml:"path"`
|
||||
Name string `yaml:"name"`
|
||||
Program string `yaml:"program"`
|
||||
Path string `yaml:"path"`
|
||||
Name string `yaml:"name"`
|
||||
Programs Programs `yaml:"program"`
|
||||
}
|
||||
|
||||
func (w Window) ExpandedPath() string {
|
||||
@@ -30,7 +54,7 @@ func (w Window) ExpandedPath() string {
|
||||
type Config map[string][]Window
|
||||
|
||||
const (
|
||||
tmux = "tmux"
|
||||
tmux = "tmux"
|
||||
mkdir = "mkdir"
|
||||
)
|
||||
|
||||
@@ -74,7 +98,7 @@ func main() {
|
||||
},
|
||||
fuzzyfinder.WithPreviewWindow(
|
||||
func(i, width, height int) string {
|
||||
width = width/2-4
|
||||
width = width/2 - 4
|
||||
if i < 0 {
|
||||
return ""
|
||||
}
|
||||
@@ -90,8 +114,11 @@ func main() {
|
||||
if window.Path != "" {
|
||||
fmt.Fprintf(&builder, " path: %s\n", window.Path)
|
||||
}
|
||||
if window.Program != "" {
|
||||
fmt.Fprintf(&builder, " prog: %s\n", window.Program)
|
||||
if len(window.Programs.programs) != 0 {
|
||||
fmt.Fprintf(&builder, " program:\n")
|
||||
}
|
||||
for _, prog := range window.Programs.programs {
|
||||
fmt.Fprintf(&builder, " - %s\n", prog)
|
||||
}
|
||||
fmt.Fprint(&builder, "\n")
|
||||
}
|
||||
@@ -164,9 +191,9 @@ func NewWindowInSession(seshName string, window Window) {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if window.Program != "" {
|
||||
for _, prog := range window.Programs.programs {
|
||||
var carriageReturn = string([]byte{13})
|
||||
cmd = exec.Command(tmux, "send-keys", "-t", fmt.Sprintf("%s:$", seshName), window.Program, carriageReturn)
|
||||
cmd = exec.Command(tmux, "send-keys", "-t", fmt.Sprintf("%s:$", seshName), prog, carriageReturn)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Stdin = os.Stdin
|
||||
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
codecov:
|
||||
require_ci_to_pass: yes
|
||||
|
||||
coverage:
|
||||
precision: 2
|
||||
round: down
|
||||
range: "70...100"
|
||||
|
||||
status:
|
||||
project:
|
||||
default:
|
||||
target: 75%
|
||||
threshold: 2%
|
||||
patch: off
|
||||
changes: no
|
||||
|
||||
parsers:
|
||||
gcov:
|
||||
branch_detection:
|
||||
conditional: yes
|
||||
loop: yes
|
||||
method: no
|
||||
macro: no
|
||||
|
||||
comment:
|
||||
layout: "header,diff"
|
||||
behavior: default
|
||||
require_changes: no
|
||||
|
||||
ignore:
|
||||
- ast
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
bin/
|
||||
.idea/
|
||||
cover.out
|
||||
+65
@@ -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$
|
||||
+186
@@ -0,0 +1,186 @@
|
||||
# 1.11.2 - 2023-09-15
|
||||
|
||||
### Fix bugs
|
||||
|
||||
- Fix quoted comments ( #370 )
|
||||
- Fix handle of space at start or last ( #376 )
|
||||
- Fix sequence with comment ( #390 )
|
||||
|
||||
# 1.11.1 - 2023-09-14
|
||||
|
||||
### Fix bugs
|
||||
|
||||
- Handle `\r` in a double-quoted string the same as `\n` ( #372 )
|
||||
- Replace loop with n.Values = append(n.Values, target.Values...) ( #380 )
|
||||
- Skip encoding an inline field if it is null ( #386 )
|
||||
- Fix comment parsing with null value ( #388 )
|
||||
|
||||
# 1.11.0 - 2023-04-03
|
||||
|
||||
### Features
|
||||
|
||||
- Supports dynamically switch encode and decode processing for a given type
|
||||
|
||||
# 1.10.1 - 2023-03-28
|
||||
|
||||
### Features
|
||||
|
||||
- Quote YAML 1.1 bools at encoding time for compatibility with other legacy parsers
|
||||
- Add support of 32-bit architecture
|
||||
|
||||
### Fix bugs
|
||||
|
||||
- Don't trim all space characters in block style sequence
|
||||
- Support strings starting with `@`
|
||||
|
||||
# 1.10.0 - 2023-03-01
|
||||
|
||||
### Fix bugs
|
||||
|
||||
Reversible conversion of comments was not working in various cases, which has been corrected.
|
||||
**Breaking Change** exists in the comment map interface. However, if you are dealing with CommentMap directly, there is no problem.
|
||||
|
||||
|
||||
# 1.9.8 - 2022-12-19
|
||||
|
||||
### Fix feature
|
||||
|
||||
- Append new line at the end of file ( #329 )
|
||||
|
||||
### Fix bugs
|
||||
|
||||
- Fix custom marshaler ( #333, #334 )
|
||||
- Fix behavior when struct fields conflicted( #335 )
|
||||
- Fix position calculation for literal, folded and raw folded strings ( #330 )
|
||||
|
||||
# 1.9.7 - 2022-12-03
|
||||
|
||||
### Fix bugs
|
||||
|
||||
- Fix handling of quoted map key ( #328 )
|
||||
- Fix resusing process of scanning context ( #322 )
|
||||
|
||||
## v1.9.6 - 2022-10-26
|
||||
|
||||
### New Features
|
||||
|
||||
- Introduce MapKeyNode interface to limit node types for map key ( #312 )
|
||||
|
||||
### Fix bugs
|
||||
|
||||
- Quote strings with special characters in flow mode ( #270 )
|
||||
- typeError implements PrettyPrinter interface ( #280 )
|
||||
- Fix incorrect const type ( #284 )
|
||||
- Fix large literals type inference on 32 bits ( #293 )
|
||||
- Fix UTF-8 characters ( #294 )
|
||||
- Fix decoding of unknown aliases ( #317 )
|
||||
- Fix stream encoder for insert a separator between each encoded document ( #318 )
|
||||
|
||||
### Update
|
||||
|
||||
- Update golang.org/x/sys ( #289 )
|
||||
- Update Go version in CI ( #295 )
|
||||
- Add test cases for missing keys to struct literals ( #300 )
|
||||
|
||||
## v1.9.5 - 2022-01-12
|
||||
|
||||
### New Features
|
||||
|
||||
* Add UseSingleQuote option ( #265 )
|
||||
|
||||
### Fix bugs
|
||||
|
||||
* Preserve defaults while decoding nested structs ( #260 )
|
||||
* Fix minor typo in decodeInit error ( #264 )
|
||||
* Handle empty sequence entries ( #275 )
|
||||
* Fix encoding of sequence with multiline string ( #276 )
|
||||
* Fix encoding of BytesMarshaler type ( #277 )
|
||||
* Fix indentState logic for multi-line value ( #278 )
|
||||
|
||||
## v1.9.4 - 2021-10-12
|
||||
|
||||
### Fix bugs
|
||||
|
||||
* Keep prev/next reference between tokens containing comments when filtering comment tokens ( #257 )
|
||||
* Supports escaping reserved keywords in PathBuilder ( #258 )
|
||||
|
||||
## v1.9.3 - 2021-09-07
|
||||
|
||||
### New Features
|
||||
|
||||
* Support encoding and decoding `time.Duration` fields ( #246 )
|
||||
* Allow reserved characters for key name in YAMLPath ( #251 )
|
||||
* Support getting YAMLPath from ast.Node ( #252 )
|
||||
* Support CommentToMap option ( #253 )
|
||||
|
||||
### Fix bugs
|
||||
|
||||
* Fix encoding nested sequences with `yaml.IndentSequence` ( #241 )
|
||||
* Fix error reporting on inline structs in strict mode ( #244, #245 )
|
||||
* Fix encoding of large floats ( #247 )
|
||||
|
||||
### Improve workflow
|
||||
|
||||
* Migrate CI from CircleCI to GitHub Action ( #249 )
|
||||
* Add workflow for ycat ( #250 )
|
||||
|
||||
## v1.9.2 - 2021-07-26
|
||||
|
||||
### Support WithComment option ( #238 )
|
||||
|
||||
`yaml.WithComment` is a option for encoding with comment.
|
||||
The position where you want to add a comment is represented by YAMLPath, and it is the key of `yaml.CommentMap`.
|
||||
Also, you can select `Head` comment or `Line` comment as the comment type.
|
||||
|
||||
## v1.9.1 - 2021-07-20
|
||||
|
||||
### Fix DecodeFromNode ( #237 )
|
||||
|
||||
- Fix YAML handling where anchor exists
|
||||
|
||||
## v1.9.0 - 2021-07-19
|
||||
|
||||
### New features
|
||||
|
||||
- Support encoding of comment node ( #233 )
|
||||
- Support `yaml.NodeToValue(ast.Node, interface{}, ...DecodeOption) error` ( #236 )
|
||||
- Can convert a AST node to a value directly
|
||||
|
||||
### Fix decoder for comment
|
||||
|
||||
- Fix parsing of literal with comment ( #234 )
|
||||
|
||||
### Rename API ( #235 )
|
||||
|
||||
- Rename `MarshalWithContext` to `MarshalContext`
|
||||
- Rename `UnmarshalWithContext` to `UnmarshalContext`
|
||||
|
||||
## v1.8.10 - 2021-07-02
|
||||
|
||||
### Fixed bugs
|
||||
|
||||
- Fix searching anchor by alias name ( #212 )
|
||||
- Fixing Issue 186, scanner should account for newline characters when processing multi-line text. Without this source annotations line/column number (for this and all subsequent tokens) is inconsistent with plain text editors. e.g. https://github.com/goccy/go-yaml/issues/186. This addresses the issue specifically for single and double quote text only. ( #210 )
|
||||
- Add error for unterminated flow mapping node ( #213 )
|
||||
- Handle missing required field validation ( #221 )
|
||||
- Nicely format unexpected node type errors ( #229 )
|
||||
- Support to encode map which has defined type key ( #231 )
|
||||
|
||||
### New features
|
||||
|
||||
- Support sequence indentation by EncodeOption ( #232 )
|
||||
|
||||
## v1.8.9 - 2021-03-01
|
||||
|
||||
### Fixed bugs
|
||||
|
||||
- Fix origin buffer for DocumentHeader and DocumentEnd and Directive
|
||||
- Fix origin buffer for anchor value
|
||||
- Fix syntax error about map value
|
||||
- Fix parsing MergeKey ('<<') characters
|
||||
- Fix encoding of float value
|
||||
- Fix incorrect column annotation when single or double quotes are used
|
||||
|
||||
### New features
|
||||
|
||||
- Support to encode/decode of ast.Node directly
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 Masaaki Goshima
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
+55
@@ -0,0 +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 -coverpkg=.,./ast,./lexer,./parser,./printer,./scanner,./token -coverprofile=cover.out -modfile=$(TESTMOD) ./... ./testdata
|
||||
|
||||
.PHONY: cover-html
|
||||
cover-html: cover
|
||||
go tool cover -html=cover.out
|
||||
|
||||
.PHONY: ycat/build
|
||||
ycat/build: $(LOCALBIN)
|
||||
cd ./cmd/ycat && go build -o $(LOCALBIN)/ycat .
|
||||
|
||||
.PHONY: lint
|
||||
lint: golangci-lint ## Run golangci-lint
|
||||
@$(GOLANGCI_LINT) run
|
||||
|
||||
.PHONY: fmt
|
||||
fmt: golangci-lint ## Ensure consistent code style
|
||||
@go mod tidy
|
||||
@go fmt ./...
|
||||
@$(GOLANGCI_LINT) run --fix
|
||||
|
||||
## Tool Binaries
|
||||
GOLANGCI_LINT ?= $(LOCALBIN)/golangci-lint
|
||||
|
||||
## Tool Versions
|
||||
GOLANGCI_VERSION := 2.1.2
|
||||
|
||||
.PHONY: golangci-lint
|
||||
.PHONY: $(GOLANGCI_LINT)
|
||||
golangci-lint: $(GOLANGCI_LINT) ## Download golangci-lint locally if necessary.
|
||||
$(GOLANGCI_LINT): $(LOCALBIN)
|
||||
@test -s $(LOCALBIN)/golangci-lint && $(LOCALBIN)/golangci-lint version --short | grep -q $(GOLANGCI_VERSION) || \
|
||||
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(LOCALBIN) v$(GOLANGCI_VERSION)
|
||||
+420
@@ -0,0 +1,420 @@
|
||||
# YAML support for the Go language
|
||||
|
||||
[](https://pkg.go.dev/github.com/goccy/go-yaml)
|
||||

|
||||
[](https://codecov.io/gh/goccy/go-yaml)
|
||||
[](https://goreportcard.com/report/github.com/goccy/go-yaml)
|
||||
|
||||
<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 believe that a new YAML library is necessary for the following reasons:
|
||||
|
||||
- Not actively maintained
|
||||
- `go-yaml/yaml` has ported the libyaml written in C to Go, so the source code is not written in Go style
|
||||
- There is a lot of content that cannot be parsed
|
||||
- YAML is often used for configuration, and it is common to include validation along with it. However, the errors in `go-yaml/yaml` are not intuitive, and it is difficult to provide meaningful validation errors
|
||||
- When creating tools that use YAML, there are cases where reversible transformation of YAML is required. However, to perform reversible transformations of content that includes Comments or Anchors/Aliases, manipulating the AST is the only option
|
||||
- Non-intuitive [Marshaler](https://pkg.go.dev/gopkg.in/yaml.v3#Marshaler) / [Unmarshaler](https://pkg.go.dev/gopkg.in/yaml.v3#Unmarshaler)
|
||||
|
||||
By the way, libraries such as [ghodss/yaml](https://github.com/ghodss/yaml) and [sigs.k8s.io/yaml](https://github.com/kubernetes-sigs/yaml) also depend on go-yaml/yaml, so if you are using these libraries, the same issues apply: they cannot parse things that go-yaml/yaml cannot parse, and they inherit many of the problems that go-yaml/yaml has.
|
||||
|
||||
# Features
|
||||
|
||||
- No dependencies
|
||||
- A better parser than `go-yaml/yaml`.
|
||||
- [Support recursive processing](https://github.com/apple/device-management/blob/release/docs/schema.yaml)
|
||||
- Higher coverage in the [YAML Test Suite](https://github.com/yaml/yaml-test-suite?tab=readme-ov-file)
|
||||
- YAML Test Suite consists of 402 cases in total, of which `gopkg.in/yaml.v3` passes `295`. In addition to passing all those test cases, `goccy/go-yaml` successfully passes nearly 60 additional test cases ( 2024/12/15 )
|
||||
- The test code is [here](https://github.com/goccy/go-yaml/blob/master/yaml_test_suite_test.go#L77)
|
||||
- Ease and sustainability of maintenance
|
||||
- The main maintainer is [@goccy](https://github.com/goccy), but we are also building a system to develop as a team with trusted developers
|
||||
- Since it is written from scratch, the code is easy to read for Gophers
|
||||
- An API structure that allows the use of not only `Encoder`/`Decoder` but also `Tokenizer` and `Parser` functionalities.
|
||||
- [lexer.Tokenize](https://pkg.go.dev/github.com/goccy/go-yaml@v1.15.4/lexer#Tokenize)
|
||||
- [parser.Parse](https://pkg.go.dev/github.com/goccy/go-yaml@v1.15.4/parser#Parse)
|
||||
- Filtering, replacing, and merging YAML content using YAML Path
|
||||
- Reversible transformation without using the AST for YAML that includes Anchors, Aliases, and Comments
|
||||
- Customize the Marshal/Unmarshal behavior for primitive types and third-party library types ([RegisterCustomMarshaler](https://pkg.go.dev/github.com/goccy/go-yaml#RegisterCustomMarshaler), [RegisterCustomUnmarshaler](https://pkg.go.dev/github.com/goccy/go-yaml#RegisterCustomUnmarshaler))
|
||||
- Respects `encoding/json` behavior
|
||||
- Accept the `json` tag. Note that not all options from the `json` tag will have significance when parsing YAML documents. If both tags exist, `yaml` tag will take precedence.
|
||||
- [json.Marshaler](https://pkg.go.dev/encoding/json#Marshaler) style [marshaler](https://pkg.go.dev/github.com/goccy/go-yaml#BytesMarshaler)
|
||||
- [json.Unmarshaler](https://pkg.go.dev/encoding/json#Unmarshaler) style [unmarshaler](https://pkg.go.dev/github.com/goccy/go-yaml#BytesUnmarshaler)
|
||||
- Options for using `MarshalJSON` and `UnmarshalJSON` ([UseJSONMarshaler](https://pkg.go.dev/github.com/goccy/go-yaml#UseJSONMarshaler), [UseJSONUnmarshaler](https://pkg.go.dev/github.com/goccy/go-yaml#UseJSONUnmarshaler))
|
||||
- Pretty format for error notifications
|
||||
- Smart validation processing combined with [go-playground/validator](https://github.com/go-playground/validator)
|
||||
- [example test code is here](https://github.com/goccy/go-yaml/blob/45889c98b0a0967240eb595a1bd6896e2f575106/testdata/validate_test.go#L12)
|
||||
- Allow referencing elements declared in another file via anchors
|
||||
|
||||
# Users
|
||||
|
||||
The repositories that use goccy/go-yaml are listed here.
|
||||
|
||||
- https://github.com/goccy/go-yaml/wiki/Users
|
||||
|
||||
The source data is [here](https://github.com/goccy/go-yaml/network/dependents).
|
||||
It is already being used in many repositories. Now it's your turn 😄
|
||||
|
||||
# Playground
|
||||
|
||||
The Playground visualizes how go-yaml processes YAML text. Use it to assist with your debugging or issue reporting.
|
||||
|
||||
https://goccy.github.io/go-yaml
|
||||
|
||||
# Installation
|
||||
|
||||
```sh
|
||||
go get github.com/goccy/go-yaml
|
||||
```
|
||||
|
||||
# Synopsis
|
||||
|
||||
## 1. Simple Encode/Decode
|
||||
|
||||
Has an interface like `go-yaml/yaml` using `reflect`
|
||||
|
||||
```go
|
||||
var v struct {
|
||||
A int
|
||||
B string
|
||||
}
|
||||
v.A = 1
|
||||
v.B = "hello"
|
||||
bytes, err := yaml.Marshal(v)
|
||||
if err != nil {
|
||||
//...
|
||||
}
|
||||
fmt.Println(string(bytes)) // "a: 1\nb: hello\n"
|
||||
```
|
||||
|
||||
```go
|
||||
yml := `
|
||||
%YAML 1.2
|
||||
---
|
||||
a: 1
|
||||
b: c
|
||||
`
|
||||
var v struct {
|
||||
A int
|
||||
B string
|
||||
}
|
||||
if err := yaml.Unmarshal([]byte(yml), &v); err != nil {
|
||||
//...
|
||||
}
|
||||
```
|
||||
|
||||
To control marshal/unmarshal behavior, you can use the `yaml` tag.
|
||||
|
||||
```go
|
||||
yml := `---
|
||||
foo: 1
|
||||
bar: c
|
||||
`
|
||||
var v struct {
|
||||
A int `yaml:"foo"`
|
||||
B string `yaml:"bar"`
|
||||
}
|
||||
if err := yaml.Unmarshal([]byte(yml), &v); err != nil {
|
||||
//...
|
||||
}
|
||||
```
|
||||
|
||||
For convenience, we also accept the `json` tag. Note that not all options from
|
||||
the `json` tag will have significance when parsing YAML documents. If both
|
||||
tags exist, `yaml` tag will take precedence.
|
||||
|
||||
```go
|
||||
yml := `---
|
||||
foo: 1
|
||||
bar: c
|
||||
`
|
||||
var v struct {
|
||||
A int `json:"foo"`
|
||||
B string `json:"bar"`
|
||||
}
|
||||
if err := yaml.Unmarshal([]byte(yml), &v); err != nil {
|
||||
//...
|
||||
}
|
||||
```
|
||||
|
||||
For custom marshal/unmarshaling, implement either `Bytes` or `Interface` variant of marshaler/unmarshaler. The difference is that while `BytesMarshaler`/`BytesUnmarshaler` behaves like [`encoding/json`](https://pkg.go.dev/encoding/json) and `InterfaceMarshaler`/`InterfaceUnmarshaler` behaves like [`gopkg.in/yaml.v2`](https://pkg.go.dev/gopkg.in/yaml.v2).
|
||||
|
||||
Semantically both are the same, but they differ in performance. Because indentation matters in YAML, you cannot simply accept a valid YAML fragment from a Marshaler, and expect it to work when it is attached to the parent container's serialized form. Therefore when we receive use the `BytesMarshaler`, which returns `[]byte`, we must decode it once to figure out how to make it work in the given context. If you use the `InterfaceMarshaler`, we can skip the decoding.
|
||||
|
||||
If you are repeatedly marshaling complex objects, the latter is always better
|
||||
performance wise. But if you are, for example, just providing a choice between
|
||||
a config file format that is read only once, the former is probably easier to
|
||||
code.
|
||||
|
||||
## 2. Reference elements declared in another file
|
||||
|
||||
`testdata` directory contains `anchor.yml` file:
|
||||
|
||||
```shell
|
||||
├── testdata
|
||||
└── anchor.yml
|
||||
```
|
||||
|
||||
And `anchor.yml` is defined as follows:
|
||||
|
||||
```yaml
|
||||
a: &a
|
||||
b: 1
|
||||
c: hello
|
||||
```
|
||||
|
||||
Then, if `yaml.ReferenceDirs("testdata")` option is passed to `yaml.Decoder`,
|
||||
`Decoder` tries to find the anchor definition from YAML files the under `testdata` directory.
|
||||
|
||||
```go
|
||||
buf := bytes.NewBufferString("a: *a\n")
|
||||
dec := yaml.NewDecoder(buf, yaml.ReferenceDirs("testdata"))
|
||||
var v struct {
|
||||
A struct {
|
||||
B int
|
||||
C string
|
||||
}
|
||||
}
|
||||
if err := dec.Decode(&v); err != nil {
|
||||
//...
|
||||
}
|
||||
fmt.Printf("%+v\n", v) // {A:{B:1 C:hello}}
|
||||
```
|
||||
|
||||
## 3. Encode with `Anchor` and `Alias`
|
||||
|
||||
### 3.1. Explicitly declared `Anchor` name and `Alias` name
|
||||
|
||||
If you want to use `anchor`, you can define it as a struct tag.
|
||||
If the value specified for an anchor is a pointer type and the same address as the pointer is found, the value is automatically set to alias.
|
||||
If an explicit alias name is specified, an error is raised if its value is different from the value specified in the anchor.
|
||||
|
||||
```go
|
||||
type T struct {
|
||||
A int
|
||||
B string
|
||||
}
|
||||
var v struct {
|
||||
C *T `yaml:"c,anchor=x"`
|
||||
D *T `yaml:"d,alias=x"`
|
||||
}
|
||||
v.C = &T{A: 1, B: "hello"}
|
||||
v.D = v.C
|
||||
bytes, err := yaml.Marshal(v)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println(string(bytes))
|
||||
/*
|
||||
c: &x
|
||||
a: 1
|
||||
b: hello
|
||||
d: *x
|
||||
*/
|
||||
```
|
||||
|
||||
### 3.2. Implicitly declared `Anchor` and `Alias` names
|
||||
|
||||
If you do not explicitly declare the anchor name, the default behavior is to
|
||||
use the equivalent of `strings.ToLower($FieldName)` as the name of the anchor.
|
||||
If the value specified for an anchor is a pointer type and the same address as the pointer is found, the value is automatically set to alias.
|
||||
|
||||
```go
|
||||
type T struct {
|
||||
I int
|
||||
S string
|
||||
}
|
||||
var v struct {
|
||||
A *T `yaml:"a,anchor"`
|
||||
B *T `yaml:"b,anchor"`
|
||||
C *T `yaml:"c"`
|
||||
D *T `yaml:"d"`
|
||||
}
|
||||
v.A = &T{I: 1, S: "hello"}
|
||||
v.B = &T{I: 2, S: "world"}
|
||||
v.C = v.A // C has same pointer address to A
|
||||
v.D = v.B // D has same pointer address to B
|
||||
bytes, err := yaml.Marshal(v)
|
||||
if err != nil {
|
||||
//...
|
||||
}
|
||||
fmt.Println(string(bytes))
|
||||
/*
|
||||
a: &a
|
||||
i: 1
|
||||
s: hello
|
||||
b: &b
|
||||
i: 2
|
||||
s: world
|
||||
c: *a
|
||||
d: *b
|
||||
*/
|
||||
```
|
||||
|
||||
### 3.3 MergeKey and Alias
|
||||
|
||||
Merge key and alias ( `<<: *alias` ) can be used by embedding a structure with the `inline,alias` tag.
|
||||
|
||||
```go
|
||||
type Person struct {
|
||||
*Person `yaml:",omitempty,inline,alias"` // embed Person type for default value
|
||||
Name string `yaml:",omitempty"`
|
||||
Age int `yaml:",omitempty"`
|
||||
}
|
||||
defaultPerson := &Person{
|
||||
Name: "John Smith",
|
||||
Age: 20,
|
||||
}
|
||||
people := []*Person{
|
||||
{
|
||||
Person: defaultPerson, // assign default value
|
||||
Name: "Ken", // override Name property
|
||||
Age: 10, // override Age property
|
||||
},
|
||||
{
|
||||
Person: defaultPerson, // assign default value only
|
||||
},
|
||||
}
|
||||
var doc struct {
|
||||
Default *Person `yaml:"default,anchor"`
|
||||
People []*Person `yaml:"people"`
|
||||
}
|
||||
doc.Default = defaultPerson
|
||||
doc.People = people
|
||||
bytes, err := yaml.Marshal(doc)
|
||||
if err != nil {
|
||||
//...
|
||||
}
|
||||
fmt.Println(string(bytes))
|
||||
/*
|
||||
default: &default
|
||||
name: John Smith
|
||||
age: 20
|
||||
people:
|
||||
- <<: *default
|
||||
name: Ken
|
||||
age: 10
|
||||
- <<: *default
|
||||
*/
|
||||
```
|
||||
|
||||
## 4. Pretty Formatted Errors
|
||||
|
||||
Error values produced during parsing have two extra features over regular
|
||||
error values.
|
||||
|
||||
First, by default, they contain extra information on the location of the error
|
||||
from the source YAML document, to make it easier to find the error location.
|
||||
|
||||
Second, the error messages can optionally be colorized.
|
||||
|
||||
If you would like to control exactly how the output looks like, consider
|
||||
using `yaml.FormatError`, which accepts two boolean values to
|
||||
control turning these features on or off.
|
||||
|
||||
<img src="https://user-images.githubusercontent.com/209884/67358124-587f0980-f59a-11e9-96fc-7205aab77695.png"></img>
|
||||
|
||||
## 5. Use YAMLPath
|
||||
|
||||
```go
|
||||
yml := `
|
||||
store:
|
||||
book:
|
||||
- author: john
|
||||
price: 10
|
||||
- author: ken
|
||||
price: 12
|
||||
bicycle:
|
||||
color: red
|
||||
price: 19.95
|
||||
`
|
||||
path, err := yaml.PathString("$.store.book[*].author")
|
||||
if err != nil {
|
||||
//...
|
||||
}
|
||||
var authors []string
|
||||
if err := path.Read(strings.NewReader(yml), &authors); err != nil {
|
||||
//...
|
||||
}
|
||||
fmt.Println(authors)
|
||||
// [john ken]
|
||||
```
|
||||
|
||||
### 5.1 Print customized error with YAML source code
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/goccy/go-yaml"
|
||||
)
|
||||
|
||||
func main() {
|
||||
yml := `
|
||||
a: 1
|
||||
b: "hello"
|
||||
`
|
||||
var v struct {
|
||||
A int
|
||||
B string
|
||||
}
|
||||
if err := yaml.Unmarshal([]byte(yml), &v); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if v.A != 2 {
|
||||
// output error with YAML source
|
||||
path, err := yaml.PathString("$.a")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
source, err := path.AnnotateSource([]byte(yml), true)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Printf("a value expected 2 but actual %d:\n%s\n", v.A, string(source))
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
output result is the following:
|
||||
|
||||
<img src="https://user-images.githubusercontent.com/209884/84148813-7aca8680-aa9a-11ea-8fc9-37dece2ebdac.png"></img>
|
||||
|
||||
|
||||
# Tools
|
||||
|
||||
## ycat
|
||||
|
||||
print yaml file with color
|
||||
|
||||
<img width="713" alt="ycat" src="https://user-images.githubusercontent.com/209884/66986084-19b00600-f0f9-11e9-9f0e-1f91eb072fe0.png">
|
||||
|
||||
### Installation
|
||||
|
||||
```sh
|
||||
git clone https://github.com/goccy/go-yaml.git
|
||||
cd go-yaml/cmd/ycat && go install .
|
||||
```
|
||||
|
||||
|
||||
# For Developers
|
||||
|
||||
> [!NOTE]
|
||||
> In this project, we manage such test code under the `testdata` directory to avoid adding dependencies on libraries that are only needed for testing to the top `go.mod` file. Therefore, if you want to add test cases that use 3rd party libraries, please add the test code to the `testdata` directory.
|
||||
|
||||
# Looking for Sponsors
|
||||
|
||||
I'm looking for sponsors this library. This library is being developed as a personal project in my spare time. If you want a quick response or problem resolution when using this library in your project, please register as a [sponsor](https://github.com/sponsors/goccy). I will cooperate as much as possible. Of course, this library is developed as an MIT license, so you can use it freely for free.
|
||||
|
||||
# License
|
||||
|
||||
MIT
|
||||
+2381
File diff suppressed because it is too large
Load Diff
+37
@@ -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
|
||||
}
|
||||
+2037
File diff suppressed because it is too large
Load Diff
+1074
File diff suppressed because it is too large
Load Diff
+77
@@ -0,0 +1,77 @@
|
||||
package yaml
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/goccy/go-yaml/ast"
|
||||
"github.com/goccy/go-yaml/internal/errors"
|
||||
)
|
||||
|
||||
var (
|
||||
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 fmt.Errorf("unsupported comment head position for %s", node.Type())
|
||||
}
|
||||
|
||||
func ErrUnsupportedLinePositionType(node ast.Node) error {
|
||||
return fmt.Errorf("unsupported comment line position for %s", node.Type())
|
||||
}
|
||||
|
||||
func ErrUnsupportedFootPositionType(node ast.Node) error {
|
||||
return fmt.Errorf("unsupported comment foot position for %s", node.Type())
|
||||
}
|
||||
|
||||
// IsInvalidQueryError whether err is ErrInvalidQuery or not.
|
||||
func IsInvalidQueryError(err error) bool {
|
||||
return errors.Is(err, ErrInvalidQuery)
|
||||
}
|
||||
|
||||
// IsInvalidPathError whether err is ErrInvalidPath or not.
|
||||
func IsInvalidPathError(err error) bool {
|
||||
return errors.Is(err, ErrInvalidPath)
|
||||
}
|
||||
|
||||
// IsInvalidPathStringError whether err is ErrInvalidPathString or not.
|
||||
func IsInvalidPathStringError(err error) bool {
|
||||
return errors.Is(err, ErrInvalidPathString)
|
||||
}
|
||||
|
||||
// IsNotFoundNodeError whether err is ErrNotFoundNode or not.
|
||||
func IsNotFoundNodeError(err error) bool {
|
||||
return errors.Is(err, ErrNotFoundNode)
|
||||
}
|
||||
|
||||
// IsInvalidTokenTypeError whether err is ast.ErrInvalidTokenType or not.
|
||||
func IsInvalidTokenTypeError(err error) bool {
|
||||
return errors.Is(err, ast.ErrInvalidTokenType)
|
||||
}
|
||||
|
||||
// IsInvalidAnchorNameError whether err is ast.ErrInvalidAnchorName or not.
|
||||
func IsInvalidAnchorNameError(err error) bool {
|
||||
return errors.Is(err, ast.ErrInvalidAnchorName)
|
||||
}
|
||||
|
||||
// IsInvalidAliasNameError whether err is ast.ErrInvalidAliasName or not.
|
||||
func IsInvalidAliasNameError(err error) bool {
|
||||
return errors.Is(err, ast.ErrInvalidAliasName)
|
||||
}
|
||||
+246
@@ -0,0 +1,246 @@
|
||||
package errors
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/goccy/go-yaml/ast"
|
||||
"github.com/goccy/go-yaml/printer"
|
||||
"github.com/goccy/go-yaml/token"
|
||||
)
|
||||
|
||||
var (
|
||||
As = errors.As
|
||||
Is = errors.Is
|
||||
New = errors.New
|
||||
)
|
||||
|
||||
const (
|
||||
defaultFormatColor = false
|
||||
defaultIncludeSource = true
|
||||
)
|
||||
|
||||
type Error interface {
|
||||
error
|
||||
GetToken() *token.Token
|
||||
GetMessage() string
|
||||
FormatError(bool, bool) string
|
||||
}
|
||||
|
||||
var (
|
||||
_ Error = new(SyntaxError)
|
||||
_ Error = new(TypeError)
|
||||
_ Error = new(OverflowError)
|
||||
_ Error = new(DuplicateKeyError)
|
||||
_ Error = new(UnknownFieldError)
|
||||
_ Error = new(UnexpectedNodeTypeError)
|
||||
)
|
||||
|
||||
type SyntaxError struct {
|
||||
Message string
|
||||
Token *token.Token
|
||||
}
|
||||
|
||||
type TypeError struct {
|
||||
DstType reflect.Type
|
||||
SrcType reflect.Type
|
||||
StructFieldName *string
|
||||
Token *token.Token
|
||||
}
|
||||
|
||||
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) GetMessage() string {
|
||||
return e.msg()
|
||||
}
|
||||
|
||||
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
|
||||
if token == nil {
|
||||
return pp.PrintErrorMessage(errMsg, 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(token, colored)
|
||||
}
|
||||
return msg
|
||||
}
|
||||
+541
@@ -0,0 +1,541 @@
|
||||
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)
|
||||
} else {
|
||||
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)
|
||||
ret += f.formatCommentGroup(n.Comment)
|
||||
}
|
||||
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)
|
||||
} else {
|
||||
// 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.Comment)
|
||||
}
|
||||
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")
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
package lexer
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/goccy/go-yaml/scanner"
|
||||
"github.com/goccy/go-yaml/token"
|
||||
)
|
||||
|
||||
// Tokenize split to token instances from string
|
||||
func Tokenize(src string) token.Tokens {
|
||||
var s scanner.Scanner
|
||||
s.Init(src)
|
||||
var tokens token.Tokens
|
||||
for {
|
||||
subTokens, err := s.Scan()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
tokens.Add(subTokens...)
|
||||
}
|
||||
return tokens
|
||||
}
|
||||
+352
@@ -0,0 +1,352 @@
|
||||
package yaml
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"reflect"
|
||||
|
||||
"github.com/goccy/go-yaml/ast"
|
||||
)
|
||||
|
||||
// DecodeOption functional option type for Decoder
|
||||
type DecodeOption func(d *Decoder) error
|
||||
|
||||
// ReferenceReaders pass to Decoder that reference to anchor defined by passed readers
|
||||
func ReferenceReaders(readers ...io.Reader) DecodeOption {
|
||||
return func(d *Decoder) error {
|
||||
d.referenceReaders = append(d.referenceReaders, readers...)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// ReferenceFiles pass to Decoder that reference to anchor defined by passed files
|
||||
func ReferenceFiles(files ...string) DecodeOption {
|
||||
return func(d *Decoder) error {
|
||||
d.referenceFiles = files
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// ReferenceDirs pass to Decoder that reference to anchor defined by files under the passed dirs
|
||||
func ReferenceDirs(dirs ...string) DecodeOption {
|
||||
return func(d *Decoder) error {
|
||||
d.referenceDirs = dirs
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// RecursiveDir search yaml file recursively from passed dirs by ReferenceDirs option
|
||||
func RecursiveDir(isRecursive bool) DecodeOption {
|
||||
return func(d *Decoder) error {
|
||||
d.isRecursiveDir = isRecursive
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Validator set StructValidator instance to Decoder
|
||||
func Validator(v StructValidator) DecodeOption {
|
||||
return func(d *Decoder) error {
|
||||
d.validator = v
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Strict enable DisallowUnknownField
|
||||
func Strict() DecodeOption {
|
||||
return func(d *Decoder) error {
|
||||
d.disallowUnknownField = true
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// DisallowUnknownField causes the Decoder to return an error when the destination
|
||||
// is a struct and the input contains object keys which do not match any
|
||||
// non-ignored, exported fields in the destination.
|
||||
func DisallowUnknownField() DecodeOption {
|
||||
return func(d *Decoder) error {
|
||||
d.disallowUnknownField = true
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// AllowFieldPrefixes, when paired with [DisallowUnknownField], allows fields
|
||||
// with the specified prefixes to bypass the unknown field check.
|
||||
func AllowFieldPrefixes(prefixes ...string) DecodeOption {
|
||||
return func(d *Decoder) error {
|
||||
d.allowedFieldPrefixes = append(d.allowedFieldPrefixes, prefixes...)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// AllowDuplicateMapKey ignore syntax error when mapping keys that are duplicates.
|
||||
func AllowDuplicateMapKey() DecodeOption {
|
||||
return func(d *Decoder) error {
|
||||
d.allowDuplicateMapKey = true
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// UseOrderedMap can be interpreted as a map,
|
||||
// and uses MapSlice ( ordered map ) aggressively if there is no type specification
|
||||
func UseOrderedMap() DecodeOption {
|
||||
return func(d *Decoder) error {
|
||||
d.useOrderedMap = true
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// UseJSONUnmarshaler if neither `BytesUnmarshaler` nor `InterfaceUnmarshaler` is implemented
|
||||
// and `UnmashalJSON([]byte)error` is implemented, convert the argument from `YAML` to `JSON` and then call it.
|
||||
func UseJSONUnmarshaler() DecodeOption {
|
||||
return func(d *Decoder) error {
|
||||
d.useJSONUnmarshaler = true
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// CustomUnmarshaler overrides any decoding process for the type specified in generics.
|
||||
//
|
||||
// NOTE: If RegisterCustomUnmarshaler and CustomUnmarshaler of DecodeOption are specified for the same type,
|
||||
// the CustomUnmarshaler specified in DecodeOption takes precedence.
|
||||
func CustomUnmarshaler[T any](unmarshaler func(*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(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.indentNum = spaces
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// IndentSequence causes sequence values to be indented the same value as Indent
|
||||
func IndentSequence(indent bool) EncodeOption {
|
||||
return func(e *Encoder) error {
|
||||
e.indentSequence = indent
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// UseSingleQuote determines if single or double quotes should be preferred for strings.
|
||||
func UseSingleQuote(sq bool) EncodeOption {
|
||||
return func(e *Encoder) error {
|
||||
e.singleQuote = sq
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Flow encoding by flow style
|
||||
func Flow(isFlowStyle bool) EncodeOption {
|
||||
return func(e *Encoder) error {
|
||||
e.isFlowStyle = isFlowStyle
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return func(e *Encoder) error {
|
||||
e.useLiteralStyleIfMultiline = useLiteralStyleIfMultiline
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// JSON encode in JSON format
|
||||
func JSON() EncodeOption {
|
||||
return func(e *Encoder) error {
|
||||
e.isJSONStyle = true
|
||||
e.isFlowStyle = true
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// MarshalAnchor call back if encoder find an anchor during encoding
|
||||
func MarshalAnchor(callback func(*ast.AnchorNode, interface{}) error) EncodeOption {
|
||||
return func(e *Encoder) error {
|
||||
e.anchorCallback = callback
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// UseJSONMarshaler if neither `BytesMarshaler` nor `InterfaceMarshaler`
|
||||
// nor `encoding.TextMarshaler` is implemented and `MarshalJSON()([]byte, error)` is implemented,
|
||||
// call `MarshalJSON` to convert the returned `JSON` to `YAML` for processing.
|
||||
func UseJSONMarshaler() EncodeOption {
|
||||
return func(e *Encoder) error {
|
||||
e.useJSONMarshaler = true
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// CustomMarshaler overrides any encoding process for the type specified in generics.
|
||||
//
|
||||
// NOTE: If type T implements MarshalYAML for pointer receiver, the type specified in CustomMarshaler must be *T.
|
||||
// If RegisterCustomMarshaler and CustomMarshaler of EncodeOption are specified for the same type,
|
||||
// the CustomMarshaler specified in EncodeOption takes precedence.
|
||||
func CustomMarshaler[T any](marshaler func(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(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
|
||||
|
||||
const (
|
||||
CommentHeadPosition CommentPosition = CommentPosition(iota)
|
||||
CommentLinePosition
|
||||
CommentFootPosition
|
||||
)
|
||||
|
||||
func (p CommentPosition) String() string {
|
||||
switch p {
|
||||
case CommentHeadPosition:
|
||||
return "Head"
|
||||
case CommentLinePosition:
|
||||
return "Line"
|
||||
case CommentFootPosition:
|
||||
return "Foot"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// LineComment create a one-line comment for CommentMap.
|
||||
func LineComment(text string) *Comment {
|
||||
return &Comment{
|
||||
Texts: []string{text},
|
||||
Position: CommentLinePosition,
|
||||
}
|
||||
}
|
||||
|
||||
// HeadComment create a multiline comment for CommentMap.
|
||||
func HeadComment(texts ...string) *Comment {
|
||||
return &Comment{
|
||||
Texts: texts,
|
||||
Position: CommentHeadPosition,
|
||||
}
|
||||
}
|
||||
|
||||
// FootComment create a multiline comment for CommentMap.
|
||||
func FootComment(texts ...string) *Comment {
|
||||
return &Comment{
|
||||
Texts: texts,
|
||||
Position: CommentFootPosition,
|
||||
}
|
||||
}
|
||||
|
||||
// Comment raw data for comment.
|
||||
type Comment struct {
|
||||
Texts []string
|
||||
Position CommentPosition
|
||||
}
|
||||
|
||||
// CommentMap map of the position of the comment and the comment information.
|
||||
type CommentMap map[string][]*Comment
|
||||
|
||||
// WithComment add a comment using the location and text information given in the CommentMap.
|
||||
func WithComment(cm CommentMap) EncodeOption {
|
||||
return func(e *Encoder) error {
|
||||
commentMap := map[*Path][]*Comment{}
|
||||
for k, v := range cm {
|
||||
path, err := PathString(k)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
commentMap[path] = v
|
||||
}
|
||||
e.commentMap = commentMap
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// CommentToMap apply the position and content of comments in a YAML document to a CommentMap.
|
||||
func CommentToMap(cm CommentMap) DecodeOption {
|
||||
return func(d *Decoder) error {
|
||||
if cm == nil {
|
||||
return ErrInvalidCommentMapValue
|
||||
}
|
||||
d.toCommentMap = cm
|
||||
return nil
|
||||
}
|
||||
}
|
||||
+28
@@ -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"
|
||||
}
|
||||
+187
@@ -0,0 +1,187 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/goccy/go-yaml/token"
|
||||
)
|
||||
|
||||
// context context at parsing
|
||||
type context struct {
|
||||
tokenRef *tokenRef
|
||||
path string
|
||||
isFlow bool
|
||||
}
|
||||
|
||||
type tokenRef struct {
|
||||
tokens []*Token
|
||||
size int
|
||||
idx int
|
||||
}
|
||||
|
||||
var pathSpecialChars = []string{
|
||||
"$", "*", ".", "[", "]",
|
||||
}
|
||||
|
||||
func containsPathSpecialChar(path string) bool {
|
||||
for _, char := range pathSpecialChars {
|
||||
if strings.Contains(path, char) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func normalizePath(path string) string {
|
||||
if containsPathSpecialChar(path) {
|
||||
return fmt.Sprintf("'%s'", path)
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
func (c *context) currentToken() *Token {
|
||||
if c.tokenRef.idx >= c.tokenRef.size {
|
||||
return nil
|
||||
}
|
||||
return c.tokenRef.tokens[c.tokenRef.idx]
|
||||
}
|
||||
|
||||
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.tokenRef.tokens[c.tokenRef.idx+1]
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *context) isTokenNotFound() bool {
|
||||
return c.currentToken() == nil
|
||||
}
|
||||
|
||||
func (c *context) withGroup(g *TokenGroup) *context {
|
||||
ctx := *c
|
||||
ctx.tokenRef = &tokenRef{
|
||||
tokens: g.Tokens,
|
||||
size: len(g.Tokens),
|
||||
}
|
||||
return &ctx
|
||||
}
|
||||
|
||||
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: "$",
|
||||
}
|
||||
}
|
||||
|
||||
func (c *context) goNext() {
|
||||
ref := c.tokenRef
|
||||
if ref.size <= ref.idx+1 {
|
||||
ref.idx = ref.size
|
||||
} else {
|
||||
ref.idx++
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
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 (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
@@ -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
@@ -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
|
||||
}
|
||||
}
|
||||
+1330
File diff suppressed because it is too large
Load Diff
+746
@@ -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
|
||||
}
|
||||
+835
@@ -0,0 +1,835 @@
|
||||
package yaml
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/goccy/go-yaml/ast"
|
||||
"github.com/goccy/go-yaml/parser"
|
||||
"github.com/goccy/go-yaml/printer"
|
||||
)
|
||||
|
||||
// PathString create Path from string
|
||||
//
|
||||
// YAMLPath rule
|
||||
// $ : the root object/element
|
||||
// . : child operator
|
||||
// .. : recursive descent
|
||||
// [num] : object/element of array by number
|
||||
// [*] : all objects/elements for array.
|
||||
//
|
||||
// If you want to use reserved characters such as `.` and `*` as a key name,
|
||||
// enclose them in single quotation as follows ( $.foo.'bar.baz-*'.hoge ).
|
||||
// If you want to use a single quote with reserved characters, escape it with `\` ( $.foo.'bar.baz\'s value'.hoge ).
|
||||
func PathString(s string) (*Path, error) {
|
||||
buf := []rune(s)
|
||||
length := len(buf)
|
||||
cursor := 0
|
||||
builder := &PathBuilder{}
|
||||
for cursor < length {
|
||||
c := buf[cursor]
|
||||
switch c {
|
||||
case '$':
|
||||
builder = builder.Root()
|
||||
cursor++
|
||||
case '.':
|
||||
b, buf, c, err := parsePathDot(builder, buf, cursor)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
length = len(buf)
|
||||
builder = b
|
||||
cursor = c
|
||||
case '[':
|
||||
b, buf, c, err := parsePathIndex(builder, buf, cursor)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
length = len(buf)
|
||||
builder = b
|
||||
cursor = c
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid path at %d: %w", cursor, ErrInvalidPathString)
|
||||
}
|
||||
}
|
||||
return builder.Build(), nil
|
||||
}
|
||||
|
||||
func parsePathRecursive(b *PathBuilder, buf []rune, cursor int) (*PathBuilder, []rune, int, error) {
|
||||
length := len(buf)
|
||||
cursor += 2 // skip .. characters
|
||||
start := cursor
|
||||
for ; cursor < length; cursor++ {
|
||||
c := buf[cursor]
|
||||
switch c {
|
||||
case '$':
|
||||
return nil, nil, 0, fmt.Errorf("specified '$' after '..' character: %w", ErrInvalidPathString)
|
||||
case '*':
|
||||
return nil, nil, 0, fmt.Errorf("specified '*' after '..' character: %w", ErrInvalidPathString)
|
||||
case '.', '[':
|
||||
goto end
|
||||
case ']':
|
||||
return nil, nil, 0, fmt.Errorf("specified ']' after '..' character: %w", ErrInvalidPathString)
|
||||
}
|
||||
}
|
||||
end:
|
||||
if start == cursor {
|
||||
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, err
|
||||
}
|
||||
return b, buf, c, nil
|
||||
}
|
||||
cursor++ // skip . character
|
||||
start := cursor
|
||||
|
||||
// if started single quote, looking for end single quote char
|
||||
if cursor < length && buf[cursor] == '\'' {
|
||||
return parseQuotedKey(b, buf, cursor)
|
||||
}
|
||||
for ; cursor < length; cursor++ {
|
||||
c := buf[cursor]
|
||||
switch c {
|
||||
case '$':
|
||||
return nil, nil, 0, fmt.Errorf("specified '$' after '.' character: %w", ErrInvalidPathString)
|
||||
case '*':
|
||||
return nil, nil, 0, fmt.Errorf("specified '*' after '.' character: %w", ErrInvalidPathString)
|
||||
case '.', '[':
|
||||
goto end
|
||||
case ']':
|
||||
return nil, nil, 0, fmt.Errorf("specified ']' after '.' character: %w", ErrInvalidPathString)
|
||||
}
|
||||
}
|
||||
end:
|
||||
if start == cursor {
|
||||
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)
|
||||
var foundEndDelim bool
|
||||
for ; cursor < length; cursor++ {
|
||||
switch buf[cursor] {
|
||||
case '\\':
|
||||
buf = append(append([]rune{}, buf[:cursor]...), buf[cursor+1:]...)
|
||||
length = len(buf)
|
||||
case '\'':
|
||||
foundEndDelim = true
|
||||
goto end
|
||||
}
|
||||
}
|
||||
end:
|
||||
if !foundEndDelim {
|
||||
return nil, nil, 0, fmt.Errorf("could not find end delimiter for key: %w", ErrInvalidPathString)
|
||||
}
|
||||
if start == cursor {
|
||||
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, fmt.Errorf("specified '$' after '.' character: %w", ErrInvalidPathString)
|
||||
case '*':
|
||||
return nil, nil, 0, fmt.Errorf("specified '*' after '.' character: %w", ErrInvalidPathString)
|
||||
case ']':
|
||||
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, fmt.Errorf("unexpected end of YAML Path: %w", ErrInvalidPathString)
|
||||
}
|
||||
c := buf[cursor]
|
||||
switch c {
|
||||
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '*':
|
||||
start := cursor
|
||||
cursor++
|
||||
for ; cursor < length; cursor++ {
|
||||
c := buf[cursor]
|
||||
switch c {
|
||||
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
if buf[cursor] != ']' {
|
||||
return nil, nil, 0, fmt.Errorf("invalid character %s at %d: %w", string(buf[cursor]), cursor, ErrInvalidPathString)
|
||||
}
|
||||
numOrAll := string(buf[start:cursor])
|
||||
if numOrAll == "*" {
|
||||
return b.IndexAll(), buf, cursor + 1, nil
|
||||
}
|
||||
num, err := strconv.ParseInt(numOrAll, 10, 64)
|
||||
if err != nil {
|
||||
return nil, nil, 0, err
|
||||
}
|
||||
return b.Index(uint(num)), buf, cursor + 1, nil
|
||||
}
|
||||
return nil, nil, 0, fmt.Errorf("invalid character %q at %d: %w", c, cursor, ErrInvalidPathString)
|
||||
}
|
||||
|
||||
// Path represent YAMLPath ( like a JSONPath ).
|
||||
type Path struct {
|
||||
node pathNode
|
||||
}
|
||||
|
||||
// String path to text.
|
||||
func (p *Path) String() string {
|
||||
return p.node.String()
|
||||
}
|
||||
|
||||
// Read decode from r and set extracted value by YAMLPath to v.
|
||||
func (p *Path) Read(r io.Reader, v interface{}) error {
|
||||
node, err := p.ReadNode(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := Unmarshal([]byte(node.String()), v); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReadNode create AST from r and extract node by YAMLPath.
|
||||
func (p *Path) ReadNode(r io.Reader) (ast.Node, error) {
|
||||
if p.node == nil {
|
||||
return nil, ErrInvalidPath
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
if _, err := io.Copy(&buf, r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f, err := parser.ParseBytes(buf.Bytes(), 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
node, err := p.FilterFile(f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return node, nil
|
||||
}
|
||||
|
||||
// Filter filter from target by YAMLPath and set it to v.
|
||||
func (p *Path) Filter(target, v interface{}) error {
|
||||
b, err := Marshal(target)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := p.Read(bytes.NewBuffer(b), v); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// FilterFile filter from ast.File by YAMLPath.
|
||||
func (p *Path) FilterFile(f *ast.File) (ast.Node, error) {
|
||||
for _, doc := range f.Docs {
|
||||
// For simplicity, directives cannot be the target of operations
|
||||
if doc.Body != nil && doc.Body.Type() == ast.DirectiveType {
|
||||
continue
|
||||
}
|
||||
node, err := p.FilterNode(doc.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if node != nil {
|
||||
return node, nil
|
||||
}
|
||||
}
|
||||
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, err
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// MergeFromReader merge YAML text into ast.File.
|
||||
func (p *Path) MergeFromReader(dst *ast.File, src io.Reader) error {
|
||||
var buf bytes.Buffer
|
||||
if _, err := io.Copy(&buf, src); err != nil {
|
||||
return err
|
||||
}
|
||||
file, err := parser.ParseBytes(buf.Bytes(), 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := p.MergeFromFile(dst, file); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MergeFromFile merge ast.File into ast.File.
|
||||
func (p *Path) MergeFromFile(dst *ast.File, src *ast.File) error {
|
||||
base, err := p.FilterFile(dst)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, doc := range src.Docs {
|
||||
if err := ast.Merge(base, doc); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MergeFromNode merge ast.Node into ast.File.
|
||||
func (p *Path) MergeFromNode(dst *ast.File, src ast.Node) error {
|
||||
base, err := p.FilterFile(dst)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ast.Merge(base, src); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReplaceWithReader replace ast.File with io.Reader.
|
||||
func (p *Path) ReplaceWithReader(dst *ast.File, src io.Reader) error {
|
||||
var buf bytes.Buffer
|
||||
if _, err := io.Copy(&buf, src); err != nil {
|
||||
return err
|
||||
}
|
||||
file, err := parser.ParseBytes(buf.Bytes(), 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := p.ReplaceWithFile(dst, file); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReplaceWithFile replace ast.File with ast.File.
|
||||
func (p *Path) ReplaceWithFile(dst *ast.File, src *ast.File) error {
|
||||
for _, doc := range src.Docs {
|
||||
if err := p.ReplaceWithNode(dst, doc); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReplaceNode replace ast.File with ast.Node.
|
||||
func (p *Path) ReplaceWithNode(dst *ast.File, node ast.Node) error {
|
||||
for _, doc := range dst.Docs {
|
||||
// For simplicity, directives cannot be the target of operations
|
||||
if doc.Body != nil && doc.Body.Type() == ast.DirectiveType {
|
||||
continue
|
||||
}
|
||||
if node.Type() == ast.DocumentType {
|
||||
node = node.(*ast.DocumentNode).Body
|
||||
}
|
||||
if err := p.node.replace(doc.Body, node); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AnnotateSource add annotation to passed source ( see section 5.1 in README.md ).
|
||||
func (p *Path) AnnotateSource(source []byte, colored bool) ([]byte, error) {
|
||||
file, err := parser.ParseBytes(source, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
node, err := p.FilterFile(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var pp printer.Printer
|
||||
return []byte(pp.PrintErrorToken(node.GetToken(), colored)), nil
|
||||
}
|
||||
|
||||
// PathBuilder represent builder for YAMLPath.
|
||||
type PathBuilder struct {
|
||||
root *rootNode
|
||||
node pathNode
|
||||
}
|
||||
|
||||
// Root add '$' to current path.
|
||||
func (b *PathBuilder) Root() *PathBuilder {
|
||||
root := newRootNode()
|
||||
return &PathBuilder{root: root, node: root}
|
||||
}
|
||||
|
||||
// IndexAll add '[*]' to current path.
|
||||
func (b *PathBuilder) IndexAll() *PathBuilder {
|
||||
b.node = b.node.chain(newIndexAllNode())
|
||||
return b
|
||||
}
|
||||
|
||||
// Recursive add '..selector' to current path.
|
||||
func (b *PathBuilder) Recursive(selector string) *PathBuilder {
|
||||
b.node = b.node.chain(newRecursiveNode(selector))
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *PathBuilder) containsReservedPathCharacters(path string) bool {
|
||||
if strings.Contains(path, ".") {
|
||||
return true
|
||||
}
|
||||
if strings.Contains(path, "*") {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (b *PathBuilder) enclosedSingleQuote(name string) bool {
|
||||
return strings.HasPrefix(name, "'") && strings.HasSuffix(name, "'")
|
||||
}
|
||||
|
||||
func (b *PathBuilder) normalizeSelectorName(name string) string {
|
||||
if b.enclosedSingleQuote(name) {
|
||||
// already escaped name
|
||||
return name
|
||||
}
|
||||
if b.containsReservedPathCharacters(name) {
|
||||
escapedName := strings.ReplaceAll(name, `'`, `\'`)
|
||||
return "'" + escapedName + "'"
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
func (b *PathBuilder) child(name string) *PathBuilder {
|
||||
b.node = b.node.chain(newSelectorNode(name))
|
||||
return b
|
||||
}
|
||||
|
||||
// Child add '.name' to current path.
|
||||
func (b *PathBuilder) Child(name string) *PathBuilder {
|
||||
return b.child(b.normalizeSelectorName(name))
|
||||
}
|
||||
|
||||
// Index add '[idx]' to current path.
|
||||
func (b *PathBuilder) Index(idx uint) *PathBuilder {
|
||||
b.node = b.node.chain(newIndexNode(idx))
|
||||
return b
|
||||
}
|
||||
|
||||
// Build build YAMLPath.
|
||||
func (b *PathBuilder) Build() *Path {
|
||||
return &Path{node: b.root}
|
||||
}
|
||||
|
||||
type pathNode interface {
|
||||
fmt.Stringer
|
||||
chain(pathNode) pathNode
|
||||
filter(ast.Node) (ast.Node, error)
|
||||
replace(ast.Node, ast.Node) error
|
||||
}
|
||||
|
||||
type basePathNode struct {
|
||||
child pathNode
|
||||
}
|
||||
|
||||
func (n *basePathNode) chain(node pathNode) pathNode {
|
||||
n.child = node
|
||||
return node
|
||||
}
|
||||
|
||||
type rootNode struct {
|
||||
*basePathNode
|
||||
}
|
||||
|
||||
func newRootNode() *rootNode {
|
||||
return &rootNode{basePathNode: &basePathNode{}}
|
||||
}
|
||||
|
||||
func (n *rootNode) String() string {
|
||||
s := "$"
|
||||
if n.child != nil {
|
||||
s += n.child.String()
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (n *rootNode) filter(node ast.Node) (ast.Node, error) {
|
||||
if n.child == nil {
|
||||
return node, nil
|
||||
}
|
||||
filtered, err := n.child.filter(node)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return filtered, nil
|
||||
}
|
||||
|
||||
func (n *rootNode) replace(node ast.Node, target ast.Node) error {
|
||||
if n.child == nil {
|
||||
return nil
|
||||
}
|
||||
if err := n.child.replace(node, target); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type selectorNode struct {
|
||||
*basePathNode
|
||||
selector string
|
||||
}
|
||||
|
||||
func newSelectorNode(selector string) *selectorNode {
|
||||
return &selectorNode{
|
||||
basePathNode: &basePathNode{},
|
||||
selector: selector,
|
||||
}
|
||||
}
|
||||
|
||||
func (n *selectorNode) filter(node ast.Node) (ast.Node, error) {
|
||||
selector := n.selector
|
||||
if len(selector) > 1 && selector[0] == '\'' && selector[len(selector)-1] == '\'' {
|
||||
selector = selector[1 : len(selector)-1]
|
||||
}
|
||||
switch node.Type() {
|
||||
case ast.MappingType:
|
||||
for _, value := range node.(*ast.MappingNode).Values {
|
||||
key := value.Key.GetToken().Value
|
||||
if len(key) > 0 {
|
||||
switch key[0] {
|
||||
case '"':
|
||||
var err error
|
||||
key, err = strconv.Unquote(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case '\'':
|
||||
if len(key) > 1 && key[len(key)-1] == '\'' {
|
||||
key = key[1 : len(key)-1]
|
||||
}
|
||||
}
|
||||
}
|
||||
if key == selector {
|
||||
if n.child == nil {
|
||||
return value.Value, nil
|
||||
}
|
||||
filtered, err := n.child.filter(value.Value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return filtered, nil
|
||||
}
|
||||
}
|
||||
case ast.MappingValueType:
|
||||
value, _ := node.(*ast.MappingValueNode)
|
||||
key := value.Key.GetToken().Value
|
||||
if key == selector {
|
||||
if n.child == nil {
|
||||
return value.Value, nil
|
||||
}
|
||||
filtered, err := n.child.filter(value.Value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return filtered, nil
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("expected node type is map or map value. but got %s: %w", node.Type(), ErrInvalidQuery)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (n *selectorNode) replaceMapValue(value *ast.MappingValueNode, target ast.Node) error {
|
||||
key := value.Key.GetToken().Value
|
||||
if key != n.selector {
|
||||
return nil
|
||||
}
|
||||
if n.child == nil {
|
||||
if err := value.Replace(target); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := n.child.replace(value.Value, target); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *selectorNode) replace(node ast.Node, target ast.Node) error {
|
||||
switch node.Type() {
|
||||
case ast.MappingType:
|
||||
for _, value := range node.(*ast.MappingNode).Values {
|
||||
if err := n.replaceMapValue(value, target); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
case ast.MappingValueType:
|
||||
value, _ := node.(*ast.MappingValueNode)
|
||||
if err := n.replaceMapValue(value, target); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("expected node type is map or map value. but got %s: %w", node.Type(), ErrInvalidQuery)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *selectorNode) String() string {
|
||||
var builder PathBuilder
|
||||
selector := builder.normalizeSelectorName(n.selector)
|
||||
s := fmt.Sprintf(".%s", selector)
|
||||
if n.child != nil {
|
||||
s += n.child.String()
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
type indexNode struct {
|
||||
*basePathNode
|
||||
selector uint
|
||||
}
|
||||
|
||||
func newIndexNode(selector uint) *indexNode {
|
||||
return &indexNode{
|
||||
basePathNode: &basePathNode{},
|
||||
selector: selector,
|
||||
}
|
||||
}
|
||||
|
||||
func (n *indexNode) filter(node ast.Node) (ast.Node, error) {
|
||||
if node.Type() != ast.SequenceType {
|
||||
return nil, fmt.Errorf("expected sequence type node. but got %s: %w", node.Type(), ErrInvalidQuery)
|
||||
}
|
||||
sequence, _ := node.(*ast.SequenceNode)
|
||||
if n.selector >= uint(len(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 {
|
||||
return value, nil
|
||||
}
|
||||
filtered, err := n.child.filter(value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return filtered, nil
|
||||
}
|
||||
|
||||
func (n *indexNode) replace(node ast.Node, target ast.Node) error {
|
||||
if node.Type() != ast.SequenceType {
|
||||
return fmt.Errorf("expected sequence type node. but got %s: %w", node.Type(), ErrInvalidQuery)
|
||||
}
|
||||
sequence, _ := node.(*ast.SequenceNode)
|
||||
if n.selector >= uint(len(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 err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if err := n.child.replace(sequence.Values[n.selector], target); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *indexNode) String() string {
|
||||
s := fmt.Sprintf("[%d]", n.selector)
|
||||
if n.child != nil {
|
||||
s += n.child.String()
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
type indexAllNode struct {
|
||||
*basePathNode
|
||||
}
|
||||
|
||||
func newIndexAllNode() *indexAllNode {
|
||||
return &indexAllNode{
|
||||
basePathNode: &basePathNode{},
|
||||
}
|
||||
}
|
||||
|
||||
func (n *indexAllNode) String() string {
|
||||
s := "[*]"
|
||||
if n.child != nil {
|
||||
s += n.child.String()
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (n *indexAllNode) filter(node ast.Node) (ast.Node, error) {
|
||||
if node.Type() != ast.SequenceType {
|
||||
return nil, fmt.Errorf("expected sequence type node. but got %s: %w", node.Type(), ErrInvalidQuery)
|
||||
}
|
||||
sequence, _ := node.(*ast.SequenceNode)
|
||||
if n.child == nil {
|
||||
return sequence, nil
|
||||
}
|
||||
out := *sequence
|
||||
out.Values = []ast.Node{}
|
||||
for _, value := range sequence.Values {
|
||||
filtered, err := n.child.filter(value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out.Values = append(out.Values, filtered)
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
func (n *indexAllNode) replace(node ast.Node, target ast.Node) error {
|
||||
if node.Type() != ast.SequenceType {
|
||||
return fmt.Errorf("expected sequence type node. but got %s: %w", node.Type(), ErrInvalidQuery)
|
||||
}
|
||||
sequence, _ := node.(*ast.SequenceNode)
|
||||
if n.child == nil {
|
||||
for idx := range sequence.Values {
|
||||
if err := sequence.Replace(idx, target); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
for _, value := range sequence.Values {
|
||||
if err := n.child.replace(value, target); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type recursiveNode struct {
|
||||
*basePathNode
|
||||
selector string
|
||||
}
|
||||
|
||||
func newRecursiveNode(selector string) *recursiveNode {
|
||||
return &recursiveNode{
|
||||
basePathNode: &basePathNode{},
|
||||
selector: selector,
|
||||
}
|
||||
}
|
||||
|
||||
func (n *recursiveNode) String() string {
|
||||
s := fmt.Sprintf("..%s", n.selector)
|
||||
if n.child != nil {
|
||||
s += n.child.String()
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (n *recursiveNode) filterNode(node ast.Node) (*ast.SequenceNode, error) {
|
||||
sequence := &ast.SequenceNode{BaseNode: &ast.BaseNode{}}
|
||||
switch typedNode := node.(type) {
|
||||
case *ast.MappingNode:
|
||||
for _, value := range typedNode.Values {
|
||||
seq, err := n.filterNode(value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sequence.Values = append(sequence.Values, seq.Values...)
|
||||
}
|
||||
case *ast.MappingValueNode:
|
||||
key := typedNode.Key.GetToken().Value
|
||||
if n.selector == key {
|
||||
sequence.Values = append(sequence.Values, typedNode.Value)
|
||||
}
|
||||
seq, err := n.filterNode(typedNode.Value)
|
||||
if err != nil {
|
||||
return nil, 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, err
|
||||
}
|
||||
sequence.Values = append(sequence.Values, seq.Values...)
|
||||
}
|
||||
}
|
||||
return sequence, nil
|
||||
}
|
||||
|
||||
func (n *recursiveNode) filter(node ast.Node) (ast.Node, error) {
|
||||
sequence, err := n.filterNode(node)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sequence.Start = node.GetToken()
|
||||
return sequence, nil
|
||||
}
|
||||
|
||||
func (n *recursiveNode) replaceNode(node ast.Node, target ast.Node) error {
|
||||
switch typedNode := node.(type) {
|
||||
case *ast.MappingNode:
|
||||
for _, value := range typedNode.Values {
|
||||
if err := n.replaceNode(value, target); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
case *ast.MappingValueNode:
|
||||
key := typedNode.Key.GetToken().Value
|
||||
if n.selector == key {
|
||||
if err := typedNode.Replace(target); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := n.replaceNode(typedNode.Value, target); err != nil {
|
||||
return err
|
||||
}
|
||||
case *ast.SequenceNode:
|
||||
for _, value := range typedNode.Values {
|
||||
if err := n.replaceNode(value, target); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *recursiveNode) replace(node ast.Node, target ast.Node) error {
|
||||
if err := n.replaceNode(node, target); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+83
@@ -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...)
|
||||
}
|
||||
+353
@@ -0,0 +1,353 @@
|
||||
package printer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"strings"
|
||||
|
||||
"github.com/goccy/go-yaml/ast"
|
||||
"github.com/goccy/go-yaml/token"
|
||||
)
|
||||
|
||||
// Property additional property set for each the token
|
||||
type Property struct {
|
||||
Prefix string
|
||||
Suffix string
|
||||
}
|
||||
|
||||
// PrintFunc returns property instance
|
||||
type PrintFunc func() *Property
|
||||
|
||||
// Printer create text from token collection or ast
|
||||
type Printer struct {
|
||||
LineNumber bool
|
||||
LineNumberFormat func(num int) string
|
||||
MapKey PrintFunc
|
||||
Anchor PrintFunc
|
||||
Alias PrintFunc
|
||||
Bool PrintFunc
|
||||
String PrintFunc
|
||||
Number PrintFunc
|
||||
Comment PrintFunc
|
||||
}
|
||||
|
||||
func defaultLineNumberFormat(num int) string {
|
||||
return fmt.Sprintf("%2d | ", num)
|
||||
}
|
||||
|
||||
func (p *Printer) property(tk *token.Token) *Property {
|
||||
prop := &Property{}
|
||||
switch tk.PreviousType() {
|
||||
case token.AnchorType:
|
||||
if p.Anchor != nil {
|
||||
return p.Anchor()
|
||||
}
|
||||
return prop
|
||||
case token.AliasType:
|
||||
if p.Alias != nil {
|
||||
return p.Alias()
|
||||
}
|
||||
return prop
|
||||
}
|
||||
switch tk.NextType() {
|
||||
case token.MappingValueType:
|
||||
if p.MapKey != nil {
|
||||
return p.MapKey()
|
||||
}
|
||||
return prop
|
||||
}
|
||||
switch tk.Type {
|
||||
case token.BoolType:
|
||||
if p.Bool != nil {
|
||||
return p.Bool()
|
||||
}
|
||||
return prop
|
||||
case token.AnchorType:
|
||||
if p.Anchor != nil {
|
||||
return p.Anchor()
|
||||
}
|
||||
return prop
|
||||
case token.AliasType:
|
||||
if p.Anchor != nil {
|
||||
return p.Alias()
|
||||
}
|
||||
return prop
|
||||
case token.StringType, token.SingleQuoteType, token.DoubleQuoteType:
|
||||
if p.String != nil {
|
||||
return p.String()
|
||||
}
|
||||
return prop
|
||||
case token.IntegerType, token.FloatType:
|
||||
if p.Number != nil {
|
||||
return p.Number()
|
||||
}
|
||||
return prop
|
||||
case token.CommentType:
|
||||
if p.Comment != nil {
|
||||
return p.Comment()
|
||||
}
|
||||
return prop
|
||||
default:
|
||||
}
|
||||
return prop
|
||||
}
|
||||
|
||||
// PrintTokens create text from token collection
|
||||
func (p *Printer) PrintTokens(tokens token.Tokens) string {
|
||||
if len(tokens) == 0 {
|
||||
return ""
|
||||
}
|
||||
if p.LineNumber {
|
||||
if p.LineNumberFormat == nil {
|
||||
p.LineNumberFormat = defaultLineNumberFormat
|
||||
}
|
||||
}
|
||||
texts := []string{}
|
||||
lineNumber := tokens[0].Position.Line
|
||||
for _, tk := range tokens {
|
||||
lines := strings.Split(tk.Origin, "\n")
|
||||
prop := p.property(tk)
|
||||
header := ""
|
||||
if p.LineNumber {
|
||||
header = p.LineNumberFormat(lineNumber)
|
||||
}
|
||||
if len(lines) == 1 {
|
||||
line := prop.Prefix + lines[0] + prop.Suffix
|
||||
if len(texts) == 0 {
|
||||
texts = append(texts, header+line)
|
||||
lineNumber++
|
||||
} else {
|
||||
text := texts[len(texts)-1]
|
||||
texts[len(texts)-1] = text + line
|
||||
}
|
||||
} else {
|
||||
for idx, src := range lines {
|
||||
if p.LineNumber {
|
||||
header = p.LineNumberFormat(lineNumber)
|
||||
}
|
||||
line := prop.Prefix + src + prop.Suffix
|
||||
if idx == 0 {
|
||||
if len(texts) == 0 {
|
||||
texts = append(texts, header+line)
|
||||
lineNumber++
|
||||
} else {
|
||||
text := texts[len(texts)-1]
|
||||
texts[len(texts)-1] = text + line
|
||||
}
|
||||
} else {
|
||||
texts = append(texts, fmt.Sprintf("%s%s", header, line))
|
||||
lineNumber++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return strings.Join(texts, "\n")
|
||||
}
|
||||
|
||||
// PrintNode create text from ast.Node
|
||||
func (p *Printer) PrintNode(node ast.Node) []byte {
|
||||
return []byte(fmt.Sprintf("%+v\n", node))
|
||||
}
|
||||
|
||||
func (p *Printer) setDefaultColorSet() {
|
||||
p.Bool = func() *Property {
|
||||
return &Property{
|
||||
Prefix: format(ColorFgHiMagenta),
|
||||
Suffix: format(ColorReset),
|
||||
}
|
||||
}
|
||||
p.Number = func() *Property {
|
||||
return &Property{
|
||||
Prefix: format(ColorFgHiMagenta),
|
||||
Suffix: format(ColorReset),
|
||||
}
|
||||
}
|
||||
p.MapKey = func() *Property {
|
||||
return &Property{
|
||||
Prefix: format(ColorFgHiCyan),
|
||||
Suffix: format(ColorReset),
|
||||
}
|
||||
}
|
||||
p.Anchor = func() *Property {
|
||||
return &Property{
|
||||
Prefix: format(ColorFgHiYellow),
|
||||
Suffix: format(ColorReset),
|
||||
}
|
||||
}
|
||||
p.Alias = func() *Property {
|
||||
return &Property{
|
||||
Prefix: format(ColorFgHiYellow),
|
||||
Suffix: format(ColorReset),
|
||||
}
|
||||
}
|
||||
p.String = func() *Property {
|
||||
return &Property{
|
||||
Prefix: format(ColorFgHiGreen),
|
||||
Suffix: format(ColorReset),
|
||||
}
|
||||
}
|
||||
p.Comment = func() *Property {
|
||||
return &Property{
|
||||
Prefix: format(ColorFgHiBlack),
|
||||
Suffix: format(ColorReset),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Printer) PrintErrorMessage(msg string, isColored bool) string {
|
||||
if isColored {
|
||||
return fmt.Sprintf("%s%s%s",
|
||||
format(ColorFgHiRed),
|
||||
msg,
|
||||
format(ColorReset),
|
||||
)
|
||||
}
|
||||
return msg
|
||||
}
|
||||
|
||||
func (p *Printer) removeLeftSideNewLineChar(src string) string {
|
||||
return strings.TrimLeft(strings.TrimLeft(strings.TrimLeft(src, "\r"), "\n"), "\r\n")
|
||||
}
|
||||
|
||||
func (p *Printer) removeRightSideNewLineChar(src string) string {
|
||||
return strings.TrimRight(strings.TrimRight(strings.TrimRight(src, "\r"), "\n"), "\r\n")
|
||||
}
|
||||
|
||||
func (p *Printer) removeRightSideWhiteSpaceChar(src string) string {
|
||||
return p.removeRightSideNewLineChar(strings.TrimRight(src, " "))
|
||||
}
|
||||
|
||||
func (p *Printer) newLineCount(s string) int {
|
||||
src := []rune(s)
|
||||
size := len(src)
|
||||
cnt := 0
|
||||
for i := 0; i < size; i++ {
|
||||
c := src[i]
|
||||
switch c {
|
||||
case '\r':
|
||||
if i+1 < size && src[i+1] == '\n' {
|
||||
i++
|
||||
}
|
||||
cnt++
|
||||
case '\n':
|
||||
cnt++
|
||||
}
|
||||
}
|
||||
return cnt
|
||||
}
|
||||
|
||||
func (p *Printer) isNewLineLastChar(s string) bool {
|
||||
for i := len(s) - 1; i > 0; i-- {
|
||||
c := s[i]
|
||||
switch c {
|
||||
case ' ':
|
||||
continue
|
||||
case '\n', '\r':
|
||||
return true
|
||||
}
|
||||
break
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *Printer) printBeforeTokens(tk *token.Token, minLine, extLine int) token.Tokens {
|
||||
for tk.Prev != nil {
|
||||
if tk.Prev.Position.Line < minLine {
|
||||
break
|
||||
}
|
||||
tk = tk.Prev
|
||||
}
|
||||
minTk := tk.Clone()
|
||||
if minTk.Prev != nil {
|
||||
// add white spaces to minTk by prev token
|
||||
prev := minTk.Prev
|
||||
whiteSpaceLen := len(prev.Origin) - len(strings.TrimRight(prev.Origin, " "))
|
||||
minTk.Origin = strings.Repeat(" ", whiteSpaceLen) + minTk.Origin
|
||||
}
|
||||
minTk.Origin = p.removeLeftSideNewLineChar(minTk.Origin)
|
||||
tokens := token.Tokens{minTk}
|
||||
tk = minTk.Next
|
||||
for tk != nil && tk.Position.Line <= extLine {
|
||||
clonedTk := tk.Clone()
|
||||
tokens.Add(clonedTk)
|
||||
tk = clonedTk.Next
|
||||
}
|
||||
lastTk := tokens[len(tokens)-1]
|
||||
trimmedOrigin := p.removeRightSideWhiteSpaceChar(lastTk.Origin)
|
||||
suffix := lastTk.Origin[len(trimmedOrigin):]
|
||||
lastTk.Origin = trimmedOrigin
|
||||
|
||||
if lastTk.Next != nil && len(suffix) > 1 {
|
||||
next := lastTk.Next.Clone()
|
||||
// add suffix to header of next token
|
||||
if suffix[0] == '\n' || suffix[0] == '\r' {
|
||||
suffix = suffix[1:]
|
||||
}
|
||||
next.Origin = suffix + next.Origin
|
||||
lastTk.Next = next
|
||||
}
|
||||
return tokens
|
||||
}
|
||||
|
||||
func (p *Printer) printAfterTokens(tk *token.Token, maxLine int) token.Tokens {
|
||||
tokens := token.Tokens{}
|
||||
if tk == nil {
|
||||
return tokens
|
||||
}
|
||||
if tk.Position.Line > maxLine {
|
||||
return tokens
|
||||
}
|
||||
minTk := tk.Clone()
|
||||
minTk.Origin = p.removeLeftSideNewLineChar(minTk.Origin)
|
||||
tokens.Add(minTk)
|
||||
tk = minTk.Next
|
||||
for tk != nil && tk.Position.Line <= maxLine {
|
||||
clonedTk := tk.Clone()
|
||||
tokens.Add(clonedTk)
|
||||
tk = clonedTk.Next
|
||||
}
|
||||
return tokens
|
||||
}
|
||||
|
||||
func (p *Printer) setupErrorTokenFormat(annotateLine int, isColored bool) {
|
||||
prefix := func(annotateLine, num int) string {
|
||||
if annotateLine == num {
|
||||
return fmt.Sprintf("> %2d | ", num)
|
||||
}
|
||||
return fmt.Sprintf(" %2d | ", num)
|
||||
}
|
||||
p.LineNumber = true
|
||||
p.LineNumberFormat = func(num int) string {
|
||||
if isColored {
|
||||
return colorize(prefix(annotateLine, num), ColorBold, ColorFgHiWhite)
|
||||
}
|
||||
return prefix(annotateLine, num)
|
||||
}
|
||||
if isColored {
|
||||
p.setDefaultColorSet()
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Printer) PrintErrorToken(tk *token.Token, isColored bool) string {
|
||||
errToken := tk
|
||||
curLine := tk.Position.Line
|
||||
curExtLine := curLine + p.newLineCount(p.removeLeftSideNewLineChar(tk.Origin))
|
||||
if p.isNewLineLastChar(tk.Origin) {
|
||||
// if last character ( exclude white space ) is new line character, ignore it.
|
||||
curExtLine--
|
||||
}
|
||||
|
||||
minLine := int(math.Max(float64(curLine-3), 1))
|
||||
maxLine := curExtLine + 3
|
||||
p.setupErrorTokenFormat(curLine, isColored)
|
||||
|
||||
beforeTokens := p.printBeforeTokens(tk, minLine, curExtLine)
|
||||
lastTk := beforeTokens[len(beforeTokens)-1]
|
||||
afterTokens := p.printAfterTokens(lastTk.Next, maxLine)
|
||||
|
||||
beforeSource := p.PrintTokens(beforeTokens)
|
||||
prefixSpaceNum := len(fmt.Sprintf(" %2d | ", curLine))
|
||||
annotateLine := strings.Repeat(" ", prefixSpaceNum+errToken.Position.Column-1) + "^"
|
||||
afterSource := p.PrintTokens(afterTokens)
|
||||
return fmt.Sprintf("%s\n%s\n%s", beforeSource, annotateLine, afterSource)
|
||||
}
|
||||
+452
@@ -0,0 +1,452 @@
|
||||
package scanner
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/goccy/go-yaml/token"
|
||||
)
|
||||
|
||||
// Context context at scanning
|
||||
type Context struct {
|
||||
idx int
|
||||
size int
|
||||
notSpaceCharPos int
|
||||
notSpaceOrgCharPos int
|
||||
src []rune
|
||||
buf []rune
|
||||
obuf []rune
|
||||
tokens token.Tokens
|
||||
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 (
|
||||
ctxPool = sync.Pool{
|
||||
New: func() interface{} {
|
||||
return createContext()
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func createContext() *Context {
|
||||
return &Context{
|
||||
idx: 0,
|
||||
tokens: token.Tokens{},
|
||||
}
|
||||
}
|
||||
|
||||
func newContext(src []rune) *Context {
|
||||
ctx, _ := ctxPool.Get().(*Context)
|
||||
ctx.reset(src)
|
||||
return ctx
|
||||
}
|
||||
|
||||
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.mstate = nil
|
||||
}
|
||||
|
||||
func (c *Context) resetBuffer() {
|
||||
c.buf = c.buf[:0]
|
||||
c.obuf = c.obuf[:0]
|
||||
c.notSpaceCharPos = 0
|
||||
c.notSpaceOrgCharPos = 0
|
||||
}
|
||||
|
||||
func (c *Context) breakMultiLine() {
|
||||
c.mstate = nil
|
||||
}
|
||||
|
||||
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) {
|
||||
if tk == nil {
|
||||
return
|
||||
}
|
||||
c.tokens = append(c.tokens, tk)
|
||||
}
|
||||
|
||||
func (c *Context) addBuf(r rune) {
|
||||
if len(c.buf) == 0 && (r == ' ' || r == '\t') {
|
||||
return
|
||||
}
|
||||
c.buf = append(c.buf, r)
|
||||
if r != ' ' && r != '\t' {
|
||||
c.notSpaceCharPos = len(c.buf)
|
||||
}
|
||||
}
|
||||
|
||||
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' {
|
||||
c.notSpaceOrgCharPos = len(c.obuf)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Context) removeRightSpaceFromBuf() {
|
||||
trimmedBuf := c.obuf[:c.notSpaceOrgCharPos]
|
||||
buflen := len(trimmedBuf)
|
||||
diff := len(c.obuf) - buflen
|
||||
if diff > 0 {
|
||||
c.obuf = c.obuf[:buflen]
|
||||
c.buf = c.bufferedSrc()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Context) isEOS() bool {
|
||||
return len(c.src)-1 <= c.idx
|
||||
}
|
||||
|
||||
func (c *Context) isNextEOS() bool {
|
||||
return len(c.src) <= c.idx+1
|
||||
}
|
||||
|
||||
func (c *Context) next() bool {
|
||||
return c.idx < c.size
|
||||
}
|
||||
|
||||
func (c *Context) source(s, e int) string {
|
||||
return string(c.src[s:e])
|
||||
}
|
||||
|
||||
func (c *Context) previousChar() rune {
|
||||
if c.idx > 0 {
|
||||
return c.src[c.idx-1]
|
||||
}
|
||||
return rune(0)
|
||||
}
|
||||
|
||||
func (c *Context) currentChar() rune {
|
||||
if c.size > c.idx {
|
||||
return c.src[c.idx]
|
||||
}
|
||||
return rune(0)
|
||||
}
|
||||
|
||||
func (c *Context) nextChar() rune {
|
||||
if c.size > c.idx+1 {
|
||||
return c.src[c.idx+1]
|
||||
}
|
||||
return rune(0)
|
||||
}
|
||||
|
||||
func (c *Context) repeatNum(r rune) int {
|
||||
cnt := 0
|
||||
for i := c.idx; i < c.size; i++ {
|
||||
if c.src[i] == r {
|
||||
cnt++
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
return cnt
|
||||
}
|
||||
|
||||
func (c *Context) progress(num int) {
|
||||
c.idx += num
|
||||
}
|
||||
|
||||
func (c *Context) 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.isMultiLine() {
|
||||
mstate := c.getMultiLineState()
|
||||
// remove end '\n' character and trailing empty lines.
|
||||
// https://yaml.org/spec/1.2.2/#8112-block-chomping-indicator
|
||||
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
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
func (c *Context) bufferedToken(pos *token.Position) *token.Token {
|
||||
if c.idx == 0 {
|
||||
return nil
|
||||
}
|
||||
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.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]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+17
@@ -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,
|
||||
}
|
||||
}
|
||||
+1536
File diff suppressed because it is too large
Load Diff
+113
@@ -0,0 +1,113 @@
|
||||
// Copied and trimmed down from https://github.com/golang/go/blob/e3769299cd3484e018e0e2a6e1b95c2b18ce4f41/src/strconv/quote.go
|
||||
// We want to use the standard library's private "quoteWith" function rather than write our own so that we get robust unicode support.
|
||||
// Every private function called by quoteWith was copied.
|
||||
// There are 2 modifications to simplify the code:
|
||||
// 1. The unicode.IsPrint function was substituted for the custom implementation of IsPrint
|
||||
// 2. All code paths reachable only when ASCIIonly or grphicOnly are set to true were removed.
|
||||
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package yaml
|
||||
|
||||
import (
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
const (
|
||||
lowerhex = "0123456789abcdef"
|
||||
)
|
||||
|
||||
func quoteWith(s string, quote byte) string {
|
||||
return string(appendQuotedWith(make([]byte, 0, 3*len(s)/2), s, quote))
|
||||
}
|
||||
|
||||
func appendQuotedWith(buf []byte, s string, quote byte) []byte {
|
||||
// Often called with big strings, so preallocate. If there's quoting,
|
||||
// this is conservative but still helps a lot.
|
||||
if cap(buf)-len(buf) < len(s) {
|
||||
nBuf := make([]byte, len(buf), len(buf)+1+len(s)+1)
|
||||
copy(nBuf, buf)
|
||||
buf = nBuf
|
||||
}
|
||||
buf = append(buf, quote)
|
||||
for width := 0; len(s) > 0; s = s[width:] {
|
||||
r := rune(s[0])
|
||||
width = 1
|
||||
if r >= utf8.RuneSelf {
|
||||
r, width = utf8.DecodeRuneInString(s)
|
||||
}
|
||||
if width == 1 && r == utf8.RuneError {
|
||||
buf = append(buf, `\x`...)
|
||||
buf = append(buf, lowerhex[s[0]>>4])
|
||||
buf = append(buf, lowerhex[s[0]&0xF])
|
||||
continue
|
||||
}
|
||||
buf = appendEscapedRune(buf, r, quote)
|
||||
}
|
||||
buf = append(buf, quote)
|
||||
return buf
|
||||
}
|
||||
|
||||
func appendEscapedRune(buf []byte, r rune, quote byte) []byte {
|
||||
var runeTmp [utf8.UTFMax]byte
|
||||
// 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
|
||||
}
|
||||
if unicode.IsPrint(r) {
|
||||
n := utf8.EncodeRune(runeTmp[:], r)
|
||||
buf = append(buf, runeTmp[:n]...)
|
||||
return buf
|
||||
}
|
||||
switch r {
|
||||
case '\a':
|
||||
buf = append(buf, `\a`...)
|
||||
case '\b':
|
||||
buf = append(buf, `\b`...)
|
||||
case '\f':
|
||||
buf = append(buf, `\f`...)
|
||||
case '\n':
|
||||
buf = append(buf, `\n`...)
|
||||
case '\r':
|
||||
buf = append(buf, `\r`...)
|
||||
case '\t':
|
||||
buf = append(buf, `\t`...)
|
||||
case '\v':
|
||||
buf = append(buf, `\v`...)
|
||||
default:
|
||||
switch {
|
||||
case r < ' ':
|
||||
buf = append(buf, `\x`...)
|
||||
buf = append(buf, lowerhex[byte(r)>>4])
|
||||
buf = append(buf, lowerhex[byte(r)&0xF])
|
||||
case r > utf8.MaxRune:
|
||||
r = 0xFFFD
|
||||
fallthrough
|
||||
case r < 0x10000:
|
||||
buf = append(buf, `\u`...)
|
||||
for s := 12; s >= 0; s -= 4 {
|
||||
buf = append(buf, lowerhex[r>>uint(s)&0xF])
|
||||
}
|
||||
default:
|
||||
buf = append(buf, `\U`...)
|
||||
for s := 28; s >= 0; s -= 4 {
|
||||
buf = append(buf, lowerhex[r>>uint(s)&0xF])
|
||||
}
|
||||
}
|
||||
}
|
||||
return buf
|
||||
}
|
||||
+128
@@ -0,0 +1,128 @@
|
||||
package yaml
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
// StructTagName tag keyword for Marshal/Unmarshal
|
||||
StructTagName = "yaml"
|
||||
)
|
||||
|
||||
// StructField information for each the field in structure
|
||||
type StructField struct {
|
||||
FieldName string
|
||||
RenderName string
|
||||
AnchorName string
|
||||
AliasName string
|
||||
IsAutoAnchor bool
|
||||
IsAutoAlias bool
|
||||
IsOmitEmpty bool
|
||||
IsOmitZero bool
|
||||
IsFlow bool
|
||||
IsInline bool
|
||||
}
|
||||
|
||||
func getTag(field reflect.StructField) string {
|
||||
// If struct tag `yaml` exist, use that. If no `yaml`
|
||||
// exists, but `json` does, use that and try the best to
|
||||
// adhere to its rules
|
||||
tag := field.Tag.Get(StructTagName)
|
||||
if tag == "" {
|
||||
tag = field.Tag.Get(`json`)
|
||||
}
|
||||
return tag
|
||||
}
|
||||
|
||||
func structField(field reflect.StructField) *StructField {
|
||||
tag := getTag(field)
|
||||
fieldName := strings.ToLower(field.Name)
|
||||
options := strings.Split(tag, ",")
|
||||
if len(options) > 0 {
|
||||
if options[0] != "" {
|
||||
fieldName = options[0]
|
||||
}
|
||||
}
|
||||
sf := &StructField{
|
||||
FieldName: field.Name,
|
||||
RenderName: fieldName,
|
||||
}
|
||||
if len(options) > 1 {
|
||||
for _, opt := range options[1:] {
|
||||
switch {
|
||||
case opt == "omitempty":
|
||||
sf.IsOmitEmpty = true
|
||||
case opt == "omitzero":
|
||||
sf.IsOmitZero = true
|
||||
case opt == "flow":
|
||||
sf.IsFlow = true
|
||||
case opt == "inline":
|
||||
sf.IsInline = true
|
||||
case strings.HasPrefix(opt, "anchor"):
|
||||
anchor := strings.Split(opt, "=")
|
||||
if len(anchor) > 1 {
|
||||
sf.AnchorName = anchor[1]
|
||||
} else {
|
||||
sf.IsAutoAnchor = true
|
||||
}
|
||||
case strings.HasPrefix(opt, "alias"):
|
||||
alias := strings.Split(opt, "=")
|
||||
if len(alias) > 1 {
|
||||
sf.AliasName = alias[1]
|
||||
} else {
|
||||
sf.IsAutoAlias = true
|
||||
}
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
return sf
|
||||
}
|
||||
|
||||
func isIgnoredStructField(field reflect.StructField) bool {
|
||||
if field.PkgPath != "" && !field.Anonymous {
|
||||
// private field
|
||||
return true
|
||||
}
|
||||
return getTag(field) == "-"
|
||||
}
|
||||
|
||||
type StructFieldMap map[string]*StructField
|
||||
|
||||
func (m StructFieldMap) isIncludedRenderName(name string) bool {
|
||||
for _, v := range m {
|
||||
if !v.IsInline && v.RenderName == name {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (m StructFieldMap) hasMergeProperty() bool {
|
||||
for _, v := range m {
|
||||
if v.IsOmitEmpty && v.IsInline && v.IsAutoAlias {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func structFieldMap(structType reflect.Type) (StructFieldMap, error) {
|
||||
fieldMap := StructFieldMap{}
|
||||
renderNameMap := map[string]struct{}{}
|
||||
for i := 0; i < structType.NumField(); i++ {
|
||||
field := structType.Field(i)
|
||||
if isIgnoredStructField(field) {
|
||||
continue
|
||||
}
|
||||
sf := structField(field)
|
||||
if _, exists := renderNameMap[sf.RenderName]; exists {
|
||||
return nil, fmt.Errorf("duplicated struct field name %s", sf.RenderName)
|
||||
}
|
||||
fieldMap[sf.FieldName] = sf
|
||||
renderNameMap[sf.RenderName] = struct{}{}
|
||||
}
|
||||
return fieldMap, nil
|
||||
}
|
||||
+1177
File diff suppressed because it is too large
Load Diff
+13
@@ -0,0 +1,13 @@
|
||||
package yaml
|
||||
|
||||
// StructValidator need to implement Struct method only
|
||||
// ( see https://pkg.go.dev/github.com/go-playground/validator/v10#Validate.Struct )
|
||||
type StructValidator interface {
|
||||
Struct(interface{}) error
|
||||
}
|
||||
|
||||
// FieldError need to implement StructField method only
|
||||
// ( see https://pkg.go.dev/github.com/go-playground/validator/v10#FieldError )
|
||||
type FieldError interface {
|
||||
StructField() string
|
||||
}
|
||||
+357
@@ -0,0 +1,357 @@
|
||||
package yaml
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"io"
|
||||
"reflect"
|
||||
"sync"
|
||||
|
||||
"github.com/goccy/go-yaml/ast"
|
||||
"github.com/goccy/go-yaml/internal/errors"
|
||||
)
|
||||
|
||||
// BytesMarshaler interface may be implemented by types to customize their
|
||||
// behavior when being marshaled into a YAML document. The returned value
|
||||
// is marshaled in place of the original value implementing Marshaler.
|
||||
//
|
||||
// If an error is returned by MarshalYAML, the marshaling procedure stops
|
||||
// and returns with the provided error.
|
||||
type BytesMarshaler interface {
|
||||
MarshalYAML() ([]byte, error)
|
||||
}
|
||||
|
||||
// BytesMarshalerContext interface use BytesMarshaler with context.Context.
|
||||
type BytesMarshalerContext interface {
|
||||
MarshalYAML(context.Context) ([]byte, error)
|
||||
}
|
||||
|
||||
// InterfaceMarshaler interface has MarshalYAML compatible with github.com/go-yaml/yaml package.
|
||||
type InterfaceMarshaler interface {
|
||||
MarshalYAML() (interface{}, error)
|
||||
}
|
||||
|
||||
// InterfaceMarshalerContext interface use InterfaceMarshaler with context.Context.
|
||||
type InterfaceMarshalerContext interface {
|
||||
MarshalYAML(context.Context) (interface{}, error)
|
||||
}
|
||||
|
||||
// BytesUnmarshaler interface may be implemented by types to customize their
|
||||
// behavior when being unmarshaled from a YAML document.
|
||||
type BytesUnmarshaler interface {
|
||||
UnmarshalYAML([]byte) error
|
||||
}
|
||||
|
||||
// BytesUnmarshalerContext interface use BytesUnmarshaler with context.Context.
|
||||
type BytesUnmarshalerContext interface {
|
||||
UnmarshalYAML(context.Context, []byte) error
|
||||
}
|
||||
|
||||
// InterfaceUnmarshaler interface has UnmarshalYAML compatible with github.com/go-yaml/yaml package.
|
||||
type InterfaceUnmarshaler interface {
|
||||
UnmarshalYAML(func(interface{}) error) error
|
||||
}
|
||||
|
||||
// InterfaceUnmarshalerContext interface use InterfaceUnmarshaler with context.Context.
|
||||
type InterfaceUnmarshalerContext interface {
|
||||
UnmarshalYAML(context.Context, func(interface{}) error) error
|
||||
}
|
||||
|
||||
// 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{}
|
||||
}
|
||||
|
||||
// MapSlice encodes and decodes as a YAML map.
|
||||
// The order of keys is preserved when encoding and decoding.
|
||||
type MapSlice []MapItem
|
||||
|
||||
// ToMap convert to map[interface{}]interface{}.
|
||||
func (s MapSlice) ToMap() map[interface{}]interface{} {
|
||||
v := map[interface{}]interface{}{}
|
||||
for _, item := range s {
|
||||
v[item.Key] = item.Value
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// Marshal serializes the value provided into a YAML document. The structure
|
||||
// of the generated document will reflect the structure of the value itself.
|
||||
// Maps and pointers (to struct, string, int, etc) are accepted as the in value.
|
||||
//
|
||||
// Struct fields are only 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 marshaling process.
|
||||
// Conflicting names result in a runtime error.
|
||||
//
|
||||
// The field tag format accepted is:
|
||||
//
|
||||
// `(...) yaml:"[<key>][,<flag1>[,<flag2>]]" (...)`
|
||||
//
|
||||
// The following flags are currently supported:
|
||||
//
|
||||
// omitempty Only include the field if it's not set to the zero
|
||||
// value for the type or to empty slices or maps.
|
||||
// Zero valued structs will be omitted if all their public
|
||||
// fields are zero, unless they implement an IsZero
|
||||
// method (see the IsZeroer interface type), in which
|
||||
// case the field will be included if that method returns true.
|
||||
// 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).
|
||||
//
|
||||
// inline Inline the field, which must be a struct or a map,
|
||||
// causing all of its fields or keys to be processed as if
|
||||
// they were part of the outer struct. For maps, keys must
|
||||
// not conflict with the yaml keys of other struct fields.
|
||||
//
|
||||
// anchor Marshal with anchor. If want to define anchor name explicitly, use anchor=name style.
|
||||
// Otherwise, if used 'anchor' name only, used the field name lowercased as the anchor name
|
||||
//
|
||||
// alias Marshal with alias. If want to define alias name explicitly, use alias=name style.
|
||||
// Otherwise, If omitted alias name and the field type is pointer type,
|
||||
// assigned anchor name automatically from same pointer address.
|
||||
//
|
||||
// In addition, if the key is "-", the field is ignored.
|
||||
//
|
||||
// For example:
|
||||
//
|
||||
// type T struct {
|
||||
// F int `yaml:"a,omitempty"`
|
||||
// B int
|
||||
// }
|
||||
// yaml.Marshal(&T{B: 2}) // Returns "b: 2\n"
|
||||
// yaml.Marshal(&T{F: 1}) // Returns "a: 1\nb: 0\n"
|
||||
func Marshal(v interface{}) ([]byte, error) {
|
||||
return MarshalWithOptions(v)
|
||||
}
|
||||
|
||||
// MarshalWithOptions serializes the value provided into a YAML document with EncodeOptions.
|
||||
func MarshalWithOptions(v interface{}, opts ...EncodeOption) ([]byte, error) {
|
||||
return MarshalContext(context.Background(), v, opts...)
|
||||
}
|
||||
|
||||
// MarshalContext serializes the value provided into a YAML document with context.Context and EncodeOptions.
|
||||
func MarshalContext(ctx context.Context, v interface{}, opts ...EncodeOption) ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
if err := NewEncoder(&buf, opts...).EncodeContext(ctx, v); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// ValueToNode convert from value to ast.Node.
|
||||
func ValueToNode(v interface{}, opts ...EncodeOption) (ast.Node, error) {
|
||||
var buf bytes.Buffer
|
||||
node, err := NewEncoder(&buf, opts...).EncodeToNode(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return node, nil
|
||||
}
|
||||
|
||||
// Unmarshal decodes the first document found within the in byte slice
|
||||
// and assigns decoded values into the out value.
|
||||
//
|
||||
// Struct fields are only unmarshalled if they are exported (have an
|
||||
// upper case first letter), and are unmarshalled using the field name
|
||||
// lowercased as the default key. Custom keys may be defined via the
|
||||
// "yaml" name in the field tag: the content preceding the first comma
|
||||
// is used as the key, and the following comma-separated options are
|
||||
// used to tweak the marshaling process (see Marshal).
|
||||
// Conflicting names result in a runtime error.
|
||||
//
|
||||
// For example:
|
||||
//
|
||||
// type T struct {
|
||||
// F int `yaml:"a,omitempty"`
|
||||
// B int
|
||||
// }
|
||||
// var t T
|
||||
// yaml.Unmarshal([]byte("a: 1\nb: 2"), &t)
|
||||
//
|
||||
// See the documentation of Marshal for the format of tags and a list of
|
||||
// supported tag options.
|
||||
func Unmarshal(data []byte, v interface{}) error {
|
||||
return UnmarshalWithOptions(data, v)
|
||||
}
|
||||
|
||||
// UnmarshalWithOptions decodes with DecodeOptions the first document found within the in byte slice
|
||||
// and assigns decoded values into the out value.
|
||||
func UnmarshalWithOptions(data []byte, v interface{}, opts ...DecodeOption) error {
|
||||
return UnmarshalContext(context.Background(), data, v, opts...)
|
||||
}
|
||||
|
||||
// UnmarshalContext decodes with context.Context and DecodeOptions.
|
||||
func UnmarshalContext(ctx context.Context, data []byte, v interface{}, opts ...DecodeOption) error {
|
||||
dec := NewDecoder(bytes.NewBuffer(data), opts...)
|
||||
if err := dec.DecodeContext(ctx, v); err != nil {
|
||||
if err == io.EOF {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NodeToValue converts node to the value pointed to by v.
|
||||
func NodeToValue(node ast.Node, v interface{}, opts ...DecodeOption) error {
|
||||
var buf bytes.Buffer
|
||||
if err := NewDecoder(&buf, opts...).DecodeFromNode(node, v); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// FormatError is a utility function that takes advantage of the metadata
|
||||
// stored in the errors returned by this package's parser.
|
||||
//
|
||||
// If the second argument `colored` is true, the error message is colorized.
|
||||
// If the third argument `inclSource` is true, the error message will
|
||||
// contain snippets of the YAML source that was used.
|
||||
func FormatError(e error, colored, inclSource bool) string {
|
||||
var yamlErr Error
|
||||
if errors.As(e, &yamlErr) {
|
||||
return yamlErr.FormatError(colored, inclSource)
|
||||
}
|
||||
|
||||
return e.Error()
|
||||
}
|
||||
|
||||
// YAMLToJSON convert YAML bytes to JSON.
|
||||
func YAMLToJSON(bytes []byte) ([]byte, error) {
|
||||
var v interface{}
|
||||
if err := UnmarshalWithOptions(bytes, &v, UseOrderedMap()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out, err := MarshalWithOptions(v, JSON())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// JSONToYAML convert JSON bytes to YAML.
|
||||
func JSONToYAML(bytes []byte) ([]byte, error) {
|
||||
var v interface{}
|
||||
if err := UnmarshalWithOptions(bytes, &v, UseOrderedMap()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out, err := Marshal(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
var (
|
||||
globalCustomMarshalerMu sync.Mutex
|
||||
globalCustomUnmarshalerMu sync.Mutex
|
||||
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.
|
||||
// If you want to switch the behavior for each encoder, use `CustomMarshaler` defined as EncodeOption.
|
||||
//
|
||||
// NOTE: If type T implements MarshalYAML for pointer receiver, the type specified in RegisterCustomMarshaler must be *T.
|
||||
// If RegisterCustomMarshaler and CustomMarshaler of EncodeOption are specified for the same type,
|
||||
// the CustomMarshaler specified in EncodeOption takes precedence.
|
||||
func RegisterCustomMarshaler[T any](marshaler func(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(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.
|
||||
//
|
||||
// NOTE: If RegisterCustomUnmarshaler and CustomUnmarshaler of DecodeOption are specified for the same type,
|
||||
// the CustomUnmarshaler specified in DecodeOption takes precedence.
|
||||
func RegisterCustomUnmarshaler[T any](unmarshaler func(*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(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)
|
||||
}
|
||||
}
|
||||
|
||||
// RawMessage is a raw encoded YAML value. It implements [BytesMarshaler] and
|
||||
// [BytesUnmarshaler] and can be used to delay YAML decoding or precompute a YAML
|
||||
// encoding.
|
||||
// It also implements [json.Marshaler] and [json.Unmarshaler].
|
||||
//
|
||||
// This is similar to [json.RawMessage] in the stdlib.
|
||||
type RawMessage []byte
|
||||
|
||||
func (m RawMessage) MarshalYAML() ([]byte, error) {
|
||||
if m == nil {
|
||||
return []byte("null"), nil
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (m *RawMessage) UnmarshalYAML(dt []byte) error {
|
||||
if m == nil {
|
||||
return errors.New("yaml.RawMessage: UnmarshalYAML on nil pointer")
|
||||
}
|
||||
*m = append((*m)[0:0], dt...)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *RawMessage) UnmarshalJSON(b []byte) error {
|
||||
return m.UnmarshalYAML(b)
|
||||
}
|
||||
|
||||
func (m RawMessage) MarshalJSON() ([]byte, error) {
|
||||
return YAMLToJSON(m)
|
||||
}
|
||||
-50
@@ -1,50 +0,0 @@
|
||||
|
||||
This project is covered by two different licenses: MIT and Apache.
|
||||
|
||||
#### MIT License ####
|
||||
|
||||
The following files were ported to Go from C files of libyaml, and thus
|
||||
are still covered by their original MIT license, with the additional
|
||||
copyright staring in 2011 when the project was ported over:
|
||||
|
||||
apic.go emitterc.go parserc.go readerc.go scannerc.go
|
||||
writerc.go yamlh.go yamlprivateh.go
|
||||
|
||||
Copyright (c) 2006-2010 Kirill Simonov
|
||||
Copyright (c) 2006-2011 Kirill Simonov
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
### Apache License ###
|
||||
|
||||
All the remaining project files are covered by the Apache license:
|
||||
|
||||
Copyright (c) 2011-2019 Canonical Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-13
@@ -1,13 +0,0 @@
|
||||
Copyright 2011-2016 Canonical Ltd.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-150
@@ -1,150 +0,0 @@
|
||||
# YAML support for the Go language
|
||||
|
||||
Introduction
|
||||
------------
|
||||
|
||||
The yaml package enables Go programs to comfortably encode and decode YAML
|
||||
values. It was developed within [Canonical](https://www.canonical.com) as
|
||||
part of the [juju](https://juju.ubuntu.com) project, and is based on a
|
||||
pure Go port of the well-known [libyaml](http://pyyaml.org/wiki/LibYAML)
|
||||
C library to parse and generate YAML data quickly and reliably.
|
||||
|
||||
Compatibility
|
||||
-------------
|
||||
|
||||
The yaml package supports most of YAML 1.2, but preserves some behavior
|
||||
from 1.1 for backwards compatibility.
|
||||
|
||||
Specifically, as of v3 of the yaml package:
|
||||
|
||||
- YAML 1.1 bools (_yes/no, on/off_) are supported as long as they are being
|
||||
decoded into a typed bool value. Otherwise they behave as a string. Booleans
|
||||
in YAML 1.2 are _true/false_ only.
|
||||
- Octals encode and decode as _0777_ per YAML 1.1, rather than _0o777_
|
||||
as specified in YAML 1.2, because most parsers still use the old format.
|
||||
Octals in the _0o777_ format are supported though, so new files work.
|
||||
- Does not support base-60 floats. These are gone from YAML 1.2, and were
|
||||
actually never supported by this package as it's clearly a poor choice.
|
||||
|
||||
and offers backwards
|
||||
compatibility with YAML 1.1 in some cases.
|
||||
1.2, including support for
|
||||
anchors, tags, map merging, etc. Multi-document unmarshalling is not yet
|
||||
implemented, and base-60 floats from YAML 1.1 are purposefully not
|
||||
supported since they're a poor design and are gone in YAML 1.2.
|
||||
|
||||
Installation and usage
|
||||
----------------------
|
||||
|
||||
The import path for the package is *gopkg.in/yaml.v3*.
|
||||
|
||||
To install it, run:
|
||||
|
||||
go get gopkg.in/yaml.v3
|
||||
|
||||
API documentation
|
||||
-----------------
|
||||
|
||||
If opened in a browser, the import path itself leads to the API documentation:
|
||||
|
||||
- [https://gopkg.in/yaml.v3](https://gopkg.in/yaml.v3)
|
||||
|
||||
API stability
|
||||
-------------
|
||||
|
||||
The package API for yaml v3 will remain stable as described in [gopkg.in](https://gopkg.in).
|
||||
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
The yaml package is licensed under the MIT and Apache License 2.0 licenses.
|
||||
Please see the LICENSE file for details.
|
||||
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
```Go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
var data = `
|
||||
a: Easy!
|
||||
b:
|
||||
c: 2
|
||||
d: [3, 4]
|
||||
`
|
||||
|
||||
// Note: struct fields must be public in order for unmarshal to
|
||||
// correctly populate the data.
|
||||
type T struct {
|
||||
A string
|
||||
B struct {
|
||||
RenamedC int `yaml:"c"`
|
||||
D []int `yaml:",flow"`
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
t := T{}
|
||||
|
||||
err := yaml.Unmarshal([]byte(data), &t)
|
||||
if err != nil {
|
||||
log.Fatalf("error: %v", err)
|
||||
}
|
||||
fmt.Printf("--- t:\n%v\n\n", t)
|
||||
|
||||
d, err := yaml.Marshal(&t)
|
||||
if err != nil {
|
||||
log.Fatalf("error: %v", err)
|
||||
}
|
||||
fmt.Printf("--- t dump:\n%s\n\n", string(d))
|
||||
|
||||
m := make(map[interface{}]interface{})
|
||||
|
||||
err = yaml.Unmarshal([]byte(data), &m)
|
||||
if err != nil {
|
||||
log.Fatalf("error: %v", err)
|
||||
}
|
||||
fmt.Printf("--- m:\n%v\n\n", m)
|
||||
|
||||
d, err = yaml.Marshal(&m)
|
||||
if err != nil {
|
||||
log.Fatalf("error: %v", err)
|
||||
}
|
||||
fmt.Printf("--- m dump:\n%s\n\n", string(d))
|
||||
}
|
||||
```
|
||||
|
||||
This example will generate the following output:
|
||||
|
||||
```
|
||||
--- t:
|
||||
{Easy! {2 [3 4]}}
|
||||
|
||||
--- t dump:
|
||||
a: Easy!
|
||||
b:
|
||||
c: 2
|
||||
d: [3, 4]
|
||||
|
||||
|
||||
--- m:
|
||||
map[a:Easy! b:map[c:2 d:[3 4]]]
|
||||
|
||||
--- m dump:
|
||||
a: Easy!
|
||||
b:
|
||||
c: 2
|
||||
d:
|
||||
- 3
|
||||
- 4
|
||||
```
|
||||
|
||||
-747
@@ -1,747 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2011-2019 Canonical Ltd
|
||||
// Copyright (c) 2006-2010 Kirill Simonov
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
// this software and associated documentation files (the "Software"), to deal in
|
||||
// the Software without restriction, including without limitation the rights to
|
||||
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
// of the Software, and to permit persons to whom the Software is furnished to do
|
||||
// so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
package yaml
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
func yaml_insert_token(parser *yaml_parser_t, pos int, token *yaml_token_t) {
|
||||
//fmt.Println("yaml_insert_token", "pos:", pos, "typ:", token.typ, "head:", parser.tokens_head, "len:", len(parser.tokens))
|
||||
|
||||
// Check if we can move the queue at the beginning of the buffer.
|
||||
if parser.tokens_head > 0 && len(parser.tokens) == cap(parser.tokens) {
|
||||
if parser.tokens_head != len(parser.tokens) {
|
||||
copy(parser.tokens, parser.tokens[parser.tokens_head:])
|
||||
}
|
||||
parser.tokens = parser.tokens[:len(parser.tokens)-parser.tokens_head]
|
||||
parser.tokens_head = 0
|
||||
}
|
||||
parser.tokens = append(parser.tokens, *token)
|
||||
if pos < 0 {
|
||||
return
|
||||
}
|
||||
copy(parser.tokens[parser.tokens_head+pos+1:], parser.tokens[parser.tokens_head+pos:])
|
||||
parser.tokens[parser.tokens_head+pos] = *token
|
||||
}
|
||||
|
||||
// Create a new parser object.
|
||||
func yaml_parser_initialize(parser *yaml_parser_t) bool {
|
||||
*parser = yaml_parser_t{
|
||||
raw_buffer: make([]byte, 0, input_raw_buffer_size),
|
||||
buffer: make([]byte, 0, input_buffer_size),
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Destroy a parser object.
|
||||
func yaml_parser_delete(parser *yaml_parser_t) {
|
||||
*parser = yaml_parser_t{}
|
||||
}
|
||||
|
||||
// String read handler.
|
||||
func yaml_string_read_handler(parser *yaml_parser_t, buffer []byte) (n int, err error) {
|
||||
if parser.input_pos == len(parser.input) {
|
||||
return 0, io.EOF
|
||||
}
|
||||
n = copy(buffer, parser.input[parser.input_pos:])
|
||||
parser.input_pos += n
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// Reader read handler.
|
||||
func yaml_reader_read_handler(parser *yaml_parser_t, buffer []byte) (n int, err error) {
|
||||
return parser.input_reader.Read(buffer)
|
||||
}
|
||||
|
||||
// Set a string input.
|
||||
func yaml_parser_set_input_string(parser *yaml_parser_t, input []byte) {
|
||||
if parser.read_handler != nil {
|
||||
panic("must set the input source only once")
|
||||
}
|
||||
parser.read_handler = yaml_string_read_handler
|
||||
parser.input = input
|
||||
parser.input_pos = 0
|
||||
}
|
||||
|
||||
// Set a file input.
|
||||
func yaml_parser_set_input_reader(parser *yaml_parser_t, r io.Reader) {
|
||||
if parser.read_handler != nil {
|
||||
panic("must set the input source only once")
|
||||
}
|
||||
parser.read_handler = yaml_reader_read_handler
|
||||
parser.input_reader = r
|
||||
}
|
||||
|
||||
// Set the source encoding.
|
||||
func yaml_parser_set_encoding(parser *yaml_parser_t, encoding yaml_encoding_t) {
|
||||
if parser.encoding != yaml_ANY_ENCODING {
|
||||
panic("must set the encoding only once")
|
||||
}
|
||||
parser.encoding = encoding
|
||||
}
|
||||
|
||||
// Create a new emitter object.
|
||||
func yaml_emitter_initialize(emitter *yaml_emitter_t) {
|
||||
*emitter = yaml_emitter_t{
|
||||
buffer: make([]byte, output_buffer_size),
|
||||
raw_buffer: make([]byte, 0, output_raw_buffer_size),
|
||||
states: make([]yaml_emitter_state_t, 0, initial_stack_size),
|
||||
events: make([]yaml_event_t, 0, initial_queue_size),
|
||||
best_width: -1,
|
||||
}
|
||||
}
|
||||
|
||||
// Destroy an emitter object.
|
||||
func yaml_emitter_delete(emitter *yaml_emitter_t) {
|
||||
*emitter = yaml_emitter_t{}
|
||||
}
|
||||
|
||||
// String write handler.
|
||||
func yaml_string_write_handler(emitter *yaml_emitter_t, buffer []byte) error {
|
||||
*emitter.output_buffer = append(*emitter.output_buffer, buffer...)
|
||||
return nil
|
||||
}
|
||||
|
||||
// yaml_writer_write_handler uses emitter.output_writer to write the
|
||||
// emitted text.
|
||||
func yaml_writer_write_handler(emitter *yaml_emitter_t, buffer []byte) error {
|
||||
_, err := emitter.output_writer.Write(buffer)
|
||||
return err
|
||||
}
|
||||
|
||||
// Set a string output.
|
||||
func yaml_emitter_set_output_string(emitter *yaml_emitter_t, output_buffer *[]byte) {
|
||||
if emitter.write_handler != nil {
|
||||
panic("must set the output target only once")
|
||||
}
|
||||
emitter.write_handler = yaml_string_write_handler
|
||||
emitter.output_buffer = output_buffer
|
||||
}
|
||||
|
||||
// Set a file output.
|
||||
func yaml_emitter_set_output_writer(emitter *yaml_emitter_t, w io.Writer) {
|
||||
if emitter.write_handler != nil {
|
||||
panic("must set the output target only once")
|
||||
}
|
||||
emitter.write_handler = yaml_writer_write_handler
|
||||
emitter.output_writer = w
|
||||
}
|
||||
|
||||
// Set the output encoding.
|
||||
func yaml_emitter_set_encoding(emitter *yaml_emitter_t, encoding yaml_encoding_t) {
|
||||
if emitter.encoding != yaml_ANY_ENCODING {
|
||||
panic("must set the output encoding only once")
|
||||
}
|
||||
emitter.encoding = encoding
|
||||
}
|
||||
|
||||
// Set the canonical output style.
|
||||
func yaml_emitter_set_canonical(emitter *yaml_emitter_t, canonical bool) {
|
||||
emitter.canonical = canonical
|
||||
}
|
||||
|
||||
// Set the indentation increment.
|
||||
func yaml_emitter_set_indent(emitter *yaml_emitter_t, indent int) {
|
||||
if indent < 2 || indent > 9 {
|
||||
indent = 2
|
||||
}
|
||||
emitter.best_indent = indent
|
||||
}
|
||||
|
||||
// Set the preferred line width.
|
||||
func yaml_emitter_set_width(emitter *yaml_emitter_t, width int) {
|
||||
if width < 0 {
|
||||
width = -1
|
||||
}
|
||||
emitter.best_width = width
|
||||
}
|
||||
|
||||
// Set if unescaped non-ASCII characters are allowed.
|
||||
func yaml_emitter_set_unicode(emitter *yaml_emitter_t, unicode bool) {
|
||||
emitter.unicode = unicode
|
||||
}
|
||||
|
||||
// Set the preferred line break character.
|
||||
func yaml_emitter_set_break(emitter *yaml_emitter_t, line_break yaml_break_t) {
|
||||
emitter.line_break = line_break
|
||||
}
|
||||
|
||||
///*
|
||||
// * Destroy a token object.
|
||||
// */
|
||||
//
|
||||
//YAML_DECLARE(void)
|
||||
//yaml_token_delete(yaml_token_t *token)
|
||||
//{
|
||||
// assert(token); // Non-NULL token object expected.
|
||||
//
|
||||
// switch (token.type)
|
||||
// {
|
||||
// case YAML_TAG_DIRECTIVE_TOKEN:
|
||||
// yaml_free(token.data.tag_directive.handle);
|
||||
// yaml_free(token.data.tag_directive.prefix);
|
||||
// break;
|
||||
//
|
||||
// case YAML_ALIAS_TOKEN:
|
||||
// yaml_free(token.data.alias.value);
|
||||
// break;
|
||||
//
|
||||
// case YAML_ANCHOR_TOKEN:
|
||||
// yaml_free(token.data.anchor.value);
|
||||
// break;
|
||||
//
|
||||
// case YAML_TAG_TOKEN:
|
||||
// yaml_free(token.data.tag.handle);
|
||||
// yaml_free(token.data.tag.suffix);
|
||||
// break;
|
||||
//
|
||||
// case YAML_SCALAR_TOKEN:
|
||||
// yaml_free(token.data.scalar.value);
|
||||
// break;
|
||||
//
|
||||
// default:
|
||||
// break;
|
||||
// }
|
||||
//
|
||||
// memset(token, 0, sizeof(yaml_token_t));
|
||||
//}
|
||||
//
|
||||
///*
|
||||
// * Check if a string is a valid UTF-8 sequence.
|
||||
// *
|
||||
// * Check 'reader.c' for more details on UTF-8 encoding.
|
||||
// */
|
||||
//
|
||||
//static int
|
||||
//yaml_check_utf8(yaml_char_t *start, size_t length)
|
||||
//{
|
||||
// yaml_char_t *end = start+length;
|
||||
// yaml_char_t *pointer = start;
|
||||
//
|
||||
// while (pointer < end) {
|
||||
// unsigned char octet;
|
||||
// unsigned int width;
|
||||
// unsigned int value;
|
||||
// size_t k;
|
||||
//
|
||||
// octet = pointer[0];
|
||||
// width = (octet & 0x80) == 0x00 ? 1 :
|
||||
// (octet & 0xE0) == 0xC0 ? 2 :
|
||||
// (octet & 0xF0) == 0xE0 ? 3 :
|
||||
// (octet & 0xF8) == 0xF0 ? 4 : 0;
|
||||
// value = (octet & 0x80) == 0x00 ? octet & 0x7F :
|
||||
// (octet & 0xE0) == 0xC0 ? octet & 0x1F :
|
||||
// (octet & 0xF0) == 0xE0 ? octet & 0x0F :
|
||||
// (octet & 0xF8) == 0xF0 ? octet & 0x07 : 0;
|
||||
// if (!width) return 0;
|
||||
// if (pointer+width > end) return 0;
|
||||
// for (k = 1; k < width; k ++) {
|
||||
// octet = pointer[k];
|
||||
// if ((octet & 0xC0) != 0x80) return 0;
|
||||
// value = (value << 6) + (octet & 0x3F);
|
||||
// }
|
||||
// if (!((width == 1) ||
|
||||
// (width == 2 && value >= 0x80) ||
|
||||
// (width == 3 && value >= 0x800) ||
|
||||
// (width == 4 && value >= 0x10000))) return 0;
|
||||
//
|
||||
// pointer += width;
|
||||
// }
|
||||
//
|
||||
// return 1;
|
||||
//}
|
||||
//
|
||||
|
||||
// Create STREAM-START.
|
||||
func yaml_stream_start_event_initialize(event *yaml_event_t, encoding yaml_encoding_t) {
|
||||
*event = yaml_event_t{
|
||||
typ: yaml_STREAM_START_EVENT,
|
||||
encoding: encoding,
|
||||
}
|
||||
}
|
||||
|
||||
// Create STREAM-END.
|
||||
func yaml_stream_end_event_initialize(event *yaml_event_t) {
|
||||
*event = yaml_event_t{
|
||||
typ: yaml_STREAM_END_EVENT,
|
||||
}
|
||||
}
|
||||
|
||||
// Create DOCUMENT-START.
|
||||
func yaml_document_start_event_initialize(
|
||||
event *yaml_event_t,
|
||||
version_directive *yaml_version_directive_t,
|
||||
tag_directives []yaml_tag_directive_t,
|
||||
implicit bool,
|
||||
) {
|
||||
*event = yaml_event_t{
|
||||
typ: yaml_DOCUMENT_START_EVENT,
|
||||
version_directive: version_directive,
|
||||
tag_directives: tag_directives,
|
||||
implicit: implicit,
|
||||
}
|
||||
}
|
||||
|
||||
// Create DOCUMENT-END.
|
||||
func yaml_document_end_event_initialize(event *yaml_event_t, implicit bool) {
|
||||
*event = yaml_event_t{
|
||||
typ: yaml_DOCUMENT_END_EVENT,
|
||||
implicit: implicit,
|
||||
}
|
||||
}
|
||||
|
||||
// Create ALIAS.
|
||||
func yaml_alias_event_initialize(event *yaml_event_t, anchor []byte) bool {
|
||||
*event = yaml_event_t{
|
||||
typ: yaml_ALIAS_EVENT,
|
||||
anchor: anchor,
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Create SCALAR.
|
||||
func yaml_scalar_event_initialize(event *yaml_event_t, anchor, tag, value []byte, plain_implicit, quoted_implicit bool, style yaml_scalar_style_t) bool {
|
||||
*event = yaml_event_t{
|
||||
typ: yaml_SCALAR_EVENT,
|
||||
anchor: anchor,
|
||||
tag: tag,
|
||||
value: value,
|
||||
implicit: plain_implicit,
|
||||
quoted_implicit: quoted_implicit,
|
||||
style: yaml_style_t(style),
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Create SEQUENCE-START.
|
||||
func yaml_sequence_start_event_initialize(event *yaml_event_t, anchor, tag []byte, implicit bool, style yaml_sequence_style_t) bool {
|
||||
*event = yaml_event_t{
|
||||
typ: yaml_SEQUENCE_START_EVENT,
|
||||
anchor: anchor,
|
||||
tag: tag,
|
||||
implicit: implicit,
|
||||
style: yaml_style_t(style),
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Create SEQUENCE-END.
|
||||
func yaml_sequence_end_event_initialize(event *yaml_event_t) bool {
|
||||
*event = yaml_event_t{
|
||||
typ: yaml_SEQUENCE_END_EVENT,
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Create MAPPING-START.
|
||||
func yaml_mapping_start_event_initialize(event *yaml_event_t, anchor, tag []byte, implicit bool, style yaml_mapping_style_t) {
|
||||
*event = yaml_event_t{
|
||||
typ: yaml_MAPPING_START_EVENT,
|
||||
anchor: anchor,
|
||||
tag: tag,
|
||||
implicit: implicit,
|
||||
style: yaml_style_t(style),
|
||||
}
|
||||
}
|
||||
|
||||
// Create MAPPING-END.
|
||||
func yaml_mapping_end_event_initialize(event *yaml_event_t) {
|
||||
*event = yaml_event_t{
|
||||
typ: yaml_MAPPING_END_EVENT,
|
||||
}
|
||||
}
|
||||
|
||||
// Destroy an event object.
|
||||
func yaml_event_delete(event *yaml_event_t) {
|
||||
*event = yaml_event_t{}
|
||||
}
|
||||
|
||||
///*
|
||||
// * Create a document object.
|
||||
// */
|
||||
//
|
||||
//YAML_DECLARE(int)
|
||||
//yaml_document_initialize(document *yaml_document_t,
|
||||
// version_directive *yaml_version_directive_t,
|
||||
// tag_directives_start *yaml_tag_directive_t,
|
||||
// tag_directives_end *yaml_tag_directive_t,
|
||||
// start_implicit int, end_implicit int)
|
||||
//{
|
||||
// struct {
|
||||
// error yaml_error_type_t
|
||||
// } context
|
||||
// struct {
|
||||
// start *yaml_node_t
|
||||
// end *yaml_node_t
|
||||
// top *yaml_node_t
|
||||
// } nodes = { NULL, NULL, NULL }
|
||||
// version_directive_copy *yaml_version_directive_t = NULL
|
||||
// struct {
|
||||
// start *yaml_tag_directive_t
|
||||
// end *yaml_tag_directive_t
|
||||
// top *yaml_tag_directive_t
|
||||
// } tag_directives_copy = { NULL, NULL, NULL }
|
||||
// value yaml_tag_directive_t = { NULL, NULL }
|
||||
// mark yaml_mark_t = { 0, 0, 0 }
|
||||
//
|
||||
// assert(document) // Non-NULL document object is expected.
|
||||
// assert((tag_directives_start && tag_directives_end) ||
|
||||
// (tag_directives_start == tag_directives_end))
|
||||
// // Valid tag directives are expected.
|
||||
//
|
||||
// if (!STACK_INIT(&context, nodes, INITIAL_STACK_SIZE)) goto error
|
||||
//
|
||||
// if (version_directive) {
|
||||
// version_directive_copy = yaml_malloc(sizeof(yaml_version_directive_t))
|
||||
// if (!version_directive_copy) goto error
|
||||
// version_directive_copy.major = version_directive.major
|
||||
// version_directive_copy.minor = version_directive.minor
|
||||
// }
|
||||
//
|
||||
// if (tag_directives_start != tag_directives_end) {
|
||||
// tag_directive *yaml_tag_directive_t
|
||||
// if (!STACK_INIT(&context, tag_directives_copy, INITIAL_STACK_SIZE))
|
||||
// goto error
|
||||
// for (tag_directive = tag_directives_start
|
||||
// tag_directive != tag_directives_end; tag_directive ++) {
|
||||
// assert(tag_directive.handle)
|
||||
// assert(tag_directive.prefix)
|
||||
// if (!yaml_check_utf8(tag_directive.handle,
|
||||
// strlen((char *)tag_directive.handle)))
|
||||
// goto error
|
||||
// if (!yaml_check_utf8(tag_directive.prefix,
|
||||
// strlen((char *)tag_directive.prefix)))
|
||||
// goto error
|
||||
// value.handle = yaml_strdup(tag_directive.handle)
|
||||
// value.prefix = yaml_strdup(tag_directive.prefix)
|
||||
// if (!value.handle || !value.prefix) goto error
|
||||
// if (!PUSH(&context, tag_directives_copy, value))
|
||||
// goto error
|
||||
// value.handle = NULL
|
||||
// value.prefix = NULL
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// DOCUMENT_INIT(*document, nodes.start, nodes.end, version_directive_copy,
|
||||
// tag_directives_copy.start, tag_directives_copy.top,
|
||||
// start_implicit, end_implicit, mark, mark)
|
||||
//
|
||||
// return 1
|
||||
//
|
||||
//error:
|
||||
// STACK_DEL(&context, nodes)
|
||||
// yaml_free(version_directive_copy)
|
||||
// while (!STACK_EMPTY(&context, tag_directives_copy)) {
|
||||
// value yaml_tag_directive_t = POP(&context, tag_directives_copy)
|
||||
// yaml_free(value.handle)
|
||||
// yaml_free(value.prefix)
|
||||
// }
|
||||
// STACK_DEL(&context, tag_directives_copy)
|
||||
// yaml_free(value.handle)
|
||||
// yaml_free(value.prefix)
|
||||
//
|
||||
// return 0
|
||||
//}
|
||||
//
|
||||
///*
|
||||
// * Destroy a document object.
|
||||
// */
|
||||
//
|
||||
//YAML_DECLARE(void)
|
||||
//yaml_document_delete(document *yaml_document_t)
|
||||
//{
|
||||
// struct {
|
||||
// error yaml_error_type_t
|
||||
// } context
|
||||
// tag_directive *yaml_tag_directive_t
|
||||
//
|
||||
// context.error = YAML_NO_ERROR // Eliminate a compiler warning.
|
||||
//
|
||||
// assert(document) // Non-NULL document object is expected.
|
||||
//
|
||||
// while (!STACK_EMPTY(&context, document.nodes)) {
|
||||
// node yaml_node_t = POP(&context, document.nodes)
|
||||
// yaml_free(node.tag)
|
||||
// switch (node.type) {
|
||||
// case YAML_SCALAR_NODE:
|
||||
// yaml_free(node.data.scalar.value)
|
||||
// break
|
||||
// case YAML_SEQUENCE_NODE:
|
||||
// STACK_DEL(&context, node.data.sequence.items)
|
||||
// break
|
||||
// case YAML_MAPPING_NODE:
|
||||
// STACK_DEL(&context, node.data.mapping.pairs)
|
||||
// break
|
||||
// default:
|
||||
// assert(0) // Should not happen.
|
||||
// }
|
||||
// }
|
||||
// STACK_DEL(&context, document.nodes)
|
||||
//
|
||||
// yaml_free(document.version_directive)
|
||||
// for (tag_directive = document.tag_directives.start
|
||||
// tag_directive != document.tag_directives.end
|
||||
// tag_directive++) {
|
||||
// yaml_free(tag_directive.handle)
|
||||
// yaml_free(tag_directive.prefix)
|
||||
// }
|
||||
// yaml_free(document.tag_directives.start)
|
||||
//
|
||||
// memset(document, 0, sizeof(yaml_document_t))
|
||||
//}
|
||||
//
|
||||
///**
|
||||
// * Get a document node.
|
||||
// */
|
||||
//
|
||||
//YAML_DECLARE(yaml_node_t *)
|
||||
//yaml_document_get_node(document *yaml_document_t, index int)
|
||||
//{
|
||||
// assert(document) // Non-NULL document object is expected.
|
||||
//
|
||||
// if (index > 0 && document.nodes.start + index <= document.nodes.top) {
|
||||
// return document.nodes.start + index - 1
|
||||
// }
|
||||
// return NULL
|
||||
//}
|
||||
//
|
||||
///**
|
||||
// * Get the root object.
|
||||
// */
|
||||
//
|
||||
//YAML_DECLARE(yaml_node_t *)
|
||||
//yaml_document_get_root_node(document *yaml_document_t)
|
||||
//{
|
||||
// assert(document) // Non-NULL document object is expected.
|
||||
//
|
||||
// if (document.nodes.top != document.nodes.start) {
|
||||
// return document.nodes.start
|
||||
// }
|
||||
// return NULL
|
||||
//}
|
||||
//
|
||||
///*
|
||||
// * Add a scalar node to a document.
|
||||
// */
|
||||
//
|
||||
//YAML_DECLARE(int)
|
||||
//yaml_document_add_scalar(document *yaml_document_t,
|
||||
// tag *yaml_char_t, value *yaml_char_t, length int,
|
||||
// style yaml_scalar_style_t)
|
||||
//{
|
||||
// struct {
|
||||
// error yaml_error_type_t
|
||||
// } context
|
||||
// mark yaml_mark_t = { 0, 0, 0 }
|
||||
// tag_copy *yaml_char_t = NULL
|
||||
// value_copy *yaml_char_t = NULL
|
||||
// node yaml_node_t
|
||||
//
|
||||
// assert(document) // Non-NULL document object is expected.
|
||||
// assert(value) // Non-NULL value is expected.
|
||||
//
|
||||
// if (!tag) {
|
||||
// tag = (yaml_char_t *)YAML_DEFAULT_SCALAR_TAG
|
||||
// }
|
||||
//
|
||||
// if (!yaml_check_utf8(tag, strlen((char *)tag))) goto error
|
||||
// tag_copy = yaml_strdup(tag)
|
||||
// if (!tag_copy) goto error
|
||||
//
|
||||
// if (length < 0) {
|
||||
// length = strlen((char *)value)
|
||||
// }
|
||||
//
|
||||
// if (!yaml_check_utf8(value, length)) goto error
|
||||
// value_copy = yaml_malloc(length+1)
|
||||
// if (!value_copy) goto error
|
||||
// memcpy(value_copy, value, length)
|
||||
// value_copy[length] = '\0'
|
||||
//
|
||||
// SCALAR_NODE_INIT(node, tag_copy, value_copy, length, style, mark, mark)
|
||||
// if (!PUSH(&context, document.nodes, node)) goto error
|
||||
//
|
||||
// return document.nodes.top - document.nodes.start
|
||||
//
|
||||
//error:
|
||||
// yaml_free(tag_copy)
|
||||
// yaml_free(value_copy)
|
||||
//
|
||||
// return 0
|
||||
//}
|
||||
//
|
||||
///*
|
||||
// * Add a sequence node to a document.
|
||||
// */
|
||||
//
|
||||
//YAML_DECLARE(int)
|
||||
//yaml_document_add_sequence(document *yaml_document_t,
|
||||
// tag *yaml_char_t, style yaml_sequence_style_t)
|
||||
//{
|
||||
// struct {
|
||||
// error yaml_error_type_t
|
||||
// } context
|
||||
// mark yaml_mark_t = { 0, 0, 0 }
|
||||
// tag_copy *yaml_char_t = NULL
|
||||
// struct {
|
||||
// start *yaml_node_item_t
|
||||
// end *yaml_node_item_t
|
||||
// top *yaml_node_item_t
|
||||
// } items = { NULL, NULL, NULL }
|
||||
// node yaml_node_t
|
||||
//
|
||||
// assert(document) // Non-NULL document object is expected.
|
||||
//
|
||||
// if (!tag) {
|
||||
// tag = (yaml_char_t *)YAML_DEFAULT_SEQUENCE_TAG
|
||||
// }
|
||||
//
|
||||
// if (!yaml_check_utf8(tag, strlen((char *)tag))) goto error
|
||||
// tag_copy = yaml_strdup(tag)
|
||||
// if (!tag_copy) goto error
|
||||
//
|
||||
// if (!STACK_INIT(&context, items, INITIAL_STACK_SIZE)) goto error
|
||||
//
|
||||
// SEQUENCE_NODE_INIT(node, tag_copy, items.start, items.end,
|
||||
// style, mark, mark)
|
||||
// if (!PUSH(&context, document.nodes, node)) goto error
|
||||
//
|
||||
// return document.nodes.top - document.nodes.start
|
||||
//
|
||||
//error:
|
||||
// STACK_DEL(&context, items)
|
||||
// yaml_free(tag_copy)
|
||||
//
|
||||
// return 0
|
||||
//}
|
||||
//
|
||||
///*
|
||||
// * Add a mapping node to a document.
|
||||
// */
|
||||
//
|
||||
//YAML_DECLARE(int)
|
||||
//yaml_document_add_mapping(document *yaml_document_t,
|
||||
// tag *yaml_char_t, style yaml_mapping_style_t)
|
||||
//{
|
||||
// struct {
|
||||
// error yaml_error_type_t
|
||||
// } context
|
||||
// mark yaml_mark_t = { 0, 0, 0 }
|
||||
// tag_copy *yaml_char_t = NULL
|
||||
// struct {
|
||||
// start *yaml_node_pair_t
|
||||
// end *yaml_node_pair_t
|
||||
// top *yaml_node_pair_t
|
||||
// } pairs = { NULL, NULL, NULL }
|
||||
// node yaml_node_t
|
||||
//
|
||||
// assert(document) // Non-NULL document object is expected.
|
||||
//
|
||||
// if (!tag) {
|
||||
// tag = (yaml_char_t *)YAML_DEFAULT_MAPPING_TAG
|
||||
// }
|
||||
//
|
||||
// if (!yaml_check_utf8(tag, strlen((char *)tag))) goto error
|
||||
// tag_copy = yaml_strdup(tag)
|
||||
// if (!tag_copy) goto error
|
||||
//
|
||||
// if (!STACK_INIT(&context, pairs, INITIAL_STACK_SIZE)) goto error
|
||||
//
|
||||
// MAPPING_NODE_INIT(node, tag_copy, pairs.start, pairs.end,
|
||||
// style, mark, mark)
|
||||
// if (!PUSH(&context, document.nodes, node)) goto error
|
||||
//
|
||||
// return document.nodes.top - document.nodes.start
|
||||
//
|
||||
//error:
|
||||
// STACK_DEL(&context, pairs)
|
||||
// yaml_free(tag_copy)
|
||||
//
|
||||
// return 0
|
||||
//}
|
||||
//
|
||||
///*
|
||||
// * Append an item to a sequence node.
|
||||
// */
|
||||
//
|
||||
//YAML_DECLARE(int)
|
||||
//yaml_document_append_sequence_item(document *yaml_document_t,
|
||||
// sequence int, item int)
|
||||
//{
|
||||
// struct {
|
||||
// error yaml_error_type_t
|
||||
// } context
|
||||
//
|
||||
// assert(document) // Non-NULL document is required.
|
||||
// assert(sequence > 0
|
||||
// && document.nodes.start + sequence <= document.nodes.top)
|
||||
// // Valid sequence id is required.
|
||||
// assert(document.nodes.start[sequence-1].type == YAML_SEQUENCE_NODE)
|
||||
// // A sequence node is required.
|
||||
// assert(item > 0 && document.nodes.start + item <= document.nodes.top)
|
||||
// // Valid item id is required.
|
||||
//
|
||||
// if (!PUSH(&context,
|
||||
// document.nodes.start[sequence-1].data.sequence.items, item))
|
||||
// return 0
|
||||
//
|
||||
// return 1
|
||||
//}
|
||||
//
|
||||
///*
|
||||
// * Append a pair of a key and a value to a mapping node.
|
||||
// */
|
||||
//
|
||||
//YAML_DECLARE(int)
|
||||
//yaml_document_append_mapping_pair(document *yaml_document_t,
|
||||
// mapping int, key int, value int)
|
||||
//{
|
||||
// struct {
|
||||
// error yaml_error_type_t
|
||||
// } context
|
||||
//
|
||||
// pair yaml_node_pair_t
|
||||
//
|
||||
// assert(document) // Non-NULL document is required.
|
||||
// assert(mapping > 0
|
||||
// && document.nodes.start + mapping <= document.nodes.top)
|
||||
// // Valid mapping id is required.
|
||||
// assert(document.nodes.start[mapping-1].type == YAML_MAPPING_NODE)
|
||||
// // A mapping node is required.
|
||||
// assert(key > 0 && document.nodes.start + key <= document.nodes.top)
|
||||
// // Valid key id is required.
|
||||
// assert(value > 0 && document.nodes.start + value <= document.nodes.top)
|
||||
// // Valid value id is required.
|
||||
//
|
||||
// pair.key = key
|
||||
// pair.value = value
|
||||
//
|
||||
// if (!PUSH(&context,
|
||||
// document.nodes.start[mapping-1].data.mapping.pairs, pair))
|
||||
// return 0
|
||||
//
|
||||
// return 1
|
||||
//}
|
||||
//
|
||||
//
|
||||
-1000
File diff suppressed because it is too large
Load Diff
-2020
File diff suppressed because it is too large
Load Diff
-577
@@ -1,577 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2011-2019 Canonical Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package yaml
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
type encoder struct {
|
||||
emitter yaml_emitter_t
|
||||
event yaml_event_t
|
||||
out []byte
|
||||
flow bool
|
||||
indent int
|
||||
doneInit bool
|
||||
}
|
||||
|
||||
func newEncoder() *encoder {
|
||||
e := &encoder{}
|
||||
yaml_emitter_initialize(&e.emitter)
|
||||
yaml_emitter_set_output_string(&e.emitter, &e.out)
|
||||
yaml_emitter_set_unicode(&e.emitter, true)
|
||||
return e
|
||||
}
|
||||
|
||||
func newEncoderWithWriter(w io.Writer) *encoder {
|
||||
e := &encoder{}
|
||||
yaml_emitter_initialize(&e.emitter)
|
||||
yaml_emitter_set_output_writer(&e.emitter, w)
|
||||
yaml_emitter_set_unicode(&e.emitter, true)
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *encoder) init() {
|
||||
if e.doneInit {
|
||||
return
|
||||
}
|
||||
if e.indent == 0 {
|
||||
e.indent = 4
|
||||
}
|
||||
e.emitter.best_indent = e.indent
|
||||
yaml_stream_start_event_initialize(&e.event, yaml_UTF8_ENCODING)
|
||||
e.emit()
|
||||
e.doneInit = true
|
||||
}
|
||||
|
||||
func (e *encoder) finish() {
|
||||
e.emitter.open_ended = false
|
||||
yaml_stream_end_event_initialize(&e.event)
|
||||
e.emit()
|
||||
}
|
||||
|
||||
func (e *encoder) destroy() {
|
||||
yaml_emitter_delete(&e.emitter)
|
||||
}
|
||||
|
||||
func (e *encoder) emit() {
|
||||
// This will internally delete the e.event value.
|
||||
e.must(yaml_emitter_emit(&e.emitter, &e.event))
|
||||
}
|
||||
|
||||
func (e *encoder) must(ok bool) {
|
||||
if !ok {
|
||||
msg := e.emitter.problem
|
||||
if msg == "" {
|
||||
msg = "unknown problem generating YAML content"
|
||||
}
|
||||
failf("%s", msg)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *encoder) marshalDoc(tag string, in reflect.Value) {
|
||||
e.init()
|
||||
var node *Node
|
||||
if in.IsValid() {
|
||||
node, _ = in.Interface().(*Node)
|
||||
}
|
||||
if node != nil && node.Kind == DocumentNode {
|
||||
e.nodev(in)
|
||||
} else {
|
||||
yaml_document_start_event_initialize(&e.event, nil, nil, true)
|
||||
e.emit()
|
||||
e.marshal(tag, in)
|
||||
yaml_document_end_event_initialize(&e.event, true)
|
||||
e.emit()
|
||||
}
|
||||
}
|
||||
|
||||
func (e *encoder) marshal(tag string, in reflect.Value) {
|
||||
tag = shortTag(tag)
|
||||
if !in.IsValid() || in.Kind() == reflect.Ptr && in.IsNil() {
|
||||
e.nilv()
|
||||
return
|
||||
}
|
||||
iface := in.Interface()
|
||||
switch value := iface.(type) {
|
||||
case *Node:
|
||||
e.nodev(in)
|
||||
return
|
||||
case Node:
|
||||
if !in.CanAddr() {
|
||||
var n = reflect.New(in.Type()).Elem()
|
||||
n.Set(in)
|
||||
in = n
|
||||
}
|
||||
e.nodev(in.Addr())
|
||||
return
|
||||
case time.Time:
|
||||
e.timev(tag, in)
|
||||
return
|
||||
case *time.Time:
|
||||
e.timev(tag, in.Elem())
|
||||
return
|
||||
case time.Duration:
|
||||
e.stringv(tag, reflect.ValueOf(value.String()))
|
||||
return
|
||||
case Marshaler:
|
||||
v, err := value.MarshalYAML()
|
||||
if err != nil {
|
||||
fail(err)
|
||||
}
|
||||
if v == nil {
|
||||
e.nilv()
|
||||
return
|
||||
}
|
||||
e.marshal(tag, reflect.ValueOf(v))
|
||||
return
|
||||
case encoding.TextMarshaler:
|
||||
text, err := value.MarshalText()
|
||||
if err != nil {
|
||||
fail(err)
|
||||
}
|
||||
in = reflect.ValueOf(string(text))
|
||||
case nil:
|
||||
e.nilv()
|
||||
return
|
||||
}
|
||||
switch in.Kind() {
|
||||
case reflect.Interface:
|
||||
e.marshal(tag, in.Elem())
|
||||
case reflect.Map:
|
||||
e.mapv(tag, in)
|
||||
case reflect.Ptr:
|
||||
e.marshal(tag, in.Elem())
|
||||
case reflect.Struct:
|
||||
e.structv(tag, in)
|
||||
case reflect.Slice, reflect.Array:
|
||||
e.slicev(tag, in)
|
||||
case reflect.String:
|
||||
e.stringv(tag, in)
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
e.intv(tag, in)
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
e.uintv(tag, in)
|
||||
case reflect.Float32, reflect.Float64:
|
||||
e.floatv(tag, in)
|
||||
case reflect.Bool:
|
||||
e.boolv(tag, in)
|
||||
default:
|
||||
panic("cannot marshal type: " + in.Type().String())
|
||||
}
|
||||
}
|
||||
|
||||
func (e *encoder) mapv(tag string, in reflect.Value) {
|
||||
e.mappingv(tag, func() {
|
||||
keys := keyList(in.MapKeys())
|
||||
sort.Sort(keys)
|
||||
for _, k := range keys {
|
||||
e.marshal("", k)
|
||||
e.marshal("", in.MapIndex(k))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (e *encoder) fieldByIndex(v reflect.Value, index []int) (field reflect.Value) {
|
||||
for _, num := range index {
|
||||
for {
|
||||
if v.Kind() == reflect.Ptr {
|
||||
if v.IsNil() {
|
||||
return reflect.Value{}
|
||||
}
|
||||
v = v.Elem()
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
v = v.Field(num)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func (e *encoder) structv(tag string, in reflect.Value) {
|
||||
sinfo, err := getStructInfo(in.Type())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
e.mappingv(tag, func() {
|
||||
for _, info := range sinfo.FieldsList {
|
||||
var value reflect.Value
|
||||
if info.Inline == nil {
|
||||
value = in.Field(info.Num)
|
||||
} else {
|
||||
value = e.fieldByIndex(in, info.Inline)
|
||||
if !value.IsValid() {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if info.OmitEmpty && isZero(value) {
|
||||
continue
|
||||
}
|
||||
e.marshal("", reflect.ValueOf(info.Key))
|
||||
e.flow = info.Flow
|
||||
e.marshal("", value)
|
||||
}
|
||||
if sinfo.InlineMap >= 0 {
|
||||
m := in.Field(sinfo.InlineMap)
|
||||
if m.Len() > 0 {
|
||||
e.flow = false
|
||||
keys := keyList(m.MapKeys())
|
||||
sort.Sort(keys)
|
||||
for _, k := range keys {
|
||||
if _, found := sinfo.FieldsMap[k.String()]; found {
|
||||
panic(fmt.Sprintf("cannot have key %q in inlined map: conflicts with struct field", k.String()))
|
||||
}
|
||||
e.marshal("", k)
|
||||
e.flow = false
|
||||
e.marshal("", m.MapIndex(k))
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (e *encoder) mappingv(tag string, f func()) {
|
||||
implicit := tag == ""
|
||||
style := yaml_BLOCK_MAPPING_STYLE
|
||||
if e.flow {
|
||||
e.flow = false
|
||||
style = yaml_FLOW_MAPPING_STYLE
|
||||
}
|
||||
yaml_mapping_start_event_initialize(&e.event, nil, []byte(tag), implicit, style)
|
||||
e.emit()
|
||||
f()
|
||||
yaml_mapping_end_event_initialize(&e.event)
|
||||
e.emit()
|
||||
}
|
||||
|
||||
func (e *encoder) slicev(tag string, in reflect.Value) {
|
||||
implicit := tag == ""
|
||||
style := yaml_BLOCK_SEQUENCE_STYLE
|
||||
if e.flow {
|
||||
e.flow = false
|
||||
style = yaml_FLOW_SEQUENCE_STYLE
|
||||
}
|
||||
e.must(yaml_sequence_start_event_initialize(&e.event, nil, []byte(tag), implicit, style))
|
||||
e.emit()
|
||||
n := in.Len()
|
||||
for i := 0; i < n; i++ {
|
||||
e.marshal("", in.Index(i))
|
||||
}
|
||||
e.must(yaml_sequence_end_event_initialize(&e.event))
|
||||
e.emit()
|
||||
}
|
||||
|
||||
// isBase60 returns whether s is in base 60 notation as defined in YAML 1.1.
|
||||
//
|
||||
// The base 60 float notation in YAML 1.1 is a terrible idea and is unsupported
|
||||
// in YAML 1.2 and by this package, but these should be marshalled quoted for
|
||||
// the time being for compatibility with other parsers.
|
||||
func isBase60Float(s string) (result bool) {
|
||||
// Fast path.
|
||||
if s == "" {
|
||||
return false
|
||||
}
|
||||
c := s[0]
|
||||
if !(c == '+' || c == '-' || c >= '0' && c <= '9') || strings.IndexByte(s, ':') < 0 {
|
||||
return false
|
||||
}
|
||||
// Do the full match.
|
||||
return base60float.MatchString(s)
|
||||
}
|
||||
|
||||
// From http://yaml.org/type/float.html, except the regular expression there
|
||||
// is bogus. In practice parsers do not enforce the "\.[0-9_]*" suffix.
|
||||
var base60float = regexp.MustCompile(`^[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+(?:\.[0-9_]*)?$`)
|
||||
|
||||
// isOldBool returns whether s is bool notation as defined in YAML 1.1.
|
||||
//
|
||||
// We continue to force strings that YAML 1.1 would interpret as booleans to be
|
||||
// rendered as quotes strings so that the marshalled output valid for YAML 1.1
|
||||
// parsing.
|
||||
func isOldBool(s string) (result bool) {
|
||||
switch s {
|
||||
case "y", "Y", "yes", "Yes", "YES", "on", "On", "ON",
|
||||
"n", "N", "no", "No", "NO", "off", "Off", "OFF":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (e *encoder) stringv(tag string, in reflect.Value) {
|
||||
var style yaml_scalar_style_t
|
||||
s := in.String()
|
||||
canUsePlain := true
|
||||
switch {
|
||||
case !utf8.ValidString(s):
|
||||
if tag == binaryTag {
|
||||
failf("explicitly tagged !!binary data must be base64-encoded")
|
||||
}
|
||||
if tag != "" {
|
||||
failf("cannot marshal invalid UTF-8 data as %s", shortTag(tag))
|
||||
}
|
||||
// It can't be encoded directly as YAML so use a binary tag
|
||||
// and encode it as base64.
|
||||
tag = binaryTag
|
||||
s = encodeBase64(s)
|
||||
case tag == "":
|
||||
// Check to see if it would resolve to a specific
|
||||
// tag when encoded unquoted. If it doesn't,
|
||||
// there's no need to quote it.
|
||||
rtag, _ := resolve("", s)
|
||||
canUsePlain = rtag == strTag && !(isBase60Float(s) || isOldBool(s))
|
||||
}
|
||||
// Note: it's possible for user code to emit invalid YAML
|
||||
// if they explicitly specify a tag and a string containing
|
||||
// text that's incompatible with that tag.
|
||||
switch {
|
||||
case strings.Contains(s, "\n"):
|
||||
if e.flow {
|
||||
style = yaml_DOUBLE_QUOTED_SCALAR_STYLE
|
||||
} else {
|
||||
style = yaml_LITERAL_SCALAR_STYLE
|
||||
}
|
||||
case canUsePlain:
|
||||
style = yaml_PLAIN_SCALAR_STYLE
|
||||
default:
|
||||
style = yaml_DOUBLE_QUOTED_SCALAR_STYLE
|
||||
}
|
||||
e.emitScalar(s, "", tag, style, nil, nil, nil, nil)
|
||||
}
|
||||
|
||||
func (e *encoder) boolv(tag string, in reflect.Value) {
|
||||
var s string
|
||||
if in.Bool() {
|
||||
s = "true"
|
||||
} else {
|
||||
s = "false"
|
||||
}
|
||||
e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE, nil, nil, nil, nil)
|
||||
}
|
||||
|
||||
func (e *encoder) intv(tag string, in reflect.Value) {
|
||||
s := strconv.FormatInt(in.Int(), 10)
|
||||
e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE, nil, nil, nil, nil)
|
||||
}
|
||||
|
||||
func (e *encoder) uintv(tag string, in reflect.Value) {
|
||||
s := strconv.FormatUint(in.Uint(), 10)
|
||||
e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE, nil, nil, nil, nil)
|
||||
}
|
||||
|
||||
func (e *encoder) timev(tag string, in reflect.Value) {
|
||||
t := in.Interface().(time.Time)
|
||||
s := t.Format(time.RFC3339Nano)
|
||||
e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE, nil, nil, nil, nil)
|
||||
}
|
||||
|
||||
func (e *encoder) floatv(tag string, in reflect.Value) {
|
||||
// Issue #352: When formatting, use the precision of the underlying value
|
||||
precision := 64
|
||||
if in.Kind() == reflect.Float32 {
|
||||
precision = 32
|
||||
}
|
||||
|
||||
s := strconv.FormatFloat(in.Float(), 'g', -1, precision)
|
||||
switch s {
|
||||
case "+Inf":
|
||||
s = ".inf"
|
||||
case "-Inf":
|
||||
s = "-.inf"
|
||||
case "NaN":
|
||||
s = ".nan"
|
||||
}
|
||||
e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE, nil, nil, nil, nil)
|
||||
}
|
||||
|
||||
func (e *encoder) nilv() {
|
||||
e.emitScalar("null", "", "", yaml_PLAIN_SCALAR_STYLE, nil, nil, nil, nil)
|
||||
}
|
||||
|
||||
func (e *encoder) emitScalar(value, anchor, tag string, style yaml_scalar_style_t, head, line, foot, tail []byte) {
|
||||
// TODO Kill this function. Replace all initialize calls by their underlining Go literals.
|
||||
implicit := tag == ""
|
||||
if !implicit {
|
||||
tag = longTag(tag)
|
||||
}
|
||||
e.must(yaml_scalar_event_initialize(&e.event, []byte(anchor), []byte(tag), []byte(value), implicit, implicit, style))
|
||||
e.event.head_comment = head
|
||||
e.event.line_comment = line
|
||||
e.event.foot_comment = foot
|
||||
e.event.tail_comment = tail
|
||||
e.emit()
|
||||
}
|
||||
|
||||
func (e *encoder) nodev(in reflect.Value) {
|
||||
e.node(in.Interface().(*Node), "")
|
||||
}
|
||||
|
||||
func (e *encoder) node(node *Node, tail string) {
|
||||
// Zero nodes behave as nil.
|
||||
if node.Kind == 0 && node.IsZero() {
|
||||
e.nilv()
|
||||
return
|
||||
}
|
||||
|
||||
// If the tag was not explicitly requested, and dropping it won't change the
|
||||
// implicit tag of the value, don't include it in the presentation.
|
||||
var tag = node.Tag
|
||||
var stag = shortTag(tag)
|
||||
var forceQuoting bool
|
||||
if tag != "" && node.Style&TaggedStyle == 0 {
|
||||
if node.Kind == ScalarNode {
|
||||
if stag == strTag && node.Style&(SingleQuotedStyle|DoubleQuotedStyle|LiteralStyle|FoldedStyle) != 0 {
|
||||
tag = ""
|
||||
} else {
|
||||
rtag, _ := resolve("", node.Value)
|
||||
if rtag == stag {
|
||||
tag = ""
|
||||
} else if stag == strTag {
|
||||
tag = ""
|
||||
forceQuoting = true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
var rtag string
|
||||
switch node.Kind {
|
||||
case MappingNode:
|
||||
rtag = mapTag
|
||||
case SequenceNode:
|
||||
rtag = seqTag
|
||||
}
|
||||
if rtag == stag {
|
||||
tag = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch node.Kind {
|
||||
case DocumentNode:
|
||||
yaml_document_start_event_initialize(&e.event, nil, nil, true)
|
||||
e.event.head_comment = []byte(node.HeadComment)
|
||||
e.emit()
|
||||
for _, node := range node.Content {
|
||||
e.node(node, "")
|
||||
}
|
||||
yaml_document_end_event_initialize(&e.event, true)
|
||||
e.event.foot_comment = []byte(node.FootComment)
|
||||
e.emit()
|
||||
|
||||
case SequenceNode:
|
||||
style := yaml_BLOCK_SEQUENCE_STYLE
|
||||
if node.Style&FlowStyle != 0 {
|
||||
style = yaml_FLOW_SEQUENCE_STYLE
|
||||
}
|
||||
e.must(yaml_sequence_start_event_initialize(&e.event, []byte(node.Anchor), []byte(longTag(tag)), tag == "", style))
|
||||
e.event.head_comment = []byte(node.HeadComment)
|
||||
e.emit()
|
||||
for _, node := range node.Content {
|
||||
e.node(node, "")
|
||||
}
|
||||
e.must(yaml_sequence_end_event_initialize(&e.event))
|
||||
e.event.line_comment = []byte(node.LineComment)
|
||||
e.event.foot_comment = []byte(node.FootComment)
|
||||
e.emit()
|
||||
|
||||
case MappingNode:
|
||||
style := yaml_BLOCK_MAPPING_STYLE
|
||||
if node.Style&FlowStyle != 0 {
|
||||
style = yaml_FLOW_MAPPING_STYLE
|
||||
}
|
||||
yaml_mapping_start_event_initialize(&e.event, []byte(node.Anchor), []byte(longTag(tag)), tag == "", style)
|
||||
e.event.tail_comment = []byte(tail)
|
||||
e.event.head_comment = []byte(node.HeadComment)
|
||||
e.emit()
|
||||
|
||||
// The tail logic below moves the foot comment of prior keys to the following key,
|
||||
// since the value for each key may be a nested structure and the foot needs to be
|
||||
// processed only the entirety of the value is streamed. The last tail is processed
|
||||
// with the mapping end event.
|
||||
var tail string
|
||||
for i := 0; i+1 < len(node.Content); i += 2 {
|
||||
k := node.Content[i]
|
||||
foot := k.FootComment
|
||||
if foot != "" {
|
||||
kopy := *k
|
||||
kopy.FootComment = ""
|
||||
k = &kopy
|
||||
}
|
||||
e.node(k, tail)
|
||||
tail = foot
|
||||
|
||||
v := node.Content[i+1]
|
||||
e.node(v, "")
|
||||
}
|
||||
|
||||
yaml_mapping_end_event_initialize(&e.event)
|
||||
e.event.tail_comment = []byte(tail)
|
||||
e.event.line_comment = []byte(node.LineComment)
|
||||
e.event.foot_comment = []byte(node.FootComment)
|
||||
e.emit()
|
||||
|
||||
case AliasNode:
|
||||
yaml_alias_event_initialize(&e.event, []byte(node.Value))
|
||||
e.event.head_comment = []byte(node.HeadComment)
|
||||
e.event.line_comment = []byte(node.LineComment)
|
||||
e.event.foot_comment = []byte(node.FootComment)
|
||||
e.emit()
|
||||
|
||||
case ScalarNode:
|
||||
value := node.Value
|
||||
if !utf8.ValidString(value) {
|
||||
if stag == binaryTag {
|
||||
failf("explicitly tagged !!binary data must be base64-encoded")
|
||||
}
|
||||
if stag != "" {
|
||||
failf("cannot marshal invalid UTF-8 data as %s", stag)
|
||||
}
|
||||
// It can't be encoded directly as YAML so use a binary tag
|
||||
// and encode it as base64.
|
||||
tag = binaryTag
|
||||
value = encodeBase64(value)
|
||||
}
|
||||
|
||||
style := yaml_PLAIN_SCALAR_STYLE
|
||||
switch {
|
||||
case node.Style&DoubleQuotedStyle != 0:
|
||||
style = yaml_DOUBLE_QUOTED_SCALAR_STYLE
|
||||
case node.Style&SingleQuotedStyle != 0:
|
||||
style = yaml_SINGLE_QUOTED_SCALAR_STYLE
|
||||
case node.Style&LiteralStyle != 0:
|
||||
style = yaml_LITERAL_SCALAR_STYLE
|
||||
case node.Style&FoldedStyle != 0:
|
||||
style = yaml_FOLDED_SCALAR_STYLE
|
||||
case strings.Contains(value, "\n"):
|
||||
style = yaml_LITERAL_SCALAR_STYLE
|
||||
case forceQuoting:
|
||||
style = yaml_DOUBLE_QUOTED_SCALAR_STYLE
|
||||
}
|
||||
|
||||
e.emitScalar(value, node.Anchor, tag, style, []byte(node.HeadComment), []byte(node.LineComment), []byte(node.FootComment), []byte(tail))
|
||||
default:
|
||||
failf("cannot encode node with unknown kind %d", node.Kind)
|
||||
}
|
||||
}
|
||||
-1258
File diff suppressed because it is too large
Load Diff
-434
@@ -1,434 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2011-2019 Canonical Ltd
|
||||
// Copyright (c) 2006-2010 Kirill Simonov
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
// this software and associated documentation files (the "Software"), to deal in
|
||||
// the Software without restriction, including without limitation the rights to
|
||||
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
// of the Software, and to permit persons to whom the Software is furnished to do
|
||||
// so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
package yaml
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
// Set the reader error and return 0.
|
||||
func yaml_parser_set_reader_error(parser *yaml_parser_t, problem string, offset int, value int) bool {
|
||||
parser.error = yaml_READER_ERROR
|
||||
parser.problem = problem
|
||||
parser.problem_offset = offset
|
||||
parser.problem_value = value
|
||||
return false
|
||||
}
|
||||
|
||||
// Byte order marks.
|
||||
const (
|
||||
bom_UTF8 = "\xef\xbb\xbf"
|
||||
bom_UTF16LE = "\xff\xfe"
|
||||
bom_UTF16BE = "\xfe\xff"
|
||||
)
|
||||
|
||||
// Determine the input stream encoding by checking the BOM symbol. If no BOM is
|
||||
// found, the UTF-8 encoding is assumed. Return 1 on success, 0 on failure.
|
||||
func yaml_parser_determine_encoding(parser *yaml_parser_t) bool {
|
||||
// Ensure that we had enough bytes in the raw buffer.
|
||||
for !parser.eof && len(parser.raw_buffer)-parser.raw_buffer_pos < 3 {
|
||||
if !yaml_parser_update_raw_buffer(parser) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Determine the encoding.
|
||||
buf := parser.raw_buffer
|
||||
pos := parser.raw_buffer_pos
|
||||
avail := len(buf) - pos
|
||||
if avail >= 2 && buf[pos] == bom_UTF16LE[0] && buf[pos+1] == bom_UTF16LE[1] {
|
||||
parser.encoding = yaml_UTF16LE_ENCODING
|
||||
parser.raw_buffer_pos += 2
|
||||
parser.offset += 2
|
||||
} else if avail >= 2 && buf[pos] == bom_UTF16BE[0] && buf[pos+1] == bom_UTF16BE[1] {
|
||||
parser.encoding = yaml_UTF16BE_ENCODING
|
||||
parser.raw_buffer_pos += 2
|
||||
parser.offset += 2
|
||||
} else if avail >= 3 && buf[pos] == bom_UTF8[0] && buf[pos+1] == bom_UTF8[1] && buf[pos+2] == bom_UTF8[2] {
|
||||
parser.encoding = yaml_UTF8_ENCODING
|
||||
parser.raw_buffer_pos += 3
|
||||
parser.offset += 3
|
||||
} else {
|
||||
parser.encoding = yaml_UTF8_ENCODING
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Update the raw buffer.
|
||||
func yaml_parser_update_raw_buffer(parser *yaml_parser_t) bool {
|
||||
size_read := 0
|
||||
|
||||
// Return if the raw buffer is full.
|
||||
if parser.raw_buffer_pos == 0 && len(parser.raw_buffer) == cap(parser.raw_buffer) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Return on EOF.
|
||||
if parser.eof {
|
||||
return true
|
||||
}
|
||||
|
||||
// Move the remaining bytes in the raw buffer to the beginning.
|
||||
if parser.raw_buffer_pos > 0 && parser.raw_buffer_pos < len(parser.raw_buffer) {
|
||||
copy(parser.raw_buffer, parser.raw_buffer[parser.raw_buffer_pos:])
|
||||
}
|
||||
parser.raw_buffer = parser.raw_buffer[:len(parser.raw_buffer)-parser.raw_buffer_pos]
|
||||
parser.raw_buffer_pos = 0
|
||||
|
||||
// Call the read handler to fill the buffer.
|
||||
size_read, err := parser.read_handler(parser, parser.raw_buffer[len(parser.raw_buffer):cap(parser.raw_buffer)])
|
||||
parser.raw_buffer = parser.raw_buffer[:len(parser.raw_buffer)+size_read]
|
||||
if err == io.EOF {
|
||||
parser.eof = true
|
||||
} else if err != nil {
|
||||
return yaml_parser_set_reader_error(parser, "input error: "+err.Error(), parser.offset, -1)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Ensure that the buffer contains at least `length` characters.
|
||||
// Return true on success, false on failure.
|
||||
//
|
||||
// The length is supposed to be significantly less that the buffer size.
|
||||
func yaml_parser_update_buffer(parser *yaml_parser_t, length int) bool {
|
||||
if parser.read_handler == nil {
|
||||
panic("read handler must be set")
|
||||
}
|
||||
|
||||
// [Go] This function was changed to guarantee the requested length size at EOF.
|
||||
// The fact we need to do this is pretty awful, but the description above implies
|
||||
// for that to be the case, and there are tests
|
||||
|
||||
// If the EOF flag is set and the raw buffer is empty, do nothing.
|
||||
if parser.eof && parser.raw_buffer_pos == len(parser.raw_buffer) {
|
||||
// [Go] ACTUALLY! Read the documentation of this function above.
|
||||
// This is just broken. To return true, we need to have the
|
||||
// given length in the buffer. Not doing that means every single
|
||||
// check that calls this function to make sure the buffer has a
|
||||
// given length is Go) panicking; or C) accessing invalid memory.
|
||||
//return true
|
||||
}
|
||||
|
||||
// Return if the buffer contains enough characters.
|
||||
if parser.unread >= length {
|
||||
return true
|
||||
}
|
||||
|
||||
// Determine the input encoding if it is not known yet.
|
||||
if parser.encoding == yaml_ANY_ENCODING {
|
||||
if !yaml_parser_determine_encoding(parser) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Move the unread characters to the beginning of the buffer.
|
||||
buffer_len := len(parser.buffer)
|
||||
if parser.buffer_pos > 0 && parser.buffer_pos < buffer_len {
|
||||
copy(parser.buffer, parser.buffer[parser.buffer_pos:])
|
||||
buffer_len -= parser.buffer_pos
|
||||
parser.buffer_pos = 0
|
||||
} else if parser.buffer_pos == buffer_len {
|
||||
buffer_len = 0
|
||||
parser.buffer_pos = 0
|
||||
}
|
||||
|
||||
// Open the whole buffer for writing, and cut it before returning.
|
||||
parser.buffer = parser.buffer[:cap(parser.buffer)]
|
||||
|
||||
// Fill the buffer until it has enough characters.
|
||||
first := true
|
||||
for parser.unread < length {
|
||||
|
||||
// Fill the raw buffer if necessary.
|
||||
if !first || parser.raw_buffer_pos == len(parser.raw_buffer) {
|
||||
if !yaml_parser_update_raw_buffer(parser) {
|
||||
parser.buffer = parser.buffer[:buffer_len]
|
||||
return false
|
||||
}
|
||||
}
|
||||
first = false
|
||||
|
||||
// Decode the raw buffer.
|
||||
inner:
|
||||
for parser.raw_buffer_pos != len(parser.raw_buffer) {
|
||||
var value rune
|
||||
var width int
|
||||
|
||||
raw_unread := len(parser.raw_buffer) - parser.raw_buffer_pos
|
||||
|
||||
// Decode the next character.
|
||||
switch parser.encoding {
|
||||
case yaml_UTF8_ENCODING:
|
||||
// Decode a UTF-8 character. Check RFC 3629
|
||||
// (http://www.ietf.org/rfc/rfc3629.txt) for more details.
|
||||
//
|
||||
// The following table (taken from the RFC) is used for
|
||||
// decoding.
|
||||
//
|
||||
// Char. number range | UTF-8 octet sequence
|
||||
// (hexadecimal) | (binary)
|
||||
// --------------------+------------------------------------
|
||||
// 0000 0000-0000 007F | 0xxxxxxx
|
||||
// 0000 0080-0000 07FF | 110xxxxx 10xxxxxx
|
||||
// 0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
|
||||
// 0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
|
||||
//
|
||||
// Additionally, the characters in the range 0xD800-0xDFFF
|
||||
// are prohibited as they are reserved for use with UTF-16
|
||||
// surrogate pairs.
|
||||
|
||||
// Determine the length of the UTF-8 sequence.
|
||||
octet := parser.raw_buffer[parser.raw_buffer_pos]
|
||||
switch {
|
||||
case octet&0x80 == 0x00:
|
||||
width = 1
|
||||
case octet&0xE0 == 0xC0:
|
||||
width = 2
|
||||
case octet&0xF0 == 0xE0:
|
||||
width = 3
|
||||
case octet&0xF8 == 0xF0:
|
||||
width = 4
|
||||
default:
|
||||
// The leading octet is invalid.
|
||||
return yaml_parser_set_reader_error(parser,
|
||||
"invalid leading UTF-8 octet",
|
||||
parser.offset, int(octet))
|
||||
}
|
||||
|
||||
// Check if the raw buffer contains an incomplete character.
|
||||
if width > raw_unread {
|
||||
if parser.eof {
|
||||
return yaml_parser_set_reader_error(parser,
|
||||
"incomplete UTF-8 octet sequence",
|
||||
parser.offset, -1)
|
||||
}
|
||||
break inner
|
||||
}
|
||||
|
||||
// Decode the leading octet.
|
||||
switch {
|
||||
case octet&0x80 == 0x00:
|
||||
value = rune(octet & 0x7F)
|
||||
case octet&0xE0 == 0xC0:
|
||||
value = rune(octet & 0x1F)
|
||||
case octet&0xF0 == 0xE0:
|
||||
value = rune(octet & 0x0F)
|
||||
case octet&0xF8 == 0xF0:
|
||||
value = rune(octet & 0x07)
|
||||
default:
|
||||
value = 0
|
||||
}
|
||||
|
||||
// Check and decode the trailing octets.
|
||||
for k := 1; k < width; k++ {
|
||||
octet = parser.raw_buffer[parser.raw_buffer_pos+k]
|
||||
|
||||
// Check if the octet is valid.
|
||||
if (octet & 0xC0) != 0x80 {
|
||||
return yaml_parser_set_reader_error(parser,
|
||||
"invalid trailing UTF-8 octet",
|
||||
parser.offset+k, int(octet))
|
||||
}
|
||||
|
||||
// Decode the octet.
|
||||
value = (value << 6) + rune(octet&0x3F)
|
||||
}
|
||||
|
||||
// Check the length of the sequence against the value.
|
||||
switch {
|
||||
case width == 1:
|
||||
case width == 2 && value >= 0x80:
|
||||
case width == 3 && value >= 0x800:
|
||||
case width == 4 && value >= 0x10000:
|
||||
default:
|
||||
return yaml_parser_set_reader_error(parser,
|
||||
"invalid length of a UTF-8 sequence",
|
||||
parser.offset, -1)
|
||||
}
|
||||
|
||||
// Check the range of the value.
|
||||
if value >= 0xD800 && value <= 0xDFFF || value > 0x10FFFF {
|
||||
return yaml_parser_set_reader_error(parser,
|
||||
"invalid Unicode character",
|
||||
parser.offset, int(value))
|
||||
}
|
||||
|
||||
case yaml_UTF16LE_ENCODING, yaml_UTF16BE_ENCODING:
|
||||
var low, high int
|
||||
if parser.encoding == yaml_UTF16LE_ENCODING {
|
||||
low, high = 0, 1
|
||||
} else {
|
||||
low, high = 1, 0
|
||||
}
|
||||
|
||||
// The UTF-16 encoding is not as simple as one might
|
||||
// naively think. Check RFC 2781
|
||||
// (http://www.ietf.org/rfc/rfc2781.txt).
|
||||
//
|
||||
// Normally, two subsequent bytes describe a Unicode
|
||||
// character. However a special technique (called a
|
||||
// surrogate pair) is used for specifying character
|
||||
// values larger than 0xFFFF.
|
||||
//
|
||||
// A surrogate pair consists of two pseudo-characters:
|
||||
// high surrogate area (0xD800-0xDBFF)
|
||||
// low surrogate area (0xDC00-0xDFFF)
|
||||
//
|
||||
// The following formulas are used for decoding
|
||||
// and encoding characters using surrogate pairs:
|
||||
//
|
||||
// U = U' + 0x10000 (0x01 00 00 <= U <= 0x10 FF FF)
|
||||
// U' = yyyyyyyyyyxxxxxxxxxx (0 <= U' <= 0x0F FF FF)
|
||||
// W1 = 110110yyyyyyyyyy
|
||||
// W2 = 110111xxxxxxxxxx
|
||||
//
|
||||
// where U is the character value, W1 is the high surrogate
|
||||
// area, W2 is the low surrogate area.
|
||||
|
||||
// Check for incomplete UTF-16 character.
|
||||
if raw_unread < 2 {
|
||||
if parser.eof {
|
||||
return yaml_parser_set_reader_error(parser,
|
||||
"incomplete UTF-16 character",
|
||||
parser.offset, -1)
|
||||
}
|
||||
break inner
|
||||
}
|
||||
|
||||
// Get the character.
|
||||
value = rune(parser.raw_buffer[parser.raw_buffer_pos+low]) +
|
||||
(rune(parser.raw_buffer[parser.raw_buffer_pos+high]) << 8)
|
||||
|
||||
// Check for unexpected low surrogate area.
|
||||
if value&0xFC00 == 0xDC00 {
|
||||
return yaml_parser_set_reader_error(parser,
|
||||
"unexpected low surrogate area",
|
||||
parser.offset, int(value))
|
||||
}
|
||||
|
||||
// Check for a high surrogate area.
|
||||
if value&0xFC00 == 0xD800 {
|
||||
width = 4
|
||||
|
||||
// Check for incomplete surrogate pair.
|
||||
if raw_unread < 4 {
|
||||
if parser.eof {
|
||||
return yaml_parser_set_reader_error(parser,
|
||||
"incomplete UTF-16 surrogate pair",
|
||||
parser.offset, -1)
|
||||
}
|
||||
break inner
|
||||
}
|
||||
|
||||
// Get the next character.
|
||||
value2 := rune(parser.raw_buffer[parser.raw_buffer_pos+low+2]) +
|
||||
(rune(parser.raw_buffer[parser.raw_buffer_pos+high+2]) << 8)
|
||||
|
||||
// Check for a low surrogate area.
|
||||
if value2&0xFC00 != 0xDC00 {
|
||||
return yaml_parser_set_reader_error(parser,
|
||||
"expected low surrogate area",
|
||||
parser.offset+2, int(value2))
|
||||
}
|
||||
|
||||
// Generate the value of the surrogate pair.
|
||||
value = 0x10000 + ((value & 0x3FF) << 10) + (value2 & 0x3FF)
|
||||
} else {
|
||||
width = 2
|
||||
}
|
||||
|
||||
default:
|
||||
panic("impossible")
|
||||
}
|
||||
|
||||
// Check if the character is in the allowed range:
|
||||
// #x9 | #xA | #xD | [#x20-#x7E] (8 bit)
|
||||
// | #x85 | [#xA0-#xD7FF] | [#xE000-#xFFFD] (16 bit)
|
||||
// | [#x10000-#x10FFFF] (32 bit)
|
||||
switch {
|
||||
case value == 0x09:
|
||||
case value == 0x0A:
|
||||
case value == 0x0D:
|
||||
case value >= 0x20 && value <= 0x7E:
|
||||
case value == 0x85:
|
||||
case value >= 0xA0 && value <= 0xD7FF:
|
||||
case value >= 0xE000 && value <= 0xFFFD:
|
||||
case value >= 0x10000 && value <= 0x10FFFF:
|
||||
default:
|
||||
return yaml_parser_set_reader_error(parser,
|
||||
"control characters are not allowed",
|
||||
parser.offset, int(value))
|
||||
}
|
||||
|
||||
// Move the raw pointers.
|
||||
parser.raw_buffer_pos += width
|
||||
parser.offset += width
|
||||
|
||||
// Finally put the character into the buffer.
|
||||
if value <= 0x7F {
|
||||
// 0000 0000-0000 007F . 0xxxxxxx
|
||||
parser.buffer[buffer_len+0] = byte(value)
|
||||
buffer_len += 1
|
||||
} else if value <= 0x7FF {
|
||||
// 0000 0080-0000 07FF . 110xxxxx 10xxxxxx
|
||||
parser.buffer[buffer_len+0] = byte(0xC0 + (value >> 6))
|
||||
parser.buffer[buffer_len+1] = byte(0x80 + (value & 0x3F))
|
||||
buffer_len += 2
|
||||
} else if value <= 0xFFFF {
|
||||
// 0000 0800-0000 FFFF . 1110xxxx 10xxxxxx 10xxxxxx
|
||||
parser.buffer[buffer_len+0] = byte(0xE0 + (value >> 12))
|
||||
parser.buffer[buffer_len+1] = byte(0x80 + ((value >> 6) & 0x3F))
|
||||
parser.buffer[buffer_len+2] = byte(0x80 + (value & 0x3F))
|
||||
buffer_len += 3
|
||||
} else {
|
||||
// 0001 0000-0010 FFFF . 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
|
||||
parser.buffer[buffer_len+0] = byte(0xF0 + (value >> 18))
|
||||
parser.buffer[buffer_len+1] = byte(0x80 + ((value >> 12) & 0x3F))
|
||||
parser.buffer[buffer_len+2] = byte(0x80 + ((value >> 6) & 0x3F))
|
||||
parser.buffer[buffer_len+3] = byte(0x80 + (value & 0x3F))
|
||||
buffer_len += 4
|
||||
}
|
||||
|
||||
parser.unread++
|
||||
}
|
||||
|
||||
// On EOF, put NUL into the buffer and return.
|
||||
if parser.eof {
|
||||
parser.buffer[buffer_len] = 0
|
||||
buffer_len++
|
||||
parser.unread++
|
||||
break
|
||||
}
|
||||
}
|
||||
// [Go] Read the documentation of this function above. To return true,
|
||||
// we need to have the given length in the buffer. Not doing that means
|
||||
// every single check that calls this function to make sure the buffer
|
||||
// has a given length is Go) panicking; or C) accessing invalid memory.
|
||||
// This happens here due to the EOF above breaking early.
|
||||
for buffer_len < length {
|
||||
parser.buffer[buffer_len] = 0
|
||||
buffer_len++
|
||||
}
|
||||
parser.buffer = parser.buffer[:buffer_len]
|
||||
return true
|
||||
}
|
||||
-326
@@ -1,326 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2011-2019 Canonical Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package yaml
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"math"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type resolveMapItem struct {
|
||||
value interface{}
|
||||
tag string
|
||||
}
|
||||
|
||||
var resolveTable = make([]byte, 256)
|
||||
var resolveMap = make(map[string]resolveMapItem)
|
||||
|
||||
func init() {
|
||||
t := resolveTable
|
||||
t[int('+')] = 'S' // Sign
|
||||
t[int('-')] = 'S'
|
||||
for _, c := range "0123456789" {
|
||||
t[int(c)] = 'D' // Digit
|
||||
}
|
||||
for _, c := range "yYnNtTfFoO~" {
|
||||
t[int(c)] = 'M' // In map
|
||||
}
|
||||
t[int('.')] = '.' // Float (potentially in map)
|
||||
|
||||
var resolveMapList = []struct {
|
||||
v interface{}
|
||||
tag string
|
||||
l []string
|
||||
}{
|
||||
{true, boolTag, []string{"true", "True", "TRUE"}},
|
||||
{false, boolTag, []string{"false", "False", "FALSE"}},
|
||||
{nil, nullTag, []string{"", "~", "null", "Null", "NULL"}},
|
||||
{math.NaN(), floatTag, []string{".nan", ".NaN", ".NAN"}},
|
||||
{math.Inf(+1), floatTag, []string{".inf", ".Inf", ".INF"}},
|
||||
{math.Inf(+1), floatTag, []string{"+.inf", "+.Inf", "+.INF"}},
|
||||
{math.Inf(-1), floatTag, []string{"-.inf", "-.Inf", "-.INF"}},
|
||||
{"<<", mergeTag, []string{"<<"}},
|
||||
}
|
||||
|
||||
m := resolveMap
|
||||
for _, item := range resolveMapList {
|
||||
for _, s := range item.l {
|
||||
m[s] = resolveMapItem{item.v, item.tag}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
nullTag = "!!null"
|
||||
boolTag = "!!bool"
|
||||
strTag = "!!str"
|
||||
intTag = "!!int"
|
||||
floatTag = "!!float"
|
||||
timestampTag = "!!timestamp"
|
||||
seqTag = "!!seq"
|
||||
mapTag = "!!map"
|
||||
binaryTag = "!!binary"
|
||||
mergeTag = "!!merge"
|
||||
)
|
||||
|
||||
var longTags = make(map[string]string)
|
||||
var shortTags = make(map[string]string)
|
||||
|
||||
func init() {
|
||||
for _, stag := range []string{nullTag, boolTag, strTag, intTag, floatTag, timestampTag, seqTag, mapTag, binaryTag, mergeTag} {
|
||||
ltag := longTag(stag)
|
||||
longTags[stag] = ltag
|
||||
shortTags[ltag] = stag
|
||||
}
|
||||
}
|
||||
|
||||
const longTagPrefix = "tag:yaml.org,2002:"
|
||||
|
||||
func shortTag(tag string) string {
|
||||
if strings.HasPrefix(tag, longTagPrefix) {
|
||||
if stag, ok := shortTags[tag]; ok {
|
||||
return stag
|
||||
}
|
||||
return "!!" + tag[len(longTagPrefix):]
|
||||
}
|
||||
return tag
|
||||
}
|
||||
|
||||
func longTag(tag string) string {
|
||||
if strings.HasPrefix(tag, "!!") {
|
||||
if ltag, ok := longTags[tag]; ok {
|
||||
return ltag
|
||||
}
|
||||
return longTagPrefix + tag[2:]
|
||||
}
|
||||
return tag
|
||||
}
|
||||
|
||||
func resolvableTag(tag string) bool {
|
||||
switch tag {
|
||||
case "", strTag, boolTag, intTag, floatTag, nullTag, timestampTag:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var yamlStyleFloat = regexp.MustCompile(`^[-+]?(\.[0-9]+|[0-9]+(\.[0-9]*)?)([eE][-+]?[0-9]+)?$`)
|
||||
|
||||
func resolve(tag string, in string) (rtag string, out interface{}) {
|
||||
tag = shortTag(tag)
|
||||
if !resolvableTag(tag) {
|
||||
return tag, in
|
||||
}
|
||||
|
||||
defer func() {
|
||||
switch tag {
|
||||
case "", rtag, strTag, binaryTag:
|
||||
return
|
||||
case floatTag:
|
||||
if rtag == intTag {
|
||||
switch v := out.(type) {
|
||||
case int64:
|
||||
rtag = floatTag
|
||||
out = float64(v)
|
||||
return
|
||||
case int:
|
||||
rtag = floatTag
|
||||
out = float64(v)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
failf("cannot decode %s `%s` as a %s", shortTag(rtag), in, shortTag(tag))
|
||||
}()
|
||||
|
||||
// Any data is accepted as a !!str or !!binary.
|
||||
// Otherwise, the prefix is enough of a hint about what it might be.
|
||||
hint := byte('N')
|
||||
if in != "" {
|
||||
hint = resolveTable[in[0]]
|
||||
}
|
||||
if hint != 0 && tag != strTag && tag != binaryTag {
|
||||
// Handle things we can lookup in a map.
|
||||
if item, ok := resolveMap[in]; ok {
|
||||
return item.tag, item.value
|
||||
}
|
||||
|
||||
// Base 60 floats are a bad idea, were dropped in YAML 1.2, and
|
||||
// are purposefully unsupported here. They're still quoted on
|
||||
// the way out for compatibility with other parser, though.
|
||||
|
||||
switch hint {
|
||||
case 'M':
|
||||
// We've already checked the map above.
|
||||
|
||||
case '.':
|
||||
// Not in the map, so maybe a normal float.
|
||||
floatv, err := strconv.ParseFloat(in, 64)
|
||||
if err == nil {
|
||||
return floatTag, floatv
|
||||
}
|
||||
|
||||
case 'D', 'S':
|
||||
// Int, float, or timestamp.
|
||||
// Only try values as a timestamp if the value is unquoted or there's an explicit
|
||||
// !!timestamp tag.
|
||||
if tag == "" || tag == timestampTag {
|
||||
t, ok := parseTimestamp(in)
|
||||
if ok {
|
||||
return timestampTag, t
|
||||
}
|
||||
}
|
||||
|
||||
plain := strings.Replace(in, "_", "", -1)
|
||||
intv, err := strconv.ParseInt(plain, 0, 64)
|
||||
if err == nil {
|
||||
if intv == int64(int(intv)) {
|
||||
return intTag, int(intv)
|
||||
} else {
|
||||
return intTag, intv
|
||||
}
|
||||
}
|
||||
uintv, err := strconv.ParseUint(plain, 0, 64)
|
||||
if err == nil {
|
||||
return intTag, uintv
|
||||
}
|
||||
if yamlStyleFloat.MatchString(plain) {
|
||||
floatv, err := strconv.ParseFloat(plain, 64)
|
||||
if err == nil {
|
||||
return floatTag, floatv
|
||||
}
|
||||
}
|
||||
if strings.HasPrefix(plain, "0b") {
|
||||
intv, err := strconv.ParseInt(plain[2:], 2, 64)
|
||||
if err == nil {
|
||||
if intv == int64(int(intv)) {
|
||||
return intTag, int(intv)
|
||||
} else {
|
||||
return intTag, intv
|
||||
}
|
||||
}
|
||||
uintv, err := strconv.ParseUint(plain[2:], 2, 64)
|
||||
if err == nil {
|
||||
return intTag, uintv
|
||||
}
|
||||
} else if strings.HasPrefix(plain, "-0b") {
|
||||
intv, err := strconv.ParseInt("-"+plain[3:], 2, 64)
|
||||
if err == nil {
|
||||
if true || intv == int64(int(intv)) {
|
||||
return intTag, int(intv)
|
||||
} else {
|
||||
return intTag, intv
|
||||
}
|
||||
}
|
||||
}
|
||||
// Octals as introduced in version 1.2 of the spec.
|
||||
// Octals from the 1.1 spec, spelled as 0777, are still
|
||||
// decoded by default in v3 as well for compatibility.
|
||||
// May be dropped in v4 depending on how usage evolves.
|
||||
if strings.HasPrefix(plain, "0o") {
|
||||
intv, err := strconv.ParseInt(plain[2:], 8, 64)
|
||||
if err == nil {
|
||||
if intv == int64(int(intv)) {
|
||||
return intTag, int(intv)
|
||||
} else {
|
||||
return intTag, intv
|
||||
}
|
||||
}
|
||||
uintv, err := strconv.ParseUint(plain[2:], 8, 64)
|
||||
if err == nil {
|
||||
return intTag, uintv
|
||||
}
|
||||
} else if strings.HasPrefix(plain, "-0o") {
|
||||
intv, err := strconv.ParseInt("-"+plain[3:], 8, 64)
|
||||
if err == nil {
|
||||
if true || intv == int64(int(intv)) {
|
||||
return intTag, int(intv)
|
||||
} else {
|
||||
return intTag, intv
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
panic("internal error: missing handler for resolver table: " + string(rune(hint)) + " (with " + in + ")")
|
||||
}
|
||||
}
|
||||
return strTag, in
|
||||
}
|
||||
|
||||
// encodeBase64 encodes s as base64 that is broken up into multiple lines
|
||||
// as appropriate for the resulting length.
|
||||
func encodeBase64(s string) string {
|
||||
const lineLen = 70
|
||||
encLen := base64.StdEncoding.EncodedLen(len(s))
|
||||
lines := encLen/lineLen + 1
|
||||
buf := make([]byte, encLen*2+lines)
|
||||
in := buf[0:encLen]
|
||||
out := buf[encLen:]
|
||||
base64.StdEncoding.Encode(in, []byte(s))
|
||||
k := 0
|
||||
for i := 0; i < len(in); i += lineLen {
|
||||
j := i + lineLen
|
||||
if j > len(in) {
|
||||
j = len(in)
|
||||
}
|
||||
k += copy(out[k:], in[i:j])
|
||||
if lines > 1 {
|
||||
out[k] = '\n'
|
||||
k++
|
||||
}
|
||||
}
|
||||
return string(out[:k])
|
||||
}
|
||||
|
||||
// This is a subset of the formats allowed by the regular expression
|
||||
// defined at http://yaml.org/type/timestamp.html.
|
||||
var allowedTimestampFormats = []string{
|
||||
"2006-1-2T15:4:5.999999999Z07:00", // RCF3339Nano with short date fields.
|
||||
"2006-1-2t15:4:5.999999999Z07:00", // RFC3339Nano with short date fields and lower-case "t".
|
||||
"2006-1-2 15:4:5.999999999", // space separated with no time zone
|
||||
"2006-1-2", // date only
|
||||
// Notable exception: time.Parse cannot handle: "2001-12-14 21:59:43.10 -5"
|
||||
// from the set of examples.
|
||||
}
|
||||
|
||||
// parseTimestamp parses s as a timestamp string and
|
||||
// returns the timestamp and reports whether it succeeded.
|
||||
// Timestamp formats are defined at http://yaml.org/type/timestamp.html
|
||||
func parseTimestamp(s string) (time.Time, bool) {
|
||||
// TODO write code to check all the formats supported by
|
||||
// http://yaml.org/type/timestamp.html instead of using time.Parse.
|
||||
|
||||
// Quick check: all date formats start with YYYY-.
|
||||
i := 0
|
||||
for ; i < len(s); i++ {
|
||||
if c := s[i]; c < '0' || c > '9' {
|
||||
break
|
||||
}
|
||||
}
|
||||
if i != 4 || i == len(s) || s[i] != '-' {
|
||||
return time.Time{}, false
|
||||
}
|
||||
for _, format := range allowedTimestampFormats {
|
||||
if t, err := time.Parse(format, s); err == nil {
|
||||
return t, true
|
||||
}
|
||||
}
|
||||
return time.Time{}, false
|
||||
}
|
||||
-3038
File diff suppressed because it is too large
Load Diff
-134
@@ -1,134 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2011-2019 Canonical Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package yaml
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
type keyList []reflect.Value
|
||||
|
||||
func (l keyList) Len() int { return len(l) }
|
||||
func (l keyList) Swap(i, j int) { l[i], l[j] = l[j], l[i] }
|
||||
func (l keyList) Less(i, j int) bool {
|
||||
a := l[i]
|
||||
b := l[j]
|
||||
ak := a.Kind()
|
||||
bk := b.Kind()
|
||||
for (ak == reflect.Interface || ak == reflect.Ptr) && !a.IsNil() {
|
||||
a = a.Elem()
|
||||
ak = a.Kind()
|
||||
}
|
||||
for (bk == reflect.Interface || bk == reflect.Ptr) && !b.IsNil() {
|
||||
b = b.Elem()
|
||||
bk = b.Kind()
|
||||
}
|
||||
af, aok := keyFloat(a)
|
||||
bf, bok := keyFloat(b)
|
||||
if aok && bok {
|
||||
if af != bf {
|
||||
return af < bf
|
||||
}
|
||||
if ak != bk {
|
||||
return ak < bk
|
||||
}
|
||||
return numLess(a, b)
|
||||
}
|
||||
if ak != reflect.String || bk != reflect.String {
|
||||
return ak < bk
|
||||
}
|
||||
ar, br := []rune(a.String()), []rune(b.String())
|
||||
digits := false
|
||||
for i := 0; i < len(ar) && i < len(br); i++ {
|
||||
if ar[i] == br[i] {
|
||||
digits = unicode.IsDigit(ar[i])
|
||||
continue
|
||||
}
|
||||
al := unicode.IsLetter(ar[i])
|
||||
bl := unicode.IsLetter(br[i])
|
||||
if al && bl {
|
||||
return ar[i] < br[i]
|
||||
}
|
||||
if al || bl {
|
||||
if digits {
|
||||
return al
|
||||
} else {
|
||||
return bl
|
||||
}
|
||||
}
|
||||
var ai, bi int
|
||||
var an, bn int64
|
||||
if ar[i] == '0' || br[i] == '0' {
|
||||
for j := i - 1; j >= 0 && unicode.IsDigit(ar[j]); j-- {
|
||||
if ar[j] != '0' {
|
||||
an = 1
|
||||
bn = 1
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
for ai = i; ai < len(ar) && unicode.IsDigit(ar[ai]); ai++ {
|
||||
an = an*10 + int64(ar[ai]-'0')
|
||||
}
|
||||
for bi = i; bi < len(br) && unicode.IsDigit(br[bi]); bi++ {
|
||||
bn = bn*10 + int64(br[bi]-'0')
|
||||
}
|
||||
if an != bn {
|
||||
return an < bn
|
||||
}
|
||||
if ai != bi {
|
||||
return ai < bi
|
||||
}
|
||||
return ar[i] < br[i]
|
||||
}
|
||||
return len(ar) < len(br)
|
||||
}
|
||||
|
||||
// keyFloat returns a float value for v if it is a number/bool
|
||||
// and whether it is a number/bool or not.
|
||||
func keyFloat(v reflect.Value) (f float64, ok bool) {
|
||||
switch v.Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return float64(v.Int()), true
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return v.Float(), true
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
return float64(v.Uint()), true
|
||||
case reflect.Bool:
|
||||
if v.Bool() {
|
||||
return 1, true
|
||||
}
|
||||
return 0, true
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// numLess returns whether a < b.
|
||||
// a and b must necessarily have the same kind.
|
||||
func numLess(a, b reflect.Value) bool {
|
||||
switch a.Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return a.Int() < b.Int()
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return a.Float() < b.Float()
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
return a.Uint() < b.Uint()
|
||||
case reflect.Bool:
|
||||
return !a.Bool() && b.Bool()
|
||||
}
|
||||
panic("not a number")
|
||||
}
|
||||
-48
@@ -1,48 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2011-2019 Canonical Ltd
|
||||
// Copyright (c) 2006-2010 Kirill Simonov
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
// this software and associated documentation files (the "Software"), to deal in
|
||||
// the Software without restriction, including without limitation the rights to
|
||||
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
// of the Software, and to permit persons to whom the Software is furnished to do
|
||||
// so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
package yaml
|
||||
|
||||
// Set the writer error and return false.
|
||||
func yaml_emitter_set_writer_error(emitter *yaml_emitter_t, problem string) bool {
|
||||
emitter.error = yaml_WRITER_ERROR
|
||||
emitter.problem = problem
|
||||
return false
|
||||
}
|
||||
|
||||
// Flush the output buffer.
|
||||
func yaml_emitter_flush(emitter *yaml_emitter_t) bool {
|
||||
if emitter.write_handler == nil {
|
||||
panic("write handler not set")
|
||||
}
|
||||
|
||||
// Check if the buffer is empty.
|
||||
if emitter.buffer_pos == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
if err := emitter.write_handler(emitter, emitter.buffer[:emitter.buffer_pos]); err != nil {
|
||||
return yaml_emitter_set_writer_error(emitter, "write error: "+err.Error())
|
||||
}
|
||||
emitter.buffer_pos = 0
|
||||
return true
|
||||
}
|
||||
-698
@@ -1,698 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2011-2019 Canonical Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package yaml implements YAML support for the Go language.
|
||||
//
|
||||
// Source code and other details for the project are available at GitHub:
|
||||
//
|
||||
// https://github.com/go-yaml/yaml
|
||||
//
|
||||
package yaml
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// The Unmarshaler interface may be implemented by types to customize their
|
||||
// behavior when being unmarshaled from a YAML document.
|
||||
type Unmarshaler interface {
|
||||
UnmarshalYAML(value *Node) error
|
||||
}
|
||||
|
||||
type obsoleteUnmarshaler interface {
|
||||
UnmarshalYAML(unmarshal func(interface{}) error) error
|
||||
}
|
||||
|
||||
// The Marshaler interface may be implemented by types to customize their
|
||||
// behavior when being marshaled into a YAML document. The returned value
|
||||
// is marshaled in place of the original value implementing Marshaler.
|
||||
//
|
||||
// If an error is returned by MarshalYAML, the marshaling procedure stops
|
||||
// and returns with the provided error.
|
||||
type Marshaler interface {
|
||||
MarshalYAML() (interface{}, error)
|
||||
}
|
||||
|
||||
// Unmarshal decodes the first document found within the in byte slice
|
||||
// and assigns decoded values into the out value.
|
||||
//
|
||||
// Maps and pointers (to a struct, string, int, etc) are accepted as out
|
||||
// values. If an internal pointer within a struct is not initialized,
|
||||
// the yaml package will initialize it if necessary for unmarshalling
|
||||
// the provided data. The out parameter must not be nil.
|
||||
//
|
||||
// The type of the decoded values should be compatible with the respective
|
||||
// values in out. If one or more values cannot be decoded due to a type
|
||||
// mismatches, decoding continues partially until the end of the YAML
|
||||
// content, and a *yaml.TypeError is returned with details for all
|
||||
// missed values.
|
||||
//
|
||||
// Struct fields are only unmarshalled if they are exported (have an
|
||||
// upper case first letter), and are unmarshalled using the field name
|
||||
// lowercased as the default key. Custom keys may be defined via the
|
||||
// "yaml" name in the field tag: the content preceding the first comma
|
||||
// is used as the key, and the following comma-separated options are
|
||||
// used to tweak the marshalling process (see Marshal).
|
||||
// Conflicting names result in a runtime error.
|
||||
//
|
||||
// For example:
|
||||
//
|
||||
// type T struct {
|
||||
// F int `yaml:"a,omitempty"`
|
||||
// B int
|
||||
// }
|
||||
// var t T
|
||||
// yaml.Unmarshal([]byte("a: 1\nb: 2"), &t)
|
||||
//
|
||||
// See the documentation of Marshal for the format of tags and a list of
|
||||
// supported tag options.
|
||||
//
|
||||
func Unmarshal(in []byte, out interface{}) (err error) {
|
||||
return unmarshal(in, out, false)
|
||||
}
|
||||
|
||||
// A Decoder reads and decodes YAML values from an input stream.
|
||||
type Decoder struct {
|
||||
parser *parser
|
||||
knownFields bool
|
||||
}
|
||||
|
||||
// NewDecoder returns a new decoder that reads from r.
|
||||
//
|
||||
// The decoder introduces its own buffering and may read
|
||||
// data from r beyond the YAML values requested.
|
||||
func NewDecoder(r io.Reader) *Decoder {
|
||||
return &Decoder{
|
||||
parser: newParserFromReader(r),
|
||||
}
|
||||
}
|
||||
|
||||
// KnownFields ensures that the keys in decoded mappings to
|
||||
// exist as fields in the struct being decoded into.
|
||||
func (dec *Decoder) KnownFields(enable bool) {
|
||||
dec.knownFields = enable
|
||||
}
|
||||
|
||||
// Decode reads the next YAML-encoded value from its input
|
||||
// and stores it in the value pointed to by v.
|
||||
//
|
||||
// See the documentation for Unmarshal for details about the
|
||||
// conversion of YAML into a Go value.
|
||||
func (dec *Decoder) Decode(v interface{}) (err error) {
|
||||
d := newDecoder()
|
||||
d.knownFields = dec.knownFields
|
||||
defer handleErr(&err)
|
||||
node := dec.parser.parse()
|
||||
if node == nil {
|
||||
return io.EOF
|
||||
}
|
||||
out := reflect.ValueOf(v)
|
||||
if out.Kind() == reflect.Ptr && !out.IsNil() {
|
||||
out = out.Elem()
|
||||
}
|
||||
d.unmarshal(node, out)
|
||||
if len(d.terrors) > 0 {
|
||||
return &TypeError{d.terrors}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decode decodes the node and stores its data into the value pointed to by v.
|
||||
//
|
||||
// See the documentation for Unmarshal for details about the
|
||||
// conversion of YAML into a Go value.
|
||||
func (n *Node) Decode(v interface{}) (err error) {
|
||||
d := newDecoder()
|
||||
defer handleErr(&err)
|
||||
out := reflect.ValueOf(v)
|
||||
if out.Kind() == reflect.Ptr && !out.IsNil() {
|
||||
out = out.Elem()
|
||||
}
|
||||
d.unmarshal(n, out)
|
||||
if len(d.terrors) > 0 {
|
||||
return &TypeError{d.terrors}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func unmarshal(in []byte, out interface{}, strict bool) (err error) {
|
||||
defer handleErr(&err)
|
||||
d := newDecoder()
|
||||
p := newParser(in)
|
||||
defer p.destroy()
|
||||
node := p.parse()
|
||||
if node != nil {
|
||||
v := reflect.ValueOf(out)
|
||||
if v.Kind() == reflect.Ptr && !v.IsNil() {
|
||||
v = v.Elem()
|
||||
}
|
||||
d.unmarshal(node, v)
|
||||
}
|
||||
if len(d.terrors) > 0 {
|
||||
return &TypeError{d.terrors}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Marshal serializes the value provided into a YAML document. The structure
|
||||
// of the generated document will reflect the structure of the value itself.
|
||||
// Maps and pointers (to struct, string, int, etc) are accepted as the in value.
|
||||
//
|
||||
// Struct fields are only marshalled if they are exported (have an upper case
|
||||
// first letter), and are marshalled using the field name lowercased as the
|
||||
// default key. Custom keys may be defined via the "yaml" name in the field
|
||||
// tag: the content preceding the first comma is used as the key, and the
|
||||
// following comma-separated options are used to tweak the marshalling process.
|
||||
// Conflicting names result in a runtime error.
|
||||
//
|
||||
// The field tag format accepted is:
|
||||
//
|
||||
// `(...) yaml:"[<key>][,<flag1>[,<flag2>]]" (...)`
|
||||
//
|
||||
// The following flags are currently supported:
|
||||
//
|
||||
// omitempty Only include the field if it's not set to the zero
|
||||
// value for the type or to empty slices or maps.
|
||||
// Zero valued structs will be omitted if all their public
|
||||
// fields are zero, unless they implement an IsZero
|
||||
// method (see the IsZeroer interface type), in which
|
||||
// case the field will be excluded if IsZero returns true.
|
||||
//
|
||||
// flow Marshal using a flow style (useful for structs,
|
||||
// sequences and maps).
|
||||
//
|
||||
// inline Inline the field, which must be a struct or a map,
|
||||
// causing all of its fields or keys to be processed as if
|
||||
// they were part of the outer struct. For maps, keys must
|
||||
// not conflict with the yaml keys of other struct fields.
|
||||
//
|
||||
// In addition, if the key is "-", the field is ignored.
|
||||
//
|
||||
// For example:
|
||||
//
|
||||
// type T struct {
|
||||
// F int `yaml:"a,omitempty"`
|
||||
// B int
|
||||
// }
|
||||
// yaml.Marshal(&T{B: 2}) // Returns "b: 2\n"
|
||||
// yaml.Marshal(&T{F: 1}} // Returns "a: 1\nb: 0\n"
|
||||
//
|
||||
func Marshal(in interface{}) (out []byte, err error) {
|
||||
defer handleErr(&err)
|
||||
e := newEncoder()
|
||||
defer e.destroy()
|
||||
e.marshalDoc("", reflect.ValueOf(in))
|
||||
e.finish()
|
||||
out = e.out
|
||||
return
|
||||
}
|
||||
|
||||
// An Encoder writes YAML values to an output stream.
|
||||
type Encoder struct {
|
||||
encoder *encoder
|
||||
}
|
||||
|
||||
// NewEncoder returns a new encoder that writes to w.
|
||||
// The Encoder should be closed after use to flush all data
|
||||
// to w.
|
||||
func NewEncoder(w io.Writer) *Encoder {
|
||||
return &Encoder{
|
||||
encoder: newEncoderWithWriter(w),
|
||||
}
|
||||
}
|
||||
|
||||
// Encode writes the YAML encoding of v to the stream.
|
||||
// If multiple items are encoded to the stream, the
|
||||
// second and subsequent document will be preceded
|
||||
// with a "---" document separator, but the first will not.
|
||||
//
|
||||
// See the documentation for Marshal for details about the conversion of Go
|
||||
// values to YAML.
|
||||
func (e *Encoder) Encode(v interface{}) (err error) {
|
||||
defer handleErr(&err)
|
||||
e.encoder.marshalDoc("", reflect.ValueOf(v))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Encode encodes value v and stores its representation in n.
|
||||
//
|
||||
// See the documentation for Marshal for details about the
|
||||
// conversion of Go values into YAML.
|
||||
func (n *Node) Encode(v interface{}) (err error) {
|
||||
defer handleErr(&err)
|
||||
e := newEncoder()
|
||||
defer e.destroy()
|
||||
e.marshalDoc("", reflect.ValueOf(v))
|
||||
e.finish()
|
||||
p := newParser(e.out)
|
||||
p.textless = true
|
||||
defer p.destroy()
|
||||
doc := p.parse()
|
||||
*n = *doc.Content[0]
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetIndent changes the used indentation used when encoding.
|
||||
func (e *Encoder) SetIndent(spaces int) {
|
||||
if spaces < 0 {
|
||||
panic("yaml: cannot indent to a negative number of spaces")
|
||||
}
|
||||
e.encoder.indent = spaces
|
||||
}
|
||||
|
||||
// Close closes the encoder by writing any remaining data.
|
||||
// It does not write a stream terminating string "...".
|
||||
func (e *Encoder) Close() (err error) {
|
||||
defer handleErr(&err)
|
||||
e.encoder.finish()
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleErr(err *error) {
|
||||
if v := recover(); v != nil {
|
||||
if e, ok := v.(yamlError); ok {
|
||||
*err = e.err
|
||||
} else {
|
||||
panic(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type yamlError struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func fail(err error) {
|
||||
panic(yamlError{err})
|
||||
}
|
||||
|
||||
func failf(format string, args ...interface{}) {
|
||||
panic(yamlError{fmt.Errorf("yaml: "+format, args...)})
|
||||
}
|
||||
|
||||
// A TypeError is returned by Unmarshal when one or more fields in
|
||||
// the YAML document cannot be properly decoded into the requested
|
||||
// types. When this error is returned, the value is still
|
||||
// unmarshaled partially.
|
||||
type TypeError struct {
|
||||
Errors []string
|
||||
}
|
||||
|
||||
func (e *TypeError) Error() string {
|
||||
return fmt.Sprintf("yaml: unmarshal errors:\n %s", strings.Join(e.Errors, "\n "))
|
||||
}
|
||||
|
||||
type Kind uint32
|
||||
|
||||
const (
|
||||
DocumentNode Kind = 1 << iota
|
||||
SequenceNode
|
||||
MappingNode
|
||||
ScalarNode
|
||||
AliasNode
|
||||
)
|
||||
|
||||
type Style uint32
|
||||
|
||||
const (
|
||||
TaggedStyle Style = 1 << iota
|
||||
DoubleQuotedStyle
|
||||
SingleQuotedStyle
|
||||
LiteralStyle
|
||||
FoldedStyle
|
||||
FlowStyle
|
||||
)
|
||||
|
||||
// Node represents an element in the YAML document hierarchy. While documents
|
||||
// are typically encoded and decoded into higher level types, such as structs
|
||||
// and maps, Node is an intermediate representation that allows detailed
|
||||
// control over the content being decoded or encoded.
|
||||
//
|
||||
// It's worth noting that although Node offers access into details such as
|
||||
// line numbers, colums, and comments, the content when re-encoded will not
|
||||
// have its original textual representation preserved. An effort is made to
|
||||
// render the data plesantly, and to preserve comments near the data they
|
||||
// describe, though.
|
||||
//
|
||||
// Values that make use of the Node type interact with the yaml package in the
|
||||
// same way any other type would do, by encoding and decoding yaml data
|
||||
// directly or indirectly into them.
|
||||
//
|
||||
// For example:
|
||||
//
|
||||
// var person struct {
|
||||
// Name string
|
||||
// Address yaml.Node
|
||||
// }
|
||||
// err := yaml.Unmarshal(data, &person)
|
||||
//
|
||||
// Or by itself:
|
||||
//
|
||||
// var person Node
|
||||
// err := yaml.Unmarshal(data, &person)
|
||||
//
|
||||
type Node struct {
|
||||
// Kind defines whether the node is a document, a mapping, a sequence,
|
||||
// a scalar value, or an alias to another node. The specific data type of
|
||||
// scalar nodes may be obtained via the ShortTag and LongTag methods.
|
||||
Kind Kind
|
||||
|
||||
// Style allows customizing the apperance of the node in the tree.
|
||||
Style Style
|
||||
|
||||
// Tag holds the YAML tag defining the data type for the value.
|
||||
// When decoding, this field will always be set to the resolved tag,
|
||||
// even when it wasn't explicitly provided in the YAML content.
|
||||
// When encoding, if this field is unset the value type will be
|
||||
// implied from the node properties, and if it is set, it will only
|
||||
// be serialized into the representation if TaggedStyle is used or
|
||||
// the implicit tag diverges from the provided one.
|
||||
Tag string
|
||||
|
||||
// Value holds the unescaped and unquoted represenation of the value.
|
||||
Value string
|
||||
|
||||
// Anchor holds the anchor name for this node, which allows aliases to point to it.
|
||||
Anchor string
|
||||
|
||||
// Alias holds the node that this alias points to. Only valid when Kind is AliasNode.
|
||||
Alias *Node
|
||||
|
||||
// Content holds contained nodes for documents, mappings, and sequences.
|
||||
Content []*Node
|
||||
|
||||
// HeadComment holds any comments in the lines preceding the node and
|
||||
// not separated by an empty line.
|
||||
HeadComment string
|
||||
|
||||
// LineComment holds any comments at the end of the line where the node is in.
|
||||
LineComment string
|
||||
|
||||
// FootComment holds any comments following the node and before empty lines.
|
||||
FootComment string
|
||||
|
||||
// Line and Column hold the node position in the decoded YAML text.
|
||||
// These fields are not respected when encoding the node.
|
||||
Line int
|
||||
Column int
|
||||
}
|
||||
|
||||
// IsZero returns whether the node has all of its fields unset.
|
||||
func (n *Node) IsZero() bool {
|
||||
return n.Kind == 0 && n.Style == 0 && n.Tag == "" && n.Value == "" && n.Anchor == "" && n.Alias == nil && n.Content == nil &&
|
||||
n.HeadComment == "" && n.LineComment == "" && n.FootComment == "" && n.Line == 0 && n.Column == 0
|
||||
}
|
||||
|
||||
|
||||
// LongTag returns the long form of the tag that indicates the data type for
|
||||
// the node. If the Tag field isn't explicitly defined, one will be computed
|
||||
// based on the node properties.
|
||||
func (n *Node) LongTag() string {
|
||||
return longTag(n.ShortTag())
|
||||
}
|
||||
|
||||
// ShortTag returns the short form of the YAML tag that indicates data type for
|
||||
// the node. If the Tag field isn't explicitly defined, one will be computed
|
||||
// based on the node properties.
|
||||
func (n *Node) ShortTag() string {
|
||||
if n.indicatedString() {
|
||||
return strTag
|
||||
}
|
||||
if n.Tag == "" || n.Tag == "!" {
|
||||
switch n.Kind {
|
||||
case MappingNode:
|
||||
return mapTag
|
||||
case SequenceNode:
|
||||
return seqTag
|
||||
case AliasNode:
|
||||
if n.Alias != nil {
|
||||
return n.Alias.ShortTag()
|
||||
}
|
||||
case ScalarNode:
|
||||
tag, _ := resolve("", n.Value)
|
||||
return tag
|
||||
case 0:
|
||||
// Special case to make the zero value convenient.
|
||||
if n.IsZero() {
|
||||
return nullTag
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
return shortTag(n.Tag)
|
||||
}
|
||||
|
||||
func (n *Node) indicatedString() bool {
|
||||
return n.Kind == ScalarNode &&
|
||||
(shortTag(n.Tag) == strTag ||
|
||||
(n.Tag == "" || n.Tag == "!") && n.Style&(SingleQuotedStyle|DoubleQuotedStyle|LiteralStyle|FoldedStyle) != 0)
|
||||
}
|
||||
|
||||
// SetString is a convenience function that sets the node to a string value
|
||||
// and defines its style in a pleasant way depending on its content.
|
||||
func (n *Node) SetString(s string) {
|
||||
n.Kind = ScalarNode
|
||||
if utf8.ValidString(s) {
|
||||
n.Value = s
|
||||
n.Tag = strTag
|
||||
} else {
|
||||
n.Value = encodeBase64(s)
|
||||
n.Tag = binaryTag
|
||||
}
|
||||
if strings.Contains(n.Value, "\n") {
|
||||
n.Style = LiteralStyle
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// Maintain a mapping of keys to structure field indexes
|
||||
|
||||
// The code in this section was copied from mgo/bson.
|
||||
|
||||
// structInfo holds details for the serialization of fields of
|
||||
// a given struct.
|
||||
type structInfo struct {
|
||||
FieldsMap map[string]fieldInfo
|
||||
FieldsList []fieldInfo
|
||||
|
||||
// InlineMap is the number of the field in the struct that
|
||||
// contains an ,inline map, or -1 if there's none.
|
||||
InlineMap int
|
||||
|
||||
// InlineUnmarshalers holds indexes to inlined fields that
|
||||
// contain unmarshaler values.
|
||||
InlineUnmarshalers [][]int
|
||||
}
|
||||
|
||||
type fieldInfo struct {
|
||||
Key string
|
||||
Num int
|
||||
OmitEmpty bool
|
||||
Flow bool
|
||||
// Id holds the unique field identifier, so we can cheaply
|
||||
// check for field duplicates without maintaining an extra map.
|
||||
Id int
|
||||
|
||||
// Inline holds the field index if the field is part of an inlined struct.
|
||||
Inline []int
|
||||
}
|
||||
|
||||
var structMap = make(map[reflect.Type]*structInfo)
|
||||
var fieldMapMutex sync.RWMutex
|
||||
var unmarshalerType reflect.Type
|
||||
|
||||
func init() {
|
||||
var v Unmarshaler
|
||||
unmarshalerType = reflect.ValueOf(&v).Elem().Type()
|
||||
}
|
||||
|
||||
func getStructInfo(st reflect.Type) (*structInfo, error) {
|
||||
fieldMapMutex.RLock()
|
||||
sinfo, found := structMap[st]
|
||||
fieldMapMutex.RUnlock()
|
||||
if found {
|
||||
return sinfo, nil
|
||||
}
|
||||
|
||||
n := st.NumField()
|
||||
fieldsMap := make(map[string]fieldInfo)
|
||||
fieldsList := make([]fieldInfo, 0, n)
|
||||
inlineMap := -1
|
||||
inlineUnmarshalers := [][]int(nil)
|
||||
for i := 0; i != n; i++ {
|
||||
field := st.Field(i)
|
||||
if field.PkgPath != "" && !field.Anonymous {
|
||||
continue // Private field
|
||||
}
|
||||
|
||||
info := fieldInfo{Num: i}
|
||||
|
||||
tag := field.Tag.Get("yaml")
|
||||
if tag == "" && strings.Index(string(field.Tag), ":") < 0 {
|
||||
tag = string(field.Tag)
|
||||
}
|
||||
if tag == "-" {
|
||||
continue
|
||||
}
|
||||
|
||||
inline := false
|
||||
fields := strings.Split(tag, ",")
|
||||
if len(fields) > 1 {
|
||||
for _, flag := range fields[1:] {
|
||||
switch flag {
|
||||
case "omitempty":
|
||||
info.OmitEmpty = true
|
||||
case "flow":
|
||||
info.Flow = true
|
||||
case "inline":
|
||||
inline = true
|
||||
default:
|
||||
return nil, errors.New(fmt.Sprintf("unsupported flag %q in tag %q of type %s", flag, tag, st))
|
||||
}
|
||||
}
|
||||
tag = fields[0]
|
||||
}
|
||||
|
||||
if inline {
|
||||
switch field.Type.Kind() {
|
||||
case reflect.Map:
|
||||
if inlineMap >= 0 {
|
||||
return nil, errors.New("multiple ,inline maps in struct " + st.String())
|
||||
}
|
||||
if field.Type.Key() != reflect.TypeOf("") {
|
||||
return nil, errors.New("option ,inline needs a map with string keys in struct " + st.String())
|
||||
}
|
||||
inlineMap = info.Num
|
||||
case reflect.Struct, reflect.Ptr:
|
||||
ftype := field.Type
|
||||
for ftype.Kind() == reflect.Ptr {
|
||||
ftype = ftype.Elem()
|
||||
}
|
||||
if ftype.Kind() != reflect.Struct {
|
||||
return nil, errors.New("option ,inline may only be used on a struct or map field")
|
||||
}
|
||||
if reflect.PtrTo(ftype).Implements(unmarshalerType) {
|
||||
inlineUnmarshalers = append(inlineUnmarshalers, []int{i})
|
||||
} else {
|
||||
sinfo, err := getStructInfo(ftype)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, index := range sinfo.InlineUnmarshalers {
|
||||
inlineUnmarshalers = append(inlineUnmarshalers, append([]int{i}, index...))
|
||||
}
|
||||
for _, finfo := range sinfo.FieldsList {
|
||||
if _, found := fieldsMap[finfo.Key]; found {
|
||||
msg := "duplicated key '" + finfo.Key + "' in struct " + st.String()
|
||||
return nil, errors.New(msg)
|
||||
}
|
||||
if finfo.Inline == nil {
|
||||
finfo.Inline = []int{i, finfo.Num}
|
||||
} else {
|
||||
finfo.Inline = append([]int{i}, finfo.Inline...)
|
||||
}
|
||||
finfo.Id = len(fieldsList)
|
||||
fieldsMap[finfo.Key] = finfo
|
||||
fieldsList = append(fieldsList, finfo)
|
||||
}
|
||||
}
|
||||
default:
|
||||
return nil, errors.New("option ,inline may only be used on a struct or map field")
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if tag != "" {
|
||||
info.Key = tag
|
||||
} else {
|
||||
info.Key = strings.ToLower(field.Name)
|
||||
}
|
||||
|
||||
if _, found = fieldsMap[info.Key]; found {
|
||||
msg := "duplicated key '" + info.Key + "' in struct " + st.String()
|
||||
return nil, errors.New(msg)
|
||||
}
|
||||
|
||||
info.Id = len(fieldsList)
|
||||
fieldsList = append(fieldsList, info)
|
||||
fieldsMap[info.Key] = info
|
||||
}
|
||||
|
||||
sinfo = &structInfo{
|
||||
FieldsMap: fieldsMap,
|
||||
FieldsList: fieldsList,
|
||||
InlineMap: inlineMap,
|
||||
InlineUnmarshalers: inlineUnmarshalers,
|
||||
}
|
||||
|
||||
fieldMapMutex.Lock()
|
||||
structMap[st] = sinfo
|
||||
fieldMapMutex.Unlock()
|
||||
return sinfo, nil
|
||||
}
|
||||
|
||||
// IsZeroer is used to check whether an object is zero to
|
||||
// determine whether it should be omitted when marshaling
|
||||
// with the omitempty flag. One notable implementation
|
||||
// is time.Time.
|
||||
type IsZeroer interface {
|
||||
IsZero() bool
|
||||
}
|
||||
|
||||
func isZero(v reflect.Value) bool {
|
||||
kind := v.Kind()
|
||||
if z, ok := v.Interface().(IsZeroer); ok {
|
||||
if (kind == reflect.Ptr || kind == reflect.Interface) && v.IsNil() {
|
||||
return true
|
||||
}
|
||||
return z.IsZero()
|
||||
}
|
||||
switch kind {
|
||||
case reflect.String:
|
||||
return len(v.String()) == 0
|
||||
case reflect.Interface, reflect.Ptr:
|
||||
return v.IsNil()
|
||||
case reflect.Slice:
|
||||
return v.Len() == 0
|
||||
case reflect.Map:
|
||||
return v.Len() == 0
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return v.Int() == 0
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return v.Float() == 0
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
return v.Uint() == 0
|
||||
case reflect.Bool:
|
||||
return !v.Bool()
|
||||
case reflect.Struct:
|
||||
vt := v.Type()
|
||||
for i := v.NumField() - 1; i >= 0; i-- {
|
||||
if vt.Field(i).PkgPath != "" {
|
||||
continue // Private field
|
||||
}
|
||||
if !isZero(v.Field(i)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
-807
@@ -1,807 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2011-2019 Canonical Ltd
|
||||
// Copyright (c) 2006-2010 Kirill Simonov
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
// this software and associated documentation files (the "Software"), to deal in
|
||||
// the Software without restriction, including without limitation the rights to
|
||||
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
// of the Software, and to permit persons to whom the Software is furnished to do
|
||||
// so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
package yaml
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
// The version directive data.
|
||||
type yaml_version_directive_t struct {
|
||||
major int8 // The major version number.
|
||||
minor int8 // The minor version number.
|
||||
}
|
||||
|
||||
// The tag directive data.
|
||||
type yaml_tag_directive_t struct {
|
||||
handle []byte // The tag handle.
|
||||
prefix []byte // The tag prefix.
|
||||
}
|
||||
|
||||
type yaml_encoding_t int
|
||||
|
||||
// The stream encoding.
|
||||
const (
|
||||
// Let the parser choose the encoding.
|
||||
yaml_ANY_ENCODING yaml_encoding_t = iota
|
||||
|
||||
yaml_UTF8_ENCODING // The default UTF-8 encoding.
|
||||
yaml_UTF16LE_ENCODING // The UTF-16-LE encoding with BOM.
|
||||
yaml_UTF16BE_ENCODING // The UTF-16-BE encoding with BOM.
|
||||
)
|
||||
|
||||
type yaml_break_t int
|
||||
|
||||
// Line break types.
|
||||
const (
|
||||
// Let the parser choose the break type.
|
||||
yaml_ANY_BREAK yaml_break_t = iota
|
||||
|
||||
yaml_CR_BREAK // Use CR for line breaks (Mac style).
|
||||
yaml_LN_BREAK // Use LN for line breaks (Unix style).
|
||||
yaml_CRLN_BREAK // Use CR LN for line breaks (DOS style).
|
||||
)
|
||||
|
||||
type yaml_error_type_t int
|
||||
|
||||
// Many bad things could happen with the parser and emitter.
|
||||
const (
|
||||
// No error is produced.
|
||||
yaml_NO_ERROR yaml_error_type_t = iota
|
||||
|
||||
yaml_MEMORY_ERROR // Cannot allocate or reallocate a block of memory.
|
||||
yaml_READER_ERROR // Cannot read or decode the input stream.
|
||||
yaml_SCANNER_ERROR // Cannot scan the input stream.
|
||||
yaml_PARSER_ERROR // Cannot parse the input stream.
|
||||
yaml_COMPOSER_ERROR // Cannot compose a YAML document.
|
||||
yaml_WRITER_ERROR // Cannot write to the output stream.
|
||||
yaml_EMITTER_ERROR // Cannot emit a YAML stream.
|
||||
)
|
||||
|
||||
// The pointer position.
|
||||
type yaml_mark_t struct {
|
||||
index int // The position index.
|
||||
line int // The position line.
|
||||
column int // The position column.
|
||||
}
|
||||
|
||||
// Node Styles
|
||||
|
||||
type yaml_style_t int8
|
||||
|
||||
type yaml_scalar_style_t yaml_style_t
|
||||
|
||||
// Scalar styles.
|
||||
const (
|
||||
// Let the emitter choose the style.
|
||||
yaml_ANY_SCALAR_STYLE yaml_scalar_style_t = 0
|
||||
|
||||
yaml_PLAIN_SCALAR_STYLE yaml_scalar_style_t = 1 << iota // The plain scalar style.
|
||||
yaml_SINGLE_QUOTED_SCALAR_STYLE // The single-quoted scalar style.
|
||||
yaml_DOUBLE_QUOTED_SCALAR_STYLE // The double-quoted scalar style.
|
||||
yaml_LITERAL_SCALAR_STYLE // The literal scalar style.
|
||||
yaml_FOLDED_SCALAR_STYLE // The folded scalar style.
|
||||
)
|
||||
|
||||
type yaml_sequence_style_t yaml_style_t
|
||||
|
||||
// Sequence styles.
|
||||
const (
|
||||
// Let the emitter choose the style.
|
||||
yaml_ANY_SEQUENCE_STYLE yaml_sequence_style_t = iota
|
||||
|
||||
yaml_BLOCK_SEQUENCE_STYLE // The block sequence style.
|
||||
yaml_FLOW_SEQUENCE_STYLE // The flow sequence style.
|
||||
)
|
||||
|
||||
type yaml_mapping_style_t yaml_style_t
|
||||
|
||||
// Mapping styles.
|
||||
const (
|
||||
// Let the emitter choose the style.
|
||||
yaml_ANY_MAPPING_STYLE yaml_mapping_style_t = iota
|
||||
|
||||
yaml_BLOCK_MAPPING_STYLE // The block mapping style.
|
||||
yaml_FLOW_MAPPING_STYLE // The flow mapping style.
|
||||
)
|
||||
|
||||
// Tokens
|
||||
|
||||
type yaml_token_type_t int
|
||||
|
||||
// Token types.
|
||||
const (
|
||||
// An empty token.
|
||||
yaml_NO_TOKEN yaml_token_type_t = iota
|
||||
|
||||
yaml_STREAM_START_TOKEN // A STREAM-START token.
|
||||
yaml_STREAM_END_TOKEN // A STREAM-END token.
|
||||
|
||||
yaml_VERSION_DIRECTIVE_TOKEN // A VERSION-DIRECTIVE token.
|
||||
yaml_TAG_DIRECTIVE_TOKEN // A TAG-DIRECTIVE token.
|
||||
yaml_DOCUMENT_START_TOKEN // A DOCUMENT-START token.
|
||||
yaml_DOCUMENT_END_TOKEN // A DOCUMENT-END token.
|
||||
|
||||
yaml_BLOCK_SEQUENCE_START_TOKEN // A BLOCK-SEQUENCE-START token.
|
||||
yaml_BLOCK_MAPPING_START_TOKEN // A BLOCK-SEQUENCE-END token.
|
||||
yaml_BLOCK_END_TOKEN // A BLOCK-END token.
|
||||
|
||||
yaml_FLOW_SEQUENCE_START_TOKEN // A FLOW-SEQUENCE-START token.
|
||||
yaml_FLOW_SEQUENCE_END_TOKEN // A FLOW-SEQUENCE-END token.
|
||||
yaml_FLOW_MAPPING_START_TOKEN // A FLOW-MAPPING-START token.
|
||||
yaml_FLOW_MAPPING_END_TOKEN // A FLOW-MAPPING-END token.
|
||||
|
||||
yaml_BLOCK_ENTRY_TOKEN // A BLOCK-ENTRY token.
|
||||
yaml_FLOW_ENTRY_TOKEN // A FLOW-ENTRY token.
|
||||
yaml_KEY_TOKEN // A KEY token.
|
||||
yaml_VALUE_TOKEN // A VALUE token.
|
||||
|
||||
yaml_ALIAS_TOKEN // An ALIAS token.
|
||||
yaml_ANCHOR_TOKEN // An ANCHOR token.
|
||||
yaml_TAG_TOKEN // A TAG token.
|
||||
yaml_SCALAR_TOKEN // A SCALAR token.
|
||||
)
|
||||
|
||||
func (tt yaml_token_type_t) String() string {
|
||||
switch tt {
|
||||
case yaml_NO_TOKEN:
|
||||
return "yaml_NO_TOKEN"
|
||||
case yaml_STREAM_START_TOKEN:
|
||||
return "yaml_STREAM_START_TOKEN"
|
||||
case yaml_STREAM_END_TOKEN:
|
||||
return "yaml_STREAM_END_TOKEN"
|
||||
case yaml_VERSION_DIRECTIVE_TOKEN:
|
||||
return "yaml_VERSION_DIRECTIVE_TOKEN"
|
||||
case yaml_TAG_DIRECTIVE_TOKEN:
|
||||
return "yaml_TAG_DIRECTIVE_TOKEN"
|
||||
case yaml_DOCUMENT_START_TOKEN:
|
||||
return "yaml_DOCUMENT_START_TOKEN"
|
||||
case yaml_DOCUMENT_END_TOKEN:
|
||||
return "yaml_DOCUMENT_END_TOKEN"
|
||||
case yaml_BLOCK_SEQUENCE_START_TOKEN:
|
||||
return "yaml_BLOCK_SEQUENCE_START_TOKEN"
|
||||
case yaml_BLOCK_MAPPING_START_TOKEN:
|
||||
return "yaml_BLOCK_MAPPING_START_TOKEN"
|
||||
case yaml_BLOCK_END_TOKEN:
|
||||
return "yaml_BLOCK_END_TOKEN"
|
||||
case yaml_FLOW_SEQUENCE_START_TOKEN:
|
||||
return "yaml_FLOW_SEQUENCE_START_TOKEN"
|
||||
case yaml_FLOW_SEQUENCE_END_TOKEN:
|
||||
return "yaml_FLOW_SEQUENCE_END_TOKEN"
|
||||
case yaml_FLOW_MAPPING_START_TOKEN:
|
||||
return "yaml_FLOW_MAPPING_START_TOKEN"
|
||||
case yaml_FLOW_MAPPING_END_TOKEN:
|
||||
return "yaml_FLOW_MAPPING_END_TOKEN"
|
||||
case yaml_BLOCK_ENTRY_TOKEN:
|
||||
return "yaml_BLOCK_ENTRY_TOKEN"
|
||||
case yaml_FLOW_ENTRY_TOKEN:
|
||||
return "yaml_FLOW_ENTRY_TOKEN"
|
||||
case yaml_KEY_TOKEN:
|
||||
return "yaml_KEY_TOKEN"
|
||||
case yaml_VALUE_TOKEN:
|
||||
return "yaml_VALUE_TOKEN"
|
||||
case yaml_ALIAS_TOKEN:
|
||||
return "yaml_ALIAS_TOKEN"
|
||||
case yaml_ANCHOR_TOKEN:
|
||||
return "yaml_ANCHOR_TOKEN"
|
||||
case yaml_TAG_TOKEN:
|
||||
return "yaml_TAG_TOKEN"
|
||||
case yaml_SCALAR_TOKEN:
|
||||
return "yaml_SCALAR_TOKEN"
|
||||
}
|
||||
return "<unknown token>"
|
||||
}
|
||||
|
||||
// The token structure.
|
||||
type yaml_token_t struct {
|
||||
// The token type.
|
||||
typ yaml_token_type_t
|
||||
|
||||
// The start/end of the token.
|
||||
start_mark, end_mark yaml_mark_t
|
||||
|
||||
// The stream encoding (for yaml_STREAM_START_TOKEN).
|
||||
encoding yaml_encoding_t
|
||||
|
||||
// The alias/anchor/scalar value or tag/tag directive handle
|
||||
// (for yaml_ALIAS_TOKEN, yaml_ANCHOR_TOKEN, yaml_SCALAR_TOKEN, yaml_TAG_TOKEN, yaml_TAG_DIRECTIVE_TOKEN).
|
||||
value []byte
|
||||
|
||||
// The tag suffix (for yaml_TAG_TOKEN).
|
||||
suffix []byte
|
||||
|
||||
// The tag directive prefix (for yaml_TAG_DIRECTIVE_TOKEN).
|
||||
prefix []byte
|
||||
|
||||
// The scalar style (for yaml_SCALAR_TOKEN).
|
||||
style yaml_scalar_style_t
|
||||
|
||||
// The version directive major/minor (for yaml_VERSION_DIRECTIVE_TOKEN).
|
||||
major, minor int8
|
||||
}
|
||||
|
||||
// Events
|
||||
|
||||
type yaml_event_type_t int8
|
||||
|
||||
// Event types.
|
||||
const (
|
||||
// An empty event.
|
||||
yaml_NO_EVENT yaml_event_type_t = iota
|
||||
|
||||
yaml_STREAM_START_EVENT // A STREAM-START event.
|
||||
yaml_STREAM_END_EVENT // A STREAM-END event.
|
||||
yaml_DOCUMENT_START_EVENT // A DOCUMENT-START event.
|
||||
yaml_DOCUMENT_END_EVENT // A DOCUMENT-END event.
|
||||
yaml_ALIAS_EVENT // An ALIAS event.
|
||||
yaml_SCALAR_EVENT // A SCALAR event.
|
||||
yaml_SEQUENCE_START_EVENT // A SEQUENCE-START event.
|
||||
yaml_SEQUENCE_END_EVENT // A SEQUENCE-END event.
|
||||
yaml_MAPPING_START_EVENT // A MAPPING-START event.
|
||||
yaml_MAPPING_END_EVENT // A MAPPING-END event.
|
||||
yaml_TAIL_COMMENT_EVENT
|
||||
)
|
||||
|
||||
var eventStrings = []string{
|
||||
yaml_NO_EVENT: "none",
|
||||
yaml_STREAM_START_EVENT: "stream start",
|
||||
yaml_STREAM_END_EVENT: "stream end",
|
||||
yaml_DOCUMENT_START_EVENT: "document start",
|
||||
yaml_DOCUMENT_END_EVENT: "document end",
|
||||
yaml_ALIAS_EVENT: "alias",
|
||||
yaml_SCALAR_EVENT: "scalar",
|
||||
yaml_SEQUENCE_START_EVENT: "sequence start",
|
||||
yaml_SEQUENCE_END_EVENT: "sequence end",
|
||||
yaml_MAPPING_START_EVENT: "mapping start",
|
||||
yaml_MAPPING_END_EVENT: "mapping end",
|
||||
yaml_TAIL_COMMENT_EVENT: "tail comment",
|
||||
}
|
||||
|
||||
func (e yaml_event_type_t) String() string {
|
||||
if e < 0 || int(e) >= len(eventStrings) {
|
||||
return fmt.Sprintf("unknown event %d", e)
|
||||
}
|
||||
return eventStrings[e]
|
||||
}
|
||||
|
||||
// The event structure.
|
||||
type yaml_event_t struct {
|
||||
|
||||
// The event type.
|
||||
typ yaml_event_type_t
|
||||
|
||||
// The start and end of the event.
|
||||
start_mark, end_mark yaml_mark_t
|
||||
|
||||
// The document encoding (for yaml_STREAM_START_EVENT).
|
||||
encoding yaml_encoding_t
|
||||
|
||||
// The version directive (for yaml_DOCUMENT_START_EVENT).
|
||||
version_directive *yaml_version_directive_t
|
||||
|
||||
// The list of tag directives (for yaml_DOCUMENT_START_EVENT).
|
||||
tag_directives []yaml_tag_directive_t
|
||||
|
||||
// The comments
|
||||
head_comment []byte
|
||||
line_comment []byte
|
||||
foot_comment []byte
|
||||
tail_comment []byte
|
||||
|
||||
// The anchor (for yaml_SCALAR_EVENT, yaml_SEQUENCE_START_EVENT, yaml_MAPPING_START_EVENT, yaml_ALIAS_EVENT).
|
||||
anchor []byte
|
||||
|
||||
// The tag (for yaml_SCALAR_EVENT, yaml_SEQUENCE_START_EVENT, yaml_MAPPING_START_EVENT).
|
||||
tag []byte
|
||||
|
||||
// The scalar value (for yaml_SCALAR_EVENT).
|
||||
value []byte
|
||||
|
||||
// Is the document start/end indicator implicit, or the tag optional?
|
||||
// (for yaml_DOCUMENT_START_EVENT, yaml_DOCUMENT_END_EVENT, yaml_SEQUENCE_START_EVENT, yaml_MAPPING_START_EVENT, yaml_SCALAR_EVENT).
|
||||
implicit bool
|
||||
|
||||
// Is the tag optional for any non-plain style? (for yaml_SCALAR_EVENT).
|
||||
quoted_implicit bool
|
||||
|
||||
// The style (for yaml_SCALAR_EVENT, yaml_SEQUENCE_START_EVENT, yaml_MAPPING_START_EVENT).
|
||||
style yaml_style_t
|
||||
}
|
||||
|
||||
func (e *yaml_event_t) scalar_style() yaml_scalar_style_t { return yaml_scalar_style_t(e.style) }
|
||||
func (e *yaml_event_t) sequence_style() yaml_sequence_style_t { return yaml_sequence_style_t(e.style) }
|
||||
func (e *yaml_event_t) mapping_style() yaml_mapping_style_t { return yaml_mapping_style_t(e.style) }
|
||||
|
||||
// Nodes
|
||||
|
||||
const (
|
||||
yaml_NULL_TAG = "tag:yaml.org,2002:null" // The tag !!null with the only possible value: null.
|
||||
yaml_BOOL_TAG = "tag:yaml.org,2002:bool" // The tag !!bool with the values: true and false.
|
||||
yaml_STR_TAG = "tag:yaml.org,2002:str" // The tag !!str for string values.
|
||||
yaml_INT_TAG = "tag:yaml.org,2002:int" // The tag !!int for integer values.
|
||||
yaml_FLOAT_TAG = "tag:yaml.org,2002:float" // The tag !!float for float values.
|
||||
yaml_TIMESTAMP_TAG = "tag:yaml.org,2002:timestamp" // The tag !!timestamp for date and time values.
|
||||
|
||||
yaml_SEQ_TAG = "tag:yaml.org,2002:seq" // The tag !!seq is used to denote sequences.
|
||||
yaml_MAP_TAG = "tag:yaml.org,2002:map" // The tag !!map is used to denote mapping.
|
||||
|
||||
// Not in original libyaml.
|
||||
yaml_BINARY_TAG = "tag:yaml.org,2002:binary"
|
||||
yaml_MERGE_TAG = "tag:yaml.org,2002:merge"
|
||||
|
||||
yaml_DEFAULT_SCALAR_TAG = yaml_STR_TAG // The default scalar tag is !!str.
|
||||
yaml_DEFAULT_SEQUENCE_TAG = yaml_SEQ_TAG // The default sequence tag is !!seq.
|
||||
yaml_DEFAULT_MAPPING_TAG = yaml_MAP_TAG // The default mapping tag is !!map.
|
||||
)
|
||||
|
||||
type yaml_node_type_t int
|
||||
|
||||
// Node types.
|
||||
const (
|
||||
// An empty node.
|
||||
yaml_NO_NODE yaml_node_type_t = iota
|
||||
|
||||
yaml_SCALAR_NODE // A scalar node.
|
||||
yaml_SEQUENCE_NODE // A sequence node.
|
||||
yaml_MAPPING_NODE // A mapping node.
|
||||
)
|
||||
|
||||
// An element of a sequence node.
|
||||
type yaml_node_item_t int
|
||||
|
||||
// An element of a mapping node.
|
||||
type yaml_node_pair_t struct {
|
||||
key int // The key of the element.
|
||||
value int // The value of the element.
|
||||
}
|
||||
|
||||
// The node structure.
|
||||
type yaml_node_t struct {
|
||||
typ yaml_node_type_t // The node type.
|
||||
tag []byte // The node tag.
|
||||
|
||||
// The node data.
|
||||
|
||||
// The scalar parameters (for yaml_SCALAR_NODE).
|
||||
scalar struct {
|
||||
value []byte // The scalar value.
|
||||
length int // The length of the scalar value.
|
||||
style yaml_scalar_style_t // The scalar style.
|
||||
}
|
||||
|
||||
// The sequence parameters (for YAML_SEQUENCE_NODE).
|
||||
sequence struct {
|
||||
items_data []yaml_node_item_t // The stack of sequence items.
|
||||
style yaml_sequence_style_t // The sequence style.
|
||||
}
|
||||
|
||||
// The mapping parameters (for yaml_MAPPING_NODE).
|
||||
mapping struct {
|
||||
pairs_data []yaml_node_pair_t // The stack of mapping pairs (key, value).
|
||||
pairs_start *yaml_node_pair_t // The beginning of the stack.
|
||||
pairs_end *yaml_node_pair_t // The end of the stack.
|
||||
pairs_top *yaml_node_pair_t // The top of the stack.
|
||||
style yaml_mapping_style_t // The mapping style.
|
||||
}
|
||||
|
||||
start_mark yaml_mark_t // The beginning of the node.
|
||||
end_mark yaml_mark_t // The end of the node.
|
||||
|
||||
}
|
||||
|
||||
// The document structure.
|
||||
type yaml_document_t struct {
|
||||
|
||||
// The document nodes.
|
||||
nodes []yaml_node_t
|
||||
|
||||
// The version directive.
|
||||
version_directive *yaml_version_directive_t
|
||||
|
||||
// The list of tag directives.
|
||||
tag_directives_data []yaml_tag_directive_t
|
||||
tag_directives_start int // The beginning of the tag directives list.
|
||||
tag_directives_end int // The end of the tag directives list.
|
||||
|
||||
start_implicit int // Is the document start indicator implicit?
|
||||
end_implicit int // Is the document end indicator implicit?
|
||||
|
||||
// The start/end of the document.
|
||||
start_mark, end_mark yaml_mark_t
|
||||
}
|
||||
|
||||
// The prototype of a read handler.
|
||||
//
|
||||
// The read handler is called when the parser needs to read more bytes from the
|
||||
// source. The handler should write not more than size bytes to the buffer.
|
||||
// The number of written bytes should be set to the size_read variable.
|
||||
//
|
||||
// [in,out] data A pointer to an application data specified by
|
||||
// yaml_parser_set_input().
|
||||
// [out] buffer The buffer to write the data from the source.
|
||||
// [in] size The size of the buffer.
|
||||
// [out] size_read The actual number of bytes read from the source.
|
||||
//
|
||||
// On success, the handler should return 1. If the handler failed,
|
||||
// the returned value should be 0. On EOF, the handler should set the
|
||||
// size_read to 0 and return 1.
|
||||
type yaml_read_handler_t func(parser *yaml_parser_t, buffer []byte) (n int, err error)
|
||||
|
||||
// This structure holds information about a potential simple key.
|
||||
type yaml_simple_key_t struct {
|
||||
possible bool // Is a simple key possible?
|
||||
required bool // Is a simple key required?
|
||||
token_number int // The number of the token.
|
||||
mark yaml_mark_t // The position mark.
|
||||
}
|
||||
|
||||
// The states of the parser.
|
||||
type yaml_parser_state_t int
|
||||
|
||||
const (
|
||||
yaml_PARSE_STREAM_START_STATE yaml_parser_state_t = iota
|
||||
|
||||
yaml_PARSE_IMPLICIT_DOCUMENT_START_STATE // Expect the beginning of an implicit document.
|
||||
yaml_PARSE_DOCUMENT_START_STATE // Expect DOCUMENT-START.
|
||||
yaml_PARSE_DOCUMENT_CONTENT_STATE // Expect the content of a document.
|
||||
yaml_PARSE_DOCUMENT_END_STATE // Expect DOCUMENT-END.
|
||||
yaml_PARSE_BLOCK_NODE_STATE // Expect a block node.
|
||||
yaml_PARSE_BLOCK_NODE_OR_INDENTLESS_SEQUENCE_STATE // Expect a block node or indentless sequence.
|
||||
yaml_PARSE_FLOW_NODE_STATE // Expect a flow node.
|
||||
yaml_PARSE_BLOCK_SEQUENCE_FIRST_ENTRY_STATE // Expect the first entry of a block sequence.
|
||||
yaml_PARSE_BLOCK_SEQUENCE_ENTRY_STATE // Expect an entry of a block sequence.
|
||||
yaml_PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE // Expect an entry of an indentless sequence.
|
||||
yaml_PARSE_BLOCK_MAPPING_FIRST_KEY_STATE // Expect the first key of a block mapping.
|
||||
yaml_PARSE_BLOCK_MAPPING_KEY_STATE // Expect a block mapping key.
|
||||
yaml_PARSE_BLOCK_MAPPING_VALUE_STATE // Expect a block mapping value.
|
||||
yaml_PARSE_FLOW_SEQUENCE_FIRST_ENTRY_STATE // Expect the first entry of a flow sequence.
|
||||
yaml_PARSE_FLOW_SEQUENCE_ENTRY_STATE // Expect an entry of a flow sequence.
|
||||
yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_KEY_STATE // Expect a key of an ordered mapping.
|
||||
yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_VALUE_STATE // Expect a value of an ordered mapping.
|
||||
yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_END_STATE // Expect the and of an ordered mapping entry.
|
||||
yaml_PARSE_FLOW_MAPPING_FIRST_KEY_STATE // Expect the first key of a flow mapping.
|
||||
yaml_PARSE_FLOW_MAPPING_KEY_STATE // Expect a key of a flow mapping.
|
||||
yaml_PARSE_FLOW_MAPPING_VALUE_STATE // Expect a value of a flow mapping.
|
||||
yaml_PARSE_FLOW_MAPPING_EMPTY_VALUE_STATE // Expect an empty value of a flow mapping.
|
||||
yaml_PARSE_END_STATE // Expect nothing.
|
||||
)
|
||||
|
||||
func (ps yaml_parser_state_t) String() string {
|
||||
switch ps {
|
||||
case yaml_PARSE_STREAM_START_STATE:
|
||||
return "yaml_PARSE_STREAM_START_STATE"
|
||||
case yaml_PARSE_IMPLICIT_DOCUMENT_START_STATE:
|
||||
return "yaml_PARSE_IMPLICIT_DOCUMENT_START_STATE"
|
||||
case yaml_PARSE_DOCUMENT_START_STATE:
|
||||
return "yaml_PARSE_DOCUMENT_START_STATE"
|
||||
case yaml_PARSE_DOCUMENT_CONTENT_STATE:
|
||||
return "yaml_PARSE_DOCUMENT_CONTENT_STATE"
|
||||
case yaml_PARSE_DOCUMENT_END_STATE:
|
||||
return "yaml_PARSE_DOCUMENT_END_STATE"
|
||||
case yaml_PARSE_BLOCK_NODE_STATE:
|
||||
return "yaml_PARSE_BLOCK_NODE_STATE"
|
||||
case yaml_PARSE_BLOCK_NODE_OR_INDENTLESS_SEQUENCE_STATE:
|
||||
return "yaml_PARSE_BLOCK_NODE_OR_INDENTLESS_SEQUENCE_STATE"
|
||||
case yaml_PARSE_FLOW_NODE_STATE:
|
||||
return "yaml_PARSE_FLOW_NODE_STATE"
|
||||
case yaml_PARSE_BLOCK_SEQUENCE_FIRST_ENTRY_STATE:
|
||||
return "yaml_PARSE_BLOCK_SEQUENCE_FIRST_ENTRY_STATE"
|
||||
case yaml_PARSE_BLOCK_SEQUENCE_ENTRY_STATE:
|
||||
return "yaml_PARSE_BLOCK_SEQUENCE_ENTRY_STATE"
|
||||
case yaml_PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE:
|
||||
return "yaml_PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE"
|
||||
case yaml_PARSE_BLOCK_MAPPING_FIRST_KEY_STATE:
|
||||
return "yaml_PARSE_BLOCK_MAPPING_FIRST_KEY_STATE"
|
||||
case yaml_PARSE_BLOCK_MAPPING_KEY_STATE:
|
||||
return "yaml_PARSE_BLOCK_MAPPING_KEY_STATE"
|
||||
case yaml_PARSE_BLOCK_MAPPING_VALUE_STATE:
|
||||
return "yaml_PARSE_BLOCK_MAPPING_VALUE_STATE"
|
||||
case yaml_PARSE_FLOW_SEQUENCE_FIRST_ENTRY_STATE:
|
||||
return "yaml_PARSE_FLOW_SEQUENCE_FIRST_ENTRY_STATE"
|
||||
case yaml_PARSE_FLOW_SEQUENCE_ENTRY_STATE:
|
||||
return "yaml_PARSE_FLOW_SEQUENCE_ENTRY_STATE"
|
||||
case yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_KEY_STATE:
|
||||
return "yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_KEY_STATE"
|
||||
case yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_VALUE_STATE:
|
||||
return "yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_VALUE_STATE"
|
||||
case yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_END_STATE:
|
||||
return "yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_END_STATE"
|
||||
case yaml_PARSE_FLOW_MAPPING_FIRST_KEY_STATE:
|
||||
return "yaml_PARSE_FLOW_MAPPING_FIRST_KEY_STATE"
|
||||
case yaml_PARSE_FLOW_MAPPING_KEY_STATE:
|
||||
return "yaml_PARSE_FLOW_MAPPING_KEY_STATE"
|
||||
case yaml_PARSE_FLOW_MAPPING_VALUE_STATE:
|
||||
return "yaml_PARSE_FLOW_MAPPING_VALUE_STATE"
|
||||
case yaml_PARSE_FLOW_MAPPING_EMPTY_VALUE_STATE:
|
||||
return "yaml_PARSE_FLOW_MAPPING_EMPTY_VALUE_STATE"
|
||||
case yaml_PARSE_END_STATE:
|
||||
return "yaml_PARSE_END_STATE"
|
||||
}
|
||||
return "<unknown parser state>"
|
||||
}
|
||||
|
||||
// This structure holds aliases data.
|
||||
type yaml_alias_data_t struct {
|
||||
anchor []byte // The anchor.
|
||||
index int // The node id.
|
||||
mark yaml_mark_t // The anchor mark.
|
||||
}
|
||||
|
||||
// The parser structure.
|
||||
//
|
||||
// All members are internal. Manage the structure using the
|
||||
// yaml_parser_ family of functions.
|
||||
type yaml_parser_t struct {
|
||||
|
||||
// Error handling
|
||||
|
||||
error yaml_error_type_t // Error type.
|
||||
|
||||
problem string // Error description.
|
||||
|
||||
// The byte about which the problem occurred.
|
||||
problem_offset int
|
||||
problem_value int
|
||||
problem_mark yaml_mark_t
|
||||
|
||||
// The error context.
|
||||
context string
|
||||
context_mark yaml_mark_t
|
||||
|
||||
// Reader stuff
|
||||
|
||||
read_handler yaml_read_handler_t // Read handler.
|
||||
|
||||
input_reader io.Reader // File input data.
|
||||
input []byte // String input data.
|
||||
input_pos int
|
||||
|
||||
eof bool // EOF flag
|
||||
|
||||
buffer []byte // The working buffer.
|
||||
buffer_pos int // The current position of the buffer.
|
||||
|
||||
unread int // The number of unread characters in the buffer.
|
||||
|
||||
newlines int // The number of line breaks since last non-break/non-blank character
|
||||
|
||||
raw_buffer []byte // The raw buffer.
|
||||
raw_buffer_pos int // The current position of the buffer.
|
||||
|
||||
encoding yaml_encoding_t // The input encoding.
|
||||
|
||||
offset int // The offset of the current position (in bytes).
|
||||
mark yaml_mark_t // The mark of the current position.
|
||||
|
||||
// Comments
|
||||
|
||||
head_comment []byte // The current head comments
|
||||
line_comment []byte // The current line comments
|
||||
foot_comment []byte // The current foot comments
|
||||
tail_comment []byte // Foot comment that happens at the end of a block.
|
||||
stem_comment []byte // Comment in item preceding a nested structure (list inside list item, etc)
|
||||
|
||||
comments []yaml_comment_t // The folded comments for all parsed tokens
|
||||
comments_head int
|
||||
|
||||
// Scanner stuff
|
||||
|
||||
stream_start_produced bool // Have we started to scan the input stream?
|
||||
stream_end_produced bool // Have we reached the end of the input stream?
|
||||
|
||||
flow_level int // The number of unclosed '[' and '{' indicators.
|
||||
|
||||
tokens []yaml_token_t // The tokens queue.
|
||||
tokens_head int // The head of the tokens queue.
|
||||
tokens_parsed int // The number of tokens fetched from the queue.
|
||||
token_available bool // Does the tokens queue contain a token ready for dequeueing.
|
||||
|
||||
indent int // The current indentation level.
|
||||
indents []int // The indentation levels stack.
|
||||
|
||||
simple_key_allowed bool // May a simple key occur at the current position?
|
||||
simple_keys []yaml_simple_key_t // The stack of simple keys.
|
||||
simple_keys_by_tok map[int]int // possible simple_key indexes indexed by token_number
|
||||
|
||||
// Parser stuff
|
||||
|
||||
state yaml_parser_state_t // The current parser state.
|
||||
states []yaml_parser_state_t // The parser states stack.
|
||||
marks []yaml_mark_t // The stack of marks.
|
||||
tag_directives []yaml_tag_directive_t // The list of TAG directives.
|
||||
|
||||
// Dumper stuff
|
||||
|
||||
aliases []yaml_alias_data_t // The alias data.
|
||||
|
||||
document *yaml_document_t // The currently parsed document.
|
||||
}
|
||||
|
||||
type yaml_comment_t struct {
|
||||
|
||||
scan_mark yaml_mark_t // Position where scanning for comments started
|
||||
token_mark yaml_mark_t // Position after which tokens will be associated with this comment
|
||||
start_mark yaml_mark_t // Position of '#' comment mark
|
||||
end_mark yaml_mark_t // Position where comment terminated
|
||||
|
||||
head []byte
|
||||
line []byte
|
||||
foot []byte
|
||||
}
|
||||
|
||||
// Emitter Definitions
|
||||
|
||||
// The prototype of a write handler.
|
||||
//
|
||||
// The write handler is called when the emitter needs to flush the accumulated
|
||||
// characters to the output. The handler should write @a size bytes of the
|
||||
// @a buffer to the output.
|
||||
//
|
||||
// @param[in,out] data A pointer to an application data specified by
|
||||
// yaml_emitter_set_output().
|
||||
// @param[in] buffer The buffer with bytes to be written.
|
||||
// @param[in] size The size of the buffer.
|
||||
//
|
||||
// @returns On success, the handler should return @c 1. If the handler failed,
|
||||
// the returned value should be @c 0.
|
||||
//
|
||||
type yaml_write_handler_t func(emitter *yaml_emitter_t, buffer []byte) error
|
||||
|
||||
type yaml_emitter_state_t int
|
||||
|
||||
// The emitter states.
|
||||
const (
|
||||
// Expect STREAM-START.
|
||||
yaml_EMIT_STREAM_START_STATE yaml_emitter_state_t = iota
|
||||
|
||||
yaml_EMIT_FIRST_DOCUMENT_START_STATE // Expect the first DOCUMENT-START or STREAM-END.
|
||||
yaml_EMIT_DOCUMENT_START_STATE // Expect DOCUMENT-START or STREAM-END.
|
||||
yaml_EMIT_DOCUMENT_CONTENT_STATE // Expect the content of a document.
|
||||
yaml_EMIT_DOCUMENT_END_STATE // Expect DOCUMENT-END.
|
||||
yaml_EMIT_FLOW_SEQUENCE_FIRST_ITEM_STATE // Expect the first item of a flow sequence.
|
||||
yaml_EMIT_FLOW_SEQUENCE_TRAIL_ITEM_STATE // Expect the next item of a flow sequence, with the comma already written out
|
||||
yaml_EMIT_FLOW_SEQUENCE_ITEM_STATE // Expect an item of a flow sequence.
|
||||
yaml_EMIT_FLOW_MAPPING_FIRST_KEY_STATE // Expect the first key of a flow mapping.
|
||||
yaml_EMIT_FLOW_MAPPING_TRAIL_KEY_STATE // Expect the next key of a flow mapping, with the comma already written out
|
||||
yaml_EMIT_FLOW_MAPPING_KEY_STATE // Expect a key of a flow mapping.
|
||||
yaml_EMIT_FLOW_MAPPING_SIMPLE_VALUE_STATE // Expect a value for a simple key of a flow mapping.
|
||||
yaml_EMIT_FLOW_MAPPING_VALUE_STATE // Expect a value of a flow mapping.
|
||||
yaml_EMIT_BLOCK_SEQUENCE_FIRST_ITEM_STATE // Expect the first item of a block sequence.
|
||||
yaml_EMIT_BLOCK_SEQUENCE_ITEM_STATE // Expect an item of a block sequence.
|
||||
yaml_EMIT_BLOCK_MAPPING_FIRST_KEY_STATE // Expect the first key of a block mapping.
|
||||
yaml_EMIT_BLOCK_MAPPING_KEY_STATE // Expect the key of a block mapping.
|
||||
yaml_EMIT_BLOCK_MAPPING_SIMPLE_VALUE_STATE // Expect a value for a simple key of a block mapping.
|
||||
yaml_EMIT_BLOCK_MAPPING_VALUE_STATE // Expect a value of a block mapping.
|
||||
yaml_EMIT_END_STATE // Expect nothing.
|
||||
)
|
||||
|
||||
// The emitter structure.
|
||||
//
|
||||
// All members are internal. Manage the structure using the @c yaml_emitter_
|
||||
// family of functions.
|
||||
type yaml_emitter_t struct {
|
||||
|
||||
// Error handling
|
||||
|
||||
error yaml_error_type_t // Error type.
|
||||
problem string // Error description.
|
||||
|
||||
// Writer stuff
|
||||
|
||||
write_handler yaml_write_handler_t // Write handler.
|
||||
|
||||
output_buffer *[]byte // String output data.
|
||||
output_writer io.Writer // File output data.
|
||||
|
||||
buffer []byte // The working buffer.
|
||||
buffer_pos int // The current position of the buffer.
|
||||
|
||||
raw_buffer []byte // The raw buffer.
|
||||
raw_buffer_pos int // The current position of the buffer.
|
||||
|
||||
encoding yaml_encoding_t // The stream encoding.
|
||||
|
||||
// Emitter stuff
|
||||
|
||||
canonical bool // If the output is in the canonical style?
|
||||
best_indent int // The number of indentation spaces.
|
||||
best_width int // The preferred width of the output lines.
|
||||
unicode bool // Allow unescaped non-ASCII characters?
|
||||
line_break yaml_break_t // The preferred line break.
|
||||
|
||||
state yaml_emitter_state_t // The current emitter state.
|
||||
states []yaml_emitter_state_t // The stack of states.
|
||||
|
||||
events []yaml_event_t // The event queue.
|
||||
events_head int // The head of the event queue.
|
||||
|
||||
indents []int // The stack of indentation levels.
|
||||
|
||||
tag_directives []yaml_tag_directive_t // The list of tag directives.
|
||||
|
||||
indent int // The current indentation level.
|
||||
|
||||
flow_level int // The current flow level.
|
||||
|
||||
root_context bool // Is it the document root context?
|
||||
sequence_context bool // Is it a sequence context?
|
||||
mapping_context bool // Is it a mapping context?
|
||||
simple_key_context bool // Is it a simple mapping key context?
|
||||
|
||||
line int // The current line.
|
||||
column int // The current column.
|
||||
whitespace bool // If the last character was a whitespace?
|
||||
indention bool // If the last character was an indentation character (' ', '-', '?', ':')?
|
||||
open_ended bool // If an explicit document end is required?
|
||||
|
||||
space_above bool // Is there's an empty line above?
|
||||
foot_indent int // The indent used to write the foot comment above, or -1 if none.
|
||||
|
||||
// Anchor analysis.
|
||||
anchor_data struct {
|
||||
anchor []byte // The anchor value.
|
||||
alias bool // Is it an alias?
|
||||
}
|
||||
|
||||
// Tag analysis.
|
||||
tag_data struct {
|
||||
handle []byte // The tag handle.
|
||||
suffix []byte // The tag suffix.
|
||||
}
|
||||
|
||||
// Scalar analysis.
|
||||
scalar_data struct {
|
||||
value []byte // The scalar value.
|
||||
multiline bool // Does the scalar contain line breaks?
|
||||
flow_plain_allowed bool // Can the scalar be expessed in the flow plain style?
|
||||
block_plain_allowed bool // Can the scalar be expressed in the block plain style?
|
||||
single_quoted_allowed bool // Can the scalar be expressed in the single quoted style?
|
||||
block_allowed bool // Can the scalar be expressed in the literal or folded styles?
|
||||
style yaml_scalar_style_t // The output style.
|
||||
}
|
||||
|
||||
// Comments
|
||||
head_comment []byte
|
||||
line_comment []byte
|
||||
foot_comment []byte
|
||||
tail_comment []byte
|
||||
|
||||
key_line_comment []byte
|
||||
|
||||
// Dumper stuff
|
||||
|
||||
opened bool // If the stream was already opened?
|
||||
closed bool // If the stream was already closed?
|
||||
|
||||
// The information associated with the document nodes.
|
||||
anchors *struct {
|
||||
references int // The number of references.
|
||||
anchor int // The anchor id.
|
||||
serialized bool // If the node has been emitted?
|
||||
}
|
||||
|
||||
last_anchor_id int // The last assigned anchor id.
|
||||
|
||||
document *yaml_document_t // The currently emitted document.
|
||||
}
|
||||
-198
@@ -1,198 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2011-2019 Canonical Ltd
|
||||
// Copyright (c) 2006-2010 Kirill Simonov
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
// this software and associated documentation files (the "Software"), to deal in
|
||||
// the Software without restriction, including without limitation the rights to
|
||||
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
// of the Software, and to permit persons to whom the Software is furnished to do
|
||||
// so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
package yaml
|
||||
|
||||
const (
|
||||
// The size of the input raw buffer.
|
||||
input_raw_buffer_size = 512
|
||||
|
||||
// The size of the input buffer.
|
||||
// It should be possible to decode the whole raw buffer.
|
||||
input_buffer_size = input_raw_buffer_size * 3
|
||||
|
||||
// The size of the output buffer.
|
||||
output_buffer_size = 128
|
||||
|
||||
// The size of the output raw buffer.
|
||||
// It should be possible to encode the whole output buffer.
|
||||
output_raw_buffer_size = (output_buffer_size*2 + 2)
|
||||
|
||||
// The size of other stacks and queues.
|
||||
initial_stack_size = 16
|
||||
initial_queue_size = 16
|
||||
initial_string_size = 16
|
||||
)
|
||||
|
||||
// Check if the character at the specified position is an alphabetical
|
||||
// character, a digit, '_', or '-'.
|
||||
func is_alpha(b []byte, i int) bool {
|
||||
return b[i] >= '0' && b[i] <= '9' || b[i] >= 'A' && b[i] <= 'Z' || b[i] >= 'a' && b[i] <= 'z' || b[i] == '_' || b[i] == '-'
|
||||
}
|
||||
|
||||
// Check if the character at the specified position is a digit.
|
||||
func is_digit(b []byte, i int) bool {
|
||||
return b[i] >= '0' && b[i] <= '9'
|
||||
}
|
||||
|
||||
// Get the value of a digit.
|
||||
func as_digit(b []byte, i int) int {
|
||||
return int(b[i]) - '0'
|
||||
}
|
||||
|
||||
// Check if the character at the specified position is a hex-digit.
|
||||
func is_hex(b []byte, i int) bool {
|
||||
return b[i] >= '0' && b[i] <= '9' || b[i] >= 'A' && b[i] <= 'F' || b[i] >= 'a' && b[i] <= 'f'
|
||||
}
|
||||
|
||||
// Get the value of a hex-digit.
|
||||
func as_hex(b []byte, i int) int {
|
||||
bi := b[i]
|
||||
if bi >= 'A' && bi <= 'F' {
|
||||
return int(bi) - 'A' + 10
|
||||
}
|
||||
if bi >= 'a' && bi <= 'f' {
|
||||
return int(bi) - 'a' + 10
|
||||
}
|
||||
return int(bi) - '0'
|
||||
}
|
||||
|
||||
// Check if the character is ASCII.
|
||||
func is_ascii(b []byte, i int) bool {
|
||||
return b[i] <= 0x7F
|
||||
}
|
||||
|
||||
// Check if the character at the start of the buffer can be printed unescaped.
|
||||
func is_printable(b []byte, i int) bool {
|
||||
return ((b[i] == 0x0A) || // . == #x0A
|
||||
(b[i] >= 0x20 && b[i] <= 0x7E) || // #x20 <= . <= #x7E
|
||||
(b[i] == 0xC2 && b[i+1] >= 0xA0) || // #0xA0 <= . <= #xD7FF
|
||||
(b[i] > 0xC2 && b[i] < 0xED) ||
|
||||
(b[i] == 0xED && b[i+1] < 0xA0) ||
|
||||
(b[i] == 0xEE) ||
|
||||
(b[i] == 0xEF && // #xE000 <= . <= #xFFFD
|
||||
!(b[i+1] == 0xBB && b[i+2] == 0xBF) && // && . != #xFEFF
|
||||
!(b[i+1] == 0xBF && (b[i+2] == 0xBE || b[i+2] == 0xBF))))
|
||||
}
|
||||
|
||||
// Check if the character at the specified position is NUL.
|
||||
func is_z(b []byte, i int) bool {
|
||||
return b[i] == 0x00
|
||||
}
|
||||
|
||||
// Check if the beginning of the buffer is a BOM.
|
||||
func is_bom(b []byte, i int) bool {
|
||||
return b[0] == 0xEF && b[1] == 0xBB && b[2] == 0xBF
|
||||
}
|
||||
|
||||
// Check if the character at the specified position is space.
|
||||
func is_space(b []byte, i int) bool {
|
||||
return b[i] == ' '
|
||||
}
|
||||
|
||||
// Check if the character at the specified position is tab.
|
||||
func is_tab(b []byte, i int) bool {
|
||||
return b[i] == '\t'
|
||||
}
|
||||
|
||||
// Check if the character at the specified position is blank (space or tab).
|
||||
func is_blank(b []byte, i int) bool {
|
||||
//return is_space(b, i) || is_tab(b, i)
|
||||
return b[i] == ' ' || b[i] == '\t'
|
||||
}
|
||||
|
||||
// Check if the character at the specified position is a line break.
|
||||
func is_break(b []byte, i int) bool {
|
||||
return (b[i] == '\r' || // CR (#xD)
|
||||
b[i] == '\n' || // LF (#xA)
|
||||
b[i] == 0xC2 && b[i+1] == 0x85 || // NEL (#x85)
|
||||
b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA8 || // LS (#x2028)
|
||||
b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA9) // PS (#x2029)
|
||||
}
|
||||
|
||||
func is_crlf(b []byte, i int) bool {
|
||||
return b[i] == '\r' && b[i+1] == '\n'
|
||||
}
|
||||
|
||||
// Check if the character is a line break or NUL.
|
||||
func is_breakz(b []byte, i int) bool {
|
||||
//return is_break(b, i) || is_z(b, i)
|
||||
return (
|
||||
// is_break:
|
||||
b[i] == '\r' || // CR (#xD)
|
||||
b[i] == '\n' || // LF (#xA)
|
||||
b[i] == 0xC2 && b[i+1] == 0x85 || // NEL (#x85)
|
||||
b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA8 || // LS (#x2028)
|
||||
b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA9 || // PS (#x2029)
|
||||
// is_z:
|
||||
b[i] == 0)
|
||||
}
|
||||
|
||||
// Check if the character is a line break, space, or NUL.
|
||||
func is_spacez(b []byte, i int) bool {
|
||||
//return is_space(b, i) || is_breakz(b, i)
|
||||
return (
|
||||
// is_space:
|
||||
b[i] == ' ' ||
|
||||
// is_breakz:
|
||||
b[i] == '\r' || // CR (#xD)
|
||||
b[i] == '\n' || // LF (#xA)
|
||||
b[i] == 0xC2 && b[i+1] == 0x85 || // NEL (#x85)
|
||||
b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA8 || // LS (#x2028)
|
||||
b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA9 || // PS (#x2029)
|
||||
b[i] == 0)
|
||||
}
|
||||
|
||||
// Check if the character is a line break, space, tab, or NUL.
|
||||
func is_blankz(b []byte, i int) bool {
|
||||
//return is_blank(b, i) || is_breakz(b, i)
|
||||
return (
|
||||
// is_blank:
|
||||
b[i] == ' ' || b[i] == '\t' ||
|
||||
// is_breakz:
|
||||
b[i] == '\r' || // CR (#xD)
|
||||
b[i] == '\n' || // LF (#xA)
|
||||
b[i] == 0xC2 && b[i+1] == 0x85 || // NEL (#x85)
|
||||
b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA8 || // LS (#x2028)
|
||||
b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA9 || // PS (#x2029)
|
||||
b[i] == 0)
|
||||
}
|
||||
|
||||
// Determine the width of the character.
|
||||
func width(b byte) int {
|
||||
// Don't replace these by a switch without first
|
||||
// confirming that it is being inlined.
|
||||
if b&0x80 == 0x00 {
|
||||
return 1
|
||||
}
|
||||
if b&0xE0 == 0xC0 {
|
||||
return 2
|
||||
}
|
||||
if b&0xF0 == 0xE0 {
|
||||
return 3
|
||||
}
|
||||
if b&0xF8 == 0xF0 {
|
||||
return 4
|
||||
}
|
||||
return 0
|
||||
|
||||
}
|
||||
Vendored
+11
-3
@@ -42,6 +42,17 @@ github.com/gdamore/tcell/v2/terminfo/x/xfce
|
||||
github.com/gdamore/tcell/v2/terminfo/x/xterm
|
||||
github.com/gdamore/tcell/v2/terminfo/x/xterm_kitty
|
||||
github.com/gdamore/tcell/v2/terminfo/x/xterm_termite
|
||||
# github.com/goccy/go-yaml v1.19.2
|
||||
## 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
|
||||
github.com/goccy/go-yaml/scanner
|
||||
github.com/goccy/go-yaml/token
|
||||
# github.com/ktr0731/go-ansisgr v0.1.0
|
||||
## explicit; go 1.19
|
||||
github.com/ktr0731/go-ansisgr
|
||||
@@ -78,6 +89,3 @@ golang.org/x/term
|
||||
golang.org/x/text/encoding
|
||||
golang.org/x/text/encoding/internal/identifier
|
||||
golang.org/x/text/transform
|
||||
# gopkg.in/yaml.v3 v3.0.1
|
||||
## explicit
|
||||
gopkg.in/yaml.v3
|
||||
|
||||
Reference in New Issue
Block a user