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
|
go 1.25.6
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/goccy/go-yaml v1.19.2
|
||||||
|
github.com/ktr0731/go-fuzzyfinder v0.9.0
|
||||||
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/gdamore/encoding v1.0.1 // indirect
|
github.com/gdamore/encoding v1.0.1 // indirect
|
||||||
github.com/gdamore/tcell/v2 v2.6.0 // indirect
|
github.com/gdamore/tcell/v2 v2.6.0 // indirect
|
||||||
github.com/ktr0731/go-ansisgr v0.1.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/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||||
github.com/nsf/termbox-go v1.1.1 // 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/sys v0.32.0 // indirect
|
||||||
golang.org/x/term v0.31.0 // indirect
|
golang.org/x/term v0.31.0 // indirect
|
||||||
golang.org/x/text v0.24.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/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 h1:OKbluoP9VYmJwZwq/iLb4BxwKcwGthaa1YNBJIyCySg=
|
||||||
github.com/gdamore/tcell/v2 v2.6.0/go.mod h1:be9omFATkdr0D9qewWW3d+MEvl5dha+Etb5y65J2H8Y=
|
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 h1:fbuupput8739hQbEmZn1cEKjqQFwtCCZNznnF6ANo5w=
|
||||||
github.com/ktr0731/go-ansisgr v0.1.0/go.mod h1:G9lxwgBwH0iey0Dw5YQd7n6PmQTwTuTM/X5Sgm/UrzE=
|
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=
|
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.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
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=
|
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
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
@@ -9,14 +10,37 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/goccy/go-yaml"
|
||||||
"github.com/ktr0731/go-fuzzyfinder"
|
"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 {
|
type Window struct {
|
||||||
Path string `yaml:"path"`
|
Path string `yaml:"path"`
|
||||||
Name string `yaml:"name"`
|
Name string `yaml:"name"`
|
||||||
Program string `yaml:"program"`
|
Programs Programs `yaml:"program"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w Window) ExpandedPath() string {
|
func (w Window) ExpandedPath() string {
|
||||||
@@ -30,7 +54,7 @@ func (w Window) ExpandedPath() string {
|
|||||||
type Config map[string][]Window
|
type Config map[string][]Window
|
||||||
|
|
||||||
const (
|
const (
|
||||||
tmux = "tmux"
|
tmux = "tmux"
|
||||||
mkdir = "mkdir"
|
mkdir = "mkdir"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -74,7 +98,7 @@ func main() {
|
|||||||
},
|
},
|
||||||
fuzzyfinder.WithPreviewWindow(
|
fuzzyfinder.WithPreviewWindow(
|
||||||
func(i, width, height int) string {
|
func(i, width, height int) string {
|
||||||
width = width/2-4
|
width = width/2 - 4
|
||||||
if i < 0 {
|
if i < 0 {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
@@ -90,8 +114,11 @@ func main() {
|
|||||||
if window.Path != "" {
|
if window.Path != "" {
|
||||||
fmt.Fprintf(&builder, " path: %s\n", window.Path)
|
fmt.Fprintf(&builder, " path: %s\n", window.Path)
|
||||||
}
|
}
|
||||||
if window.Program != "" {
|
if len(window.Programs.programs) != 0 {
|
||||||
fmt.Fprintf(&builder, " prog: %s\n", window.Program)
|
fmt.Fprintf(&builder, " program:\n")
|
||||||
|
}
|
||||||
|
for _, prog := range window.Programs.programs {
|
||||||
|
fmt.Fprintf(&builder, " - %s\n", prog)
|
||||||
}
|
}
|
||||||
fmt.Fprint(&builder, "\n")
|
fmt.Fprint(&builder, "\n")
|
||||||
}
|
}
|
||||||
@@ -164,9 +191,9 @@ func NewWindowInSession(seshName string, window Window) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
if window.Program != "" {
|
for _, prog := range window.Programs.programs {
|
||||||
var carriageReturn = string([]byte{13})
|
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.Stdout = os.Stdout
|
||||||
cmd.Stderr = os.Stderr
|
cmd.Stderr = os.Stderr
|
||||||
cmd.Stdin = os.Stdin
|
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
|
||||||
github.com/gdamore/tcell/v2/terminfo/x/xterm_kitty
|
github.com/gdamore/tcell/v2/terminfo/x/xterm_kitty
|
||||||
github.com/gdamore/tcell/v2/terminfo/x/xterm_termite
|
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
|
# github.com/ktr0731/go-ansisgr v0.1.0
|
||||||
## explicit; go 1.19
|
## explicit; go 1.19
|
||||||
github.com/ktr0731/go-ansisgr
|
github.com/ktr0731/go-ansisgr
|
||||||
@@ -78,6 +89,3 @@ golang.org/x/term
|
|||||||
golang.org/x/text/encoding
|
golang.org/x/text/encoding
|
||||||
golang.org/x/text/encoding/internal/identifier
|
golang.org/x/text/encoding/internal/identifier
|
||||||
golang.org/x/text/transform
|
golang.org/x/text/transform
|
||||||
# gopkg.in/yaml.v3 v3.0.1
|
|
||||||
## explicit
|
|
||||||
gopkg.in/yaml.v3
|
|
||||||
|
|||||||
Reference in New Issue
Block a user