diff --git a/cmd/garm-cli/cmd/templates.go b/cmd/garm-cli/cmd/templates.go index 8b0bebfd2..d6d74b8ec 100644 --- a/cmd/garm-cli/cmd/templates.go +++ b/cmd/garm-cli/cmd/templates.go @@ -14,6 +14,8 @@ package cmd import ( + "bufio" + "errors" "fmt" "os" "os/exec" @@ -23,6 +25,7 @@ import ( "github.com/spf13/cobra" commonParams "github.com/cloudbase/garm-provider-common/params" + apiserverParams "github.com/cloudbase/garm/apiserver/params" apiTemplates "github.com/cloudbase/garm/client/templates" "github.com/cloudbase/garm/cmd/garm-cli/common" "github.com/cloudbase/garm/cmd/garm-cli/editor" @@ -446,24 +449,40 @@ is opened in $EDITOR instead.`, updateReq := apiTemplates.NewUpdateTemplateParams() updateReq.TemplateID = float64(response.Payload.ID) updateReq.Body.Data = []byte(text) - _, err := apiCli.Templates.UpdateTemplate(updateReq, authToken) - return err + if _, err := apiCli.Templates.UpdateTemplate(updateReq, authToken); err != nil { + // Surface the API's error detail (e.g. the template parse error) + // rather than the raw go-swagger response blob. The built-in + // editor reports this in place, keeping the editor open to fix. + return errors.New(templateAPIErrorMessage(err)) + } + return nil } if templateEditExternal { - newContent, changed, err := editWithExternalEditor(original, string(response.Payload.OSType)) - if err != nil { - return err - } - if !changed { - fmt.Println("no changes made") + // The external editor closes after each pass, so on a failed save we + // show the error and offer to reopen with the user's edits intact. + toEdit := original + for { + newContent, _, err := editWithExternalEditor(toEdit, string(response.Payload.OSType)) + if err != nil { + return err + } + if newContent == original { + fmt.Println("no changes made") + return nil + } + if err := updateTemplate(newContent); err != nil { + fmt.Fprintf(os.Stderr, "\ntemplate not saved: %s\nHit enter to retry, ctrl-c to cancel edit ", err) + if _, rerr := bufio.NewReader(os.Stdin).ReadString('\n'); rerr != nil { + // stdin closed (e.g. non-interactive) — nothing to retry with. + return errors.New("template not saved") + } + toEdit = newContent + continue + } + fmt.Println("changes saved successfully") return nil } - if err := updateTemplate(newContent); err != nil { - return fmt.Errorf("failed to update template: %s", err) - } - fmt.Println("changes saved successfully") - return nil } ed := editor.NewEditor() @@ -541,6 +560,36 @@ func editWithExternalEditor(content, osType string) (string, bool, error) { return string(edited), string(edited) != content, nil } +// templateAPIErrorMessage extracts the human-readable message from a template +// API error, preferring the structured APIErrorResponse detail (already +// unmarshaled by the client) over the raw go-swagger response string. +func templateAPIErrorMessage(err error) string { + var updateErr *apiTemplates.UpdateTemplateDefault + if errors.As(err, &updateErr) { + if msg := apiErrorResponseMessage(updateErr.Payload); msg != "" { + return msg + } + } + var createErr *apiTemplates.CreateTemplateDefault + if errors.As(err, &createErr) { + if msg := apiErrorResponseMessage(createErr.Payload); msg != "" { + return msg + } + } + return err.Error() +} + +func apiErrorResponseMessage(p apiserverParams.APIErrorResponse) string { + switch { + case p.Details != "": + return p.Details + case p.Error != "": + return p.Error + default: + return "" + } +} + func init() { templateCreateCmd.Flags().StringVar(&templateName, "name", "", "Name of the template.") templateCreateCmd.Flags().StringVar(&templateDescription, "description", "", "Template description.") diff --git a/go.mod b/go.mod index 8ee6fe0f3..4941b565f 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/BurntSushi/toml v1.6.0 github.com/alecthomas/chroma/v2 v2.27.0 github.com/bradleyfalzon/ghinstallation/v2 v2.19.0 - github.com/cloudbase/garm-provider-common v0.1.10-0.20260627162627-e00d7529cc6f + github.com/cloudbase/garm-provider-common v0.1.10-0.20260707095006-33396540b57b github.com/felixge/httpsnoop v1.1.0 github.com/gdamore/tcell/v2 v2.13.10 github.com/go-gormigrate/gormigrate/v2 v2.1.6 @@ -106,6 +106,7 @@ require ( go.opentelemetry.io/otel/metric v1.44.0 // indirect go.opentelemetry.io/otel/trace v1.44.0 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect + go.yaml.in/yaml/v4 v4.0.0-rc.6 // indirect golang.org/x/net v0.56.0 // indirect golang.org/x/sys v0.46.0 // indirect golang.org/x/text v0.38.0 // indirect diff --git a/go.sum b/go.sum index fbd39e680..4e95b6242 100644 --- a/go.sum +++ b/go.sum @@ -25,8 +25,8 @@ github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04= github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk= github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM= -github.com/cloudbase/garm-provider-common v0.1.10-0.20260627162627-e00d7529cc6f h1:IalGGcSKBGUd+KFFn2OXO6SMcODyCkSO/SMXtm4nwy8= -github.com/cloudbase/garm-provider-common v0.1.10-0.20260627162627-e00d7529cc6f/go.mod h1:i1KXJVzi5ouzbdu5BXjuA+rqk4nxIBVBEYrKReSERIU= +github.com/cloudbase/garm-provider-common v0.1.10-0.20260707095006-33396540b57b h1:70W/+KXIfnNXy8KcT17CdnDG5U7yflS3rxvIgfIKzDw= +github.com/cloudbase/garm-provider-common v0.1.10-0.20260707095006-33396540b57b/go.mod h1:9UvQ/bHlwgSAeLFV94by9M7kRg3d0H3kemkjcFzJMmc= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -226,6 +226,8 @@ go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ= go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +go.yaml.in/yaml/v4 v4.0.0-rc.6 h1:1h7H1ohdUh93/FyE4YaDa1Zh64K6VVbjF4K6WUxMtH4= +go.yaml.in/yaml/v4 v4.0.0-rc.6/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto= diff --git a/internal/templates/templates.go b/internal/templates/templates.go index 8075a6ccb..da59dad5d 100644 --- a/internal/templates/templates.go +++ b/internal/templates/templates.go @@ -11,6 +11,7 @@ import ( "log/slog" "os" "path" + "regexp" "text/template" "github.com/cloudbase/garm-provider-common/cloudconfig" @@ -26,6 +27,7 @@ var Userdata embed.FS type WrapperContext struct { CallbackToken string + CallbackURL string MetadataURL string CACertBundle string } @@ -74,8 +76,62 @@ func GetTemplateContent(osType commonParams.OSType, forge params.EndpointType) ( return data, nil } +// parseTemplate is the single entrypoint used to parse a runner install +// template. Both rendering and validation go through it so that a template +// which validates successfully is guaranteed to parse identically at render +// time (same delimiters, same absence of a custom FuncMap). If the render path +// ever gains custom functions or options, adding them here keeps validation +// faithful automatically. +func parseTemplate(tpl string) (*template.Template, error) { + return template.New("").Parse(tpl) +} + +// tmplParseErrRe matches the prefix text/template adds to parse errors, e.g. +// `template: :3: unexpected "and"`. Our templates are unnamed, so the name +// segment between the two colons is empty. +var tmplParseErrRe = regexp.MustCompile(`^template: [^:]*:(\d+): *(.*)$`) + +// cleanTemplateParseError rewrites the noisy `template: :: ` prefix +// that text/template emits into a friendlier `line : ` form while +// preserving the line number so callers (e.g. the web UI editor) can anchor a +// diagnostic to it. Errors that don't match the expected shape are returned +// verbatim. +func cleanTemplateParseError(err error) string { + msg := err.Error() + if m := tmplParseErrRe.FindStringSubmatch(msg); m != nil { + return fmt.Sprintf("line %s: %s", m[1], m[2]) + } + return msg +} + +// ValidateTemplate performs parse-time validation of a runner install template. +// +// It only parses the template; it deliberately does NOT execute it. Parsing +// validates the grammar (delimiter balance, pipelines, block/end matching and +// references to known functions) without ever inspecting the render context, so +// it is completely independent of the variable data a template is rendered +// against — most notably the per-pool ExtraSpecs/ExtraContext map. A reference +// such as `{{ index .ExtraContext "gitea_cache_address" }}` is valid regardless +// of which keys exist at render time. Executing to validate would be both +// unsound (the context is unknown and variable at save time) and unnecessary +// (a missing map key yields "" rather than an error), which is why parse-time +// is exactly the right level. +func ValidateTemplate(data []byte) error { + // Return typed bad-request errors (not plain fmt.Errorf) so that callers + // which surface this directly get an HTTP 400 with the message in the + // response body. A plain error would fall through handleError's default + // case to an opaque 500 with the details stripped. + if len(data) == 0 { + return runnerErrors.NewBadRequestError("template data is empty") + } + if _, err := parseTemplate(string(data)); err != nil { + return runnerErrors.NewBadRequestError("invalid template: %s", cleanTemplateParseError(err)) + } + return nil +} + func RenderRunnerInstallScript(tpl string, context any) ([]byte, error) { - t, err := template.New("").Parse(tpl) + t, err := parseTemplate(tpl) if err != nil { return nil, fmt.Errorf("failed to parse template: %w", err) } @@ -88,7 +144,7 @@ func RenderRunnerInstallScript(tpl string, context any) ([]byte, error) { return []byte(data), nil } -func RenderRunnerInstallWrapper(ctx context.Context, osType commonParams.OSType, metadataURL, token string) ([]byte, error) { +func RenderRunnerInstallWrapper(ctx context.Context, osType commonParams.OSType, metadataURL, callbackURL, token string) ([]byte, error) { tmpl, err := template.ParseFS(Userdata, "userdata/*_wrapper.tmpl") if err != nil { return nil, fmt.Errorf("failed to parse templates: %w", err) @@ -96,6 +152,7 @@ func RenderRunnerInstallWrapper(ctx context.Context, osType commonParams.OSType, templateCtx := WrapperContext{ MetadataURL: metadataURL, + CallbackURL: callbackURL, CallbackToken: token, } diff --git a/internal/templates/userdata/linux_wrapper.tmpl b/internal/templates/userdata/linux_wrapper.tmpl index b672e8a50..9d4d1e1ab 100644 --- a/internal/templates/userdata/linux_wrapper.tmpl +++ b/internal/templates/userdata/linux_wrapper.tmpl @@ -3,11 +3,31 @@ set -ex set -o pipefail +CALLBACK_URL="{{ .CallbackURL }}" METADATA_URL="{{ .MetadataURL }}" BEARER_TOKEN="{{ .CallbackToken }}" -curl -H "Authorization: Bearer $BEARER_TOKEN" --retry 5 --retry-delay 5 --retry-connrefused --fail $METADATA_URL/install-script/ -o /tmp/real-install.sh -chmod +x /tmp/real-install.sh +function call() { + PAYLOAD="$1" + [[ $CALLBACK_URL =~ ^(.*)/status(/)?$ ]] || CALLBACK_URL="${CALLBACK_URL}/status" + curl --retry 5 --retry-delay 5 --retry-connrefused --fail -s -X POST -d "${PAYLOAD}" -H 'Accept: application/json' -H "Authorization: Bearer ${BEARER_TOKEN}" "${CALLBACK_URL}" || echo "failed to call home: exit code ($?)" +} + +function fail() { + MSG="$1" + call "{\"status\": \"failed\", \"message\": \"$MSG\"}" + exit 1 +} +HTTP_CODE=$(curl -H "Authorization: Bearer $BEARER_TOKEN" --retry 5 --retry-delay 5 --retry-connrefused -s -w "%{http_code}" -o /tmp/real-install.sh "$METADATA_URL/install-script/" || echo "000") +if [ "$HTTP_CODE" != "200" ]; then + BODY=$(cat /tmp/real-install.sh 2>/dev/null || true) + rm -f /tmp/real-install.sh + ERR=$(printf '%s' "$BODY" | sed -n 's/.*"details":"\(.*\)"}[[:space:]]*$/\1/p') + [ -n "$ERR" ] || ERR=$(printf '%s' "$BODY" | sed -n 's/.*"error":"\([^"]*\)".*/\1/p') + fail "failed to fetch install script (HTTP ${HTTP_CODE}): ${ERR}" +fi + +chmod +x /tmp/real-install.sh /tmp/real-install.sh rm -f /tmp/real-install.sh diff --git a/internal/templates/userdata/windows_wrapper.tmpl b/internal/templates/userdata/windows_wrapper.tmpl index 095b905aa..d5b54f856 100644 --- a/internal/templates/userdata/windows_wrapper.tmpl +++ b/internal/templates/userdata/windows_wrapper.tmpl @@ -3,6 +3,10 @@ Set-ExecutionPolicy RemoteSigned -Force -ErrorAction SilentlyContinue $ErrorActionPreference="Stop" +$MetadataUrl = "{{ .MetadataURL }}" +$CallbackUrl = "{{ .CallbackURL }}" +$CallbackToken = "{{ .CallbackToken }}" + function Import-Certificate() { [CmdletBinding()] param ( @@ -72,6 +76,24 @@ function Start-ExecuteWithRetry { } } +function Send-Failure() { + [CmdletBinding()] + param( + [Parameter(Mandatory=$true)] + [string]$Message + ) + $statusUrl = $CallbackUrl + if ($statusUrl -notmatch '/status/?$') { + $statusUrl = "$statusUrl/status" + } + $payload = @{ status = "failed"; message = $Message } | ConvertTo-Json -Compress + try { + Invoke-WebRequest -UseBasicParsing -Method Post -Uri $statusUrl -Headers @{"Accept"="application/json"; "Authorization"="Bearer $CallbackToken"} -Body $payload | Out-Null + } catch { + Write-Output "failed to report failure status: $($_.Exception.Message)" + } +} + {{- if .CACertBundle }} $caBundleJson = '{{.CACertBundle}}' $converted = ConvertFrom-Json $caBundleJson @@ -82,8 +104,31 @@ foreach ($i in $converted.psobject.Properties){ {{- end }} $installScript = (Join-Path $env:TMP "garm-install.ps1") -Start-ExecuteWithRetry -ScriptBlock { - wget -UseBasicParsing -Headers @{"Accept"="application/json"; "Authorization"="Bearer {{ .CallbackToken }}"} -Uri {{ .MetadataURL }}/install-script/ -OutFile $installScript -} -MaxRetryCount 5 -RetryInterval 5 -RetryMessage "Retrying download of runner install script..." +try { + Start-ExecuteWithRetry -ScriptBlock { + wget -UseBasicParsing -Headers @{"Accept"="application/json"; "Authorization"="Bearer $CallbackToken"} -Uri "$MetadataUrl/install-script/" -OutFile $installScript + } -MaxRetryCount 5 -RetryInterval 5 -RetryMessage "Retrying download of runner install script..." +} catch { + $detail = $_.Exception.Message + $resp = $_.Exception.Response + if ($resp -ne $null) { + try { + $reader = New-Object System.IO.StreamReader($resp.GetResponseStream()) + $bodyText = $reader.ReadToEnd() + if (![string]::IsNullOrWhiteSpace($bodyText)) { + try { + $parsed = $bodyText | ConvertFrom-Json + if ($parsed.details) { $detail = $parsed.details } + elseif ($parsed.error) { $detail = $parsed.error } + else { $detail = $bodyText } + } catch { + $detail = $bodyText + } + } + } catch { } + } + Send-Failure "failed to fetch install script: $detail" + exit 1 +} -powershell.exe -Sta -NonInteractive -ExecutionPolicy RemoteSigned -File $installScript \ No newline at end of file +powershell.exe -Sta -NonInteractive -ExecutionPolicy RemoteSigned -File $installScript diff --git a/runner/metadata.go b/runner/metadata.go index 014eaa068..afefc2f80 100644 --- a/runner/metadata.go +++ b/runner/metadata.go @@ -470,7 +470,11 @@ func (r *Runner) GetRunnerInstallScript(ctx context.Context) ([]byte, error) { installScript, err := templates.RenderRunnerInstallScript(string(tplBytes), tplCtx) if err != nil { - return nil, fmt.Errorf("failed to get runner install script: %w", err) + // A render failure is deterministic (the template is broken), so return + // a bad-request rather than a generic 500 error. A 500 error is opaque + // to the caller. The install wrapper surfaces this detail as the runner's + // "failed" status so the instance doesn't hang in "installing" forever. + return nil, runnerErrors.NewBadRequestError("failed to render runner install script: %s", err) } return installScript, nil } diff --git a/runner/templates.go b/runner/templates.go index 9d2906fe7..9d996375d 100644 --- a/runner/templates.go +++ b/runner/templates.go @@ -34,6 +34,13 @@ func (r *Runner) CreateTemplate(ctx context.Context, param params.CreateTemplate return params.Template{}, runnerErrors.NewBadRequestError("invalid create params %q", err) } + // ValidateTemplate returns a typed bad-request error carrying the parse + // error message. Wrap with %w to add call-site context for tracking while + // preserving the bad-request type (so it still maps to HTTP 400). + if err := templates.ValidateTemplate(param.Data); err != nil { + return params.Template{}, fmt.Errorf("failed to validate template: %w", err) + } + template, err := r.store.CreateTemplate(ctx, param) if err != nil { return params.Template{}, fmt.Errorf("failed to create template: %w", err) @@ -172,6 +179,15 @@ func (r *Runner) UpdateTemplate(ctx context.Context, id uint, param params.Updat return params.Template{}, runnerErrors.NewBadRequestError("invalid update params: %q", err) } + // Data is optional on update (name/description-only updates leave it nil, + // and the store only writes it when non-empty), so only validate the + // template body when the caller is actually changing it. + if len(param.Data) > 0 { + if err := templates.ValidateTemplate(param.Data); err != nil { + return params.Template{}, fmt.Errorf("failed to validate template: %w", err) + } + } + template, err := r.store.UpdateTemplate(ctx, id, param) if err != nil { return params.Template{}, fmt.Errorf("failed to update template: %w", err) diff --git a/util/util.go b/util/util.go index 374313a13..f286c2935 100644 --- a/util/util.go +++ b/util/util.go @@ -151,7 +151,7 @@ func MaybeAddWrapperToExtraSpecs(ctx context.Context, param commonParams.Bootstr return param } - wrapper, err := templates.RenderRunnerInstallWrapper(ctx, param.OSType, param.MetadataURL, param.InstanceToken) + wrapper, err := templates.RenderRunnerInstallWrapper(ctx, param.OSType, param.MetadataURL, param.CallbackURL, param.InstanceToken) if err != nil { slog.WarnContext(ctx, "failed to get runner install wrapper", "os_type", param.OSType, "error", err) return param diff --git a/vendor/github.com/cloudbase/garm-provider-common/cloudconfig/cloudconfig.go b/vendor/github.com/cloudbase/garm-provider-common/cloudconfig/cloudconfig.go index a49fe42a1..eb73bc4e9 100644 --- a/vendor/github.com/cloudbase/garm-provider-common/cloudconfig/cloudconfig.go +++ b/vendor/github.com/cloudbase/garm-provider-common/cloudconfig/cloudconfig.go @@ -23,8 +23,8 @@ import ( "github.com/cloudbase/garm-provider-common/defaults" + "go.yaml.in/yaml/v4" "github.com/pkg/errors" - "gopkg.in/yaml.v3" ) func NewDefaultCloudInitConfig() *CloudInit { @@ -78,6 +78,7 @@ type CloudInit struct { RunCmd []string `yaml:"runcmd,omitempty"` WriteFiles []File `yaml:"write_files,omitempty"` CACerts CACerts `yaml:"ca-certs,omitempty"` + BootCmd []string `yaml:"bootcmd,omitempty"` } type CACerts struct { @@ -142,10 +143,15 @@ func (c *CloudInit) AddPackage(pkgs ...string) { func (c *CloudInit) AddRunCmd(cmd string) { c.mux.Lock() defer c.mux.Unlock() - c.RunCmd = append(c.RunCmd, cmd) } +func (c *CloudInit) AddBootCmd(cmd string) { + c.mux.Lock() + defer c.mux.Unlock() + c.BootCmd = append(c.BootCmd, cmd) +} + func (c *CloudInit) AddFile(contents []byte, path, owner, permissions string) { c.mux.Lock() defer c.mux.Unlock() diff --git a/vendor/github.com/cloudbase/garm-provider-common/cloudconfig/util.go b/vendor/github.com/cloudbase/garm-provider-common/cloudconfig/util.go index 2a8f4a345..bf4021eaa 100644 --- a/vendor/github.com/cloudbase/garm-provider-common/cloudconfig/util.go +++ b/vendor/github.com/cloudbase/garm-provider-common/cloudconfig/util.go @@ -117,7 +117,7 @@ func GetRunnerInstallScript(bootstrapParams params.BootstrapInstance, tools para UseJITConfig: bootstrapParams.JitConfigEnabled, } - if bootstrapParams.CACertBundle != nil && len(bootstrapParams.CACertBundle) > 0 { + if len(bootstrapParams.CACertBundle) > 0 { installRunnerParams.CABundle = string(bootstrapParams.CACertBundle) } @@ -162,12 +162,16 @@ func GetCloudInitConfig(bootstrapParams params.BootstrapInstance, installScript cloudCfg.AddFile(installScript, "/install_runner.sh", "root:root", "755") cloudCfg.AddRunCmd(fmt.Sprintf("su -l -c /install_runner.sh %s", defaults.DefaultUser)) cloudCfg.AddRunCmd("rm -f /install_runner.sh") - if bootstrapParams.CACertBundle != nil && len(bootstrapParams.CACertBundle) > 0 { + if len(bootstrapParams.CACertBundle) > 0 { if err := cloudCfg.AddCACert(bootstrapParams.CACertBundle); err != nil { return "", errors.Wrap(err, "adding CA cert bundle") } } + if proxyCfg, ok := bootstrapParams.ProxyConfig.GetBootCmd(); ok { + cloudCfg.AddBootCmd(proxyCfg) + } + asStr, err := cloudCfg.Serialize() if err != nil { return "", errors.Wrap(err, "creating cloud config") diff --git a/vendor/github.com/cloudbase/garm-provider-common/params/params.go b/vendor/github.com/cloudbase/garm-provider-common/params/params.go index 0a63f7098..a4e55291e 100644 --- a/vendor/github.com/cloudbase/garm-provider-common/params/params.go +++ b/vendor/github.com/cloudbase/garm-provider-common/params/params.go @@ -15,7 +15,9 @@ package params import ( + "bytes" "encoding/json" + "text/template" ) type ( @@ -56,6 +58,62 @@ const ( PrivateAddress AddressType = "private" ) +var bootCmdProxyTemplate = `{{- if .HTTPProxy }} +HTTP_PROXY="{{.HTTPProxy}}" +{{- end}} +{{- if .HTTPSProxy }} +HTTPS_PROXY="{{.HTTPSProxy}}" +{{- end}} +{{- if .NoProxy }} +NO_PROXY="{{.NoProxy}}" +{{- end}} +if [ -d "/etc/apt/apt.conf.d" ]; then + APT_PROXY_CONF="/etc/apt/apt.conf.d/95garm-proxy" + : > "$APT_PROXY_CONF" + [ -n "$HTTP_PROXY" ] && printf 'Acquire::http::Proxy "%s";\n' "$HTTP_PROXY" >> "$APT_PROXY_CONF" + [ -n "$HTTPS_PROXY" ] && printf 'Acquire::https::Proxy "%s";\n' "$HTTPS_PROXY" >> "$APT_PROXY_CONF" +elif [ -f /etc/dnf/dnf.conf ]; then + PROXY="${HTTP_PROXY:-$HTTPS_PROXY}" + [ -n "$PROXY" ] && grep -q '^proxy=' /etc/dnf/dnf.conf || echo "proxy=$PROXY" >> /etc/dnf/dnf.conf +elif [ -f /etc/yum.conf ]; then + PROXY="${HTTP_PROXY:-$HTTPS_PROXY}" + [ -n "$PROXY" ] && grep -q '^proxy=' /etc/yum.conf || echo "proxy=$PROXY" >> /etc/yum.conf +elif [ -d /etc/zypp ]; then + printf 'PROXY_ENABLED="yes"\n' > /etc/sysconfig/proxy + [ -n "$HTTP_PROXY" ] && printf 'HTTP_PROXY="%s"\n' "$HTTP_PROXY" >> /etc/sysconfig/proxy + [ -n "$HTTPS_PROXY" ] && printf 'HTTPS_PROXY="%s"\n' "$HTTPS_PROXY" >> /etc/sysconfig/proxy + [ -n "$NO_PROXY" ] && printf 'NO_PROXY="%s"\n' "$NO_PROXY" >> /etc/sysconfig/proxy +fi +` + +type ProxyConfig struct { + HTTPProxy string `json:"http_proxy"` + HTTPSProxy string `json:"https_proxy"` + NoProxy string `json:"no_proxy"` +} + +func (p ProxyConfig) GetBootCmd() (string, bool) { + if !p.HasProxy() { + return "", false + } + + tmpl, err := template.New("bootcmd-proxy").Parse(bootCmdProxyTemplate) + if err != nil { + return "", false + } + + var buf bytes.Buffer + if err := tmpl.Execute(&buf, p); err != nil { + return "", false + } + + return buf.String(), true +} + +func (p ProxyConfig) HasProxy() bool { + return p.HTTPProxy != "" || p.HTTPSProxy != "" +} + type UserDataOptions struct { DisableUpdatesOnBoot bool `json:"disable_updates_on_boot"` ExtraPackages []string `json:"extra_packages"` @@ -124,6 +182,10 @@ type BootstrapInstance struct { // from the metadata service instead of the runner registration token. The runner registration token // is not available if the runner is configured to use JIT. JitConfigEnabled bool `json:"jit_config_enabled"` + + // ProxyConfig is the proxy configuration that gets added to the cloud-init + // config. + ProxyConfig ProxyConfig `json:"proxy_config"` } type Address struct { diff --git a/vendor/go.yaml.in/yaml/v4/.gitignore b/vendor/go.yaml.in/yaml/v4/.gitignore new file mode 100644 index 000000000..5bdcb9a09 --- /dev/null +++ b/vendor/go.yaml.in/yaml/v4/.gitignore @@ -0,0 +1,11 @@ +# Copyright 2025 The go-yaml Project Contributors +# SPDX-License-Identifier: Apache-2.0 + +/.cache/ +/.claude/ +/note/ +/worktree/ +/yts/testdata/ +/go-yaml +/*.md +/*.yaml diff --git a/vendor/go.yaml.in/yaml/v4/.typos.toml b/vendor/go.yaml.in/yaml/v4/.typos.toml new file mode 100644 index 000000000..e0842f9a3 --- /dev/null +++ b/vendor/go.yaml.in/yaml/v4/.typos.toml @@ -0,0 +1,36 @@ +# Copyright 2025 The go-yaml Project Contributors +# SPDX-License-Identifier: Apache-2.0 + +# This is the configuration file of typos (spell checker) +# https://github.com/crate-ci/typos + +[files] +# excluded file +extend-exclude = [ + "yts/testdata", # third-party test data +] + +# setting for Go files configuration +[type.go] +extend-ignore-re = [ + 'ba-dum-tss\W+', # this one can be found in test files + '"yYnNtTfFoO', # this one can be found in test files + 'ba\?r', # this one can be found in test files +] + +[type.go.extend-words] +# Here is a list of words we want to ignore in Go files +typ = "typ" # commonly used abbreviation for "type" in Go as "type" is a reserved identifier + +# setting for YAML files configuration +[type.yaml] +extend-ignore-re = [ + 'ba\?r', # this one can be found in test files +] + +[default.extend-words] +caf = "caf" # part of "café" shown as "caf\u00e9" in Unicode escape examples +deprecat = "deprecat" # Used as part of a command in a docs/ file +Desolver = "Desolver" # Type name: inverse of Resolver (removes tags) +Desolve = "Desolve" # Function name: inverse of Resolve +desolve = "desolve" # Function name variants diff --git a/vendor/go.yaml.in/yaml/v4/GNUmakefile b/vendor/go.yaml.in/yaml/v4/GNUmakefile new file mode 100644 index 000000000..fb46f814c --- /dev/null +++ b/vendor/go.yaml.in/yaml/v4/GNUmakefile @@ -0,0 +1,191 @@ +# Copyright 2025 The go-yaml Project Contributors +# SPDX-License-Identifier: Apache-2.0 + +# Auto-install https://github.com/makeplus/makes at specific commit: +MAKES := .cache/makes +MAKES-LOCAL := .cache/local +MAKES-COMMIT ?= 4e48a743c3652b88adc4a257398d895a801e6d11 +$(shell [ -d $(MAKES) ] || ( \ + git clone -q https://github.com/makeplus/makes $(MAKES) && \ + git -C $(MAKES) reset -q --hard $(MAKES-COMMIT))) +ifneq ($(shell git -C $(MAKES) rev-parse HEAD), \ + $(shell git -C $(MAKES) rev-parse $(MAKES-COMMIT))) +$(error $(MAKES) is not at the correct commit: $(MAKES-COMMIT). \ + Remove $(MAKES) and try again.) +endif + +include $(MAKES)/init.mk +include $(MAKES)/shellcheck.mk + +# Auto-install go unless GO_YAML_PATH is set: +ifdef GO_YAML_PATH +override export PATH := $(GO_YAML_PATH):$(PATH) +else +GO-VERSION ?= 1.25.5 +endif +GO-VERSION-NEEDED := $(GO-VERSION) + +# yaml-test-suite info: +YTS-URL ?= https://github.com/yaml/yaml-test-suite +YTS-TAG ?= data-2022-01-17 +YTS-DIR := yts/testdata/$(YTS-TAG) + +CLI-BINARY := go-yaml + +# Pager for viewing documentation: +PAGER ?= less -FRX + +# Setup and include go.mk and shell.mk: + +# We need to limit `find` to avoid dirs like `.cache/` and any git worktrees, +# as this makes `make` operations very slow: +REPO-DIRS := $(shell find * -maxdepth 0 -type d \ + ! -exec test -f {}/.git \; -print) +GO-FILES := $(shell find $(REPO-DIRS) -name '*.go') + +ifndef GO-VERSION-NEEDED +GO-NO-DEP-GO := true +endif + +include $(MAKES)/go.mk + +# Set this from the `make` command to override: +GOLANGCI-LINT-VERSION ?= v2.8.0 +GOLANGCI-LINT-INSTALLER := \ + https://github.com/golangci/golangci-lint/raw/main/install.sh +GOLANGCI-LINT := $(LOCAL-BIN)/golangci-lint +GOLANGCI-LINT-VERSIONED := $(GOLANGCI-LINT)-$(GOLANGCI-LINT-VERSION) + +SHELL-DEPS += $(GOLANGCI-LINT-VERSIONED) + +ifdef GO-VERSION-NEEDED +GO-DEPS += $(GO) +else +SHELL-DEPS := $(filter-out $(GO),$(SHELL-DEPS)) +endif + +SHELL-NAME := makes go-yaml +include $(MAKES)/clean.mk +include $(MAKES)/shell.mk + +MAKES-CLEAN := $(CLI-BINARY) $(GOLANGCI-LINT) +MAKES-REALCLEAN := $(dir $(YTS-DIR)) + +SHELL-SCRIPTS = \ + util/common.bash \ + $(shell grep -rl '^.!/usr/bin/env bash' util | \ + grep -v '\.sw') + +COVER-TESTS := \ + . \ + ./cmd/... \ + ./internal/... \ + +# v=1 for verbose +MAKE := $(MAKE) --no-print-directory + +v ?= +cover ?= +fuzz ?= +time ?= 60s +opts ?= + +TEST-OPTS := \ +$(if $v, -v)\ +$(if $(cover), --cover)\ +$(if $(fuzz), --fuzz=FuzzEncodeFromJSON --fuzztime=$(time))\ +$(if $(opts), $(opts))\ + +# Test rules: +test: test-main test-internal test-cmd test-yts-all test-shell + @echo 'ALL TESTS PASS' + +check: + $(MAKE) fmt + $(MAKE) tidy + $(MAKE) lint + $(MAKE) test + +test-main: $(GO-DEPS) + go test .$(TEST-OPTS) + @echo 'ALL MAIN FILES PASS' + +test-cmd: $(GO-DEPS) + go test ./cmd/...$(TEST-OPTS) + @echo 'ALL CMD FILES PASS' + +test-internal: $(GO-DEPS) + go test ./internal/...$(TEST-OPTS) + @echo 'ALL INTERNAL FILES PASS' + +test-cover: $(GO-DEPS) + go test . $(COVER-TESTS) -vet=off --cover$(TEST-OPTS) + +test-yts: $(GO-DEPS) $(YTS-DIR) + go test ./yts$(TEST-OPTS) + +test-yts-all: $(GO-DEPS) $(YTS-DIR) + @echo 'Testing yaml-test-suite' + util/yaml-test-suite all + +test-yts-fail: $(GO-DEPS) $(YTS-DIR) + @echo 'Testing yaml-test-suite failures' + util/yaml-test-suite fail + +test-shell: $(SHELLCHECK) + shellcheck $(SHELL-SCRIPTS) + @echo 'ALL SHELL FILES PASS' + +test-count: $(GO-DEPS) + util/test-count + +yts-dir: $(YTS-DIR) + +get-test-data: $(YTS-DIR) + +# Install golangci-lint for GitHub Actions: +golangci-lint-install: $(GOLANGCI-LINT) + +fmt: $(GOLANGCI-LINT-VERSIONED) + $< fmt ./... + +lint: $(GOLANGCI-LINT-VERSIONED) + $< run ./... + +tidy: $(GO-DEPS) + go mod tidy + +cli: $(CLI-BINARY) + +$(CLI-BINARY): $(GO) + go build -o $@ ./cmd/$@ + +run-examples: $(GO) + @for dir in example/*/; do \ + (set -x; go run "$${dir}main.go") || \ + { echo "$$dir failed"; break; }; \ + done + +# CLI documentation (go doc) - view in terminal: +doc: $(GO-DEPS) + @go doc -all . | $(PAGER) + +# HTTP documentation server - opens browser: +doc-http: $(GO-DEPS) + go doc -http -all + +# Setup rules: +$(YTS-DIR): + git clone -q $(YTS-URL) $@ + git -C $@ checkout -q $(YTS-TAG) + +# Downloads golangci-lint binary and moves to versioned path +# (.cache/local/bin/golangci-lint-). +$(GOLANGCI-LINT-VERSIONED): $(GO-DEPS) + curl -sSfL $(GOLANGCI-LINT-INSTALLER) | \ + bash -s -- -b $(LOCAL-BIN) $(GOLANGCI-LINT-VERSION) + mv $(GOLANGCI-LINT) $@ + +# Moves golangci-lint- to golangci-lint for CI requirement +$(GOLANGCI-LINT): $(GOLANGCI-LINT-VERSIONED) + cp $< $@ diff --git a/vendor/go.yaml.in/yaml/v4/LICENSE b/vendor/go.yaml.in/yaml/v4/LICENSE new file mode 100644 index 000000000..b0fa97112 --- /dev/null +++ b/vendor/go.yaml.in/yaml/v4/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2025 - The go-yaml Project Contributors + + 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. diff --git a/vendor/go.yaml.in/yaml/v4/NOTICE b/vendor/go.yaml.in/yaml/v4/NOTICE new file mode 100644 index 000000000..15093073b --- /dev/null +++ b/vendor/go.yaml.in/yaml/v4/NOTICE @@ -0,0 +1,21 @@ +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 +starting in 2011 when the project was ported over: + +- internal/libyaml/api.go +- internal/libyaml/emitter.go +- internal/libyaml/parser.go +- internal/libyaml/reader.go +- internal/libyaml/scanner.go +- internal/libyaml/writer.go +- internal/libyaml/yaml.go +- internal/libyaml/yamlprivate.go + +Copyright 2006-2010 Kirill Simonov +https://opensource.org/license/mit + +All the remaining project files are covered by the Apache license: + +Copyright 2011-2019 Canonical Ltd +Copyright 2025 The go-yaml Project Contributors +http://www.apache.org/licenses/LICENSE-2.0 diff --git a/vendor/go.yaml.in/yaml/v4/doc.go b/vendor/go.yaml.in/yaml/v4/doc.go new file mode 100644 index 000000000..3aa872d27 --- /dev/null +++ b/vendor/go.yaml.in/yaml/v4/doc.go @@ -0,0 +1,113 @@ +// Copyright 2025 The go-yaml Project Contributors +// SPDX-License-Identifier: Apache-2.0 + +// Package yaml implements YAML 1.1/1.2 encoding and decoding for Go programs. +// +// # Quick Start +// +// For simple encoding and decoding, use [Unmarshal] and [Marshal]: +// +// type Config struct { +// Name string `yaml:"name"` +// Version string `yaml:"version"` +// } +// +// // Decode YAML to Go struct +// var config Config +// err := yaml.Unmarshal(yamlData, &config) +// +// // Encode Go struct to YAML +// data, err := yaml.Marshal(&config) +// +// For encoding/decoding with options, use [Load] and [Dump]: +// +// // Decode with strict field checking +// err := yaml.Load(data, &config, yaml.WithKnownFields()) +// +// // Encode with custom indent +// data, err := yaml.Dump(&config, yaml.WithIndent(2)) +// +// // Decode all documents from multi-document stream +// var docs []Config +// err := yaml.Load(multiDocYAML, &docs, yaml.WithAllDocuments()) +// +// // Encode multiple documents as multi-document stream +// docs := []Config{config1, config2} +// data, err := yaml.Dump(docs, yaml.WithAllDocuments()) +// +// # Streaming with Loader and Dumper +// +// For multi-document streams or when you need custom options, use [Loader] and [Dumper]: +// +// // Load multiple documents from a stream +// loader, err := yaml.NewLoader(reader) +// if err != nil { +// log.Fatal(err) +// } +// for { +// var doc any +// if err := loader.Load(&doc); err == io.EOF { +// break +// } else if err != nil { +// log.Fatal(err) +// } +// // Process document... +// } +// +// // Dump multiple documents to a stream +// dumper, err := yaml.NewDumper(writer, yaml.WithIndent(2)) +// if err != nil { +// log.Fatal(err) +// } +// dumper.Dump(&doc1) +// dumper.Dump(&doc2) +// dumper.Close() +// +// # Options System +// +// Configure YAML processing behavior with functional options: +// +// yaml.NewDumper(w, +// yaml.WithIndent(2), // Indentation spacing +// yaml.WithCompactSeqIndent(), // Compact sequences (defaults to true) +// yaml.WithLineWidth(80), // Line wrapping width +// yaml.WithUnicode(false), // Escape non-ASCII (override default true) +// yaml.WithKnownFields(), // Strict field checking (defaults to true) +// yaml.WithUniqueKeys(), // Prevent duplicate keys (defaults to true) +// yaml.WithSingleDocument(), // Single document mode +// ) +// +// Or use version-specific option presets for consistent formatting: +// +// yaml.NewDumper(w, yaml.WithV3Defaults()) +// +// Options can be combined and later options override earlier ones: +// +// // Start with v3 defaults, then override indent +// yaml.NewDumper(w, +// yaml.WithV3Defaults(), +// yaml.WithIndent(2), +// ) +// +// Load options from YAML configuration files: +// +// opts, err := yaml.OptsYAML(configYAML) +// dumper, err := yaml.NewDumper(w, opts) +// +// # YAML Compatibility +// +// This package supports most of YAML 1.2, but preserves some YAML 1.1 +// behavior for backward compatibility: +// +// - YAML 1.1 booleans (yes/no, on/off) are supported when decoding into +// typed bool values, otherwise treated as strings +// - Octals can use 0777 format (YAML 1.1) or 0o777 format (YAML 1.2) +// - Base-60 floats are not supported (removed in YAML 1.2) +// +// # Version Defaults +// +// [NewLoader] and [NewDumper] use v4 defaults (2-space indentation, compact +// sequences). The older [Marshal] and [Unmarshal] functions use v3 defaults +// for backward compatibility. Use the options system to select different +// version defaults if needed. +package yaml diff --git a/vendor/go.yaml.in/yaml/v4/internal/libyaml/README.md b/vendor/go.yaml.in/yaml/v4/internal/libyaml/README.md new file mode 100644 index 000000000..cfcbcc984 --- /dev/null +++ b/vendor/go.yaml.in/yaml/v4/internal/libyaml/README.md @@ -0,0 +1,549 @@ +# internal/libyaml + +This package provides low-level YAML processing functionality through a 3-stage +pipeline: Scanner → Parser → Emitter. +It implements the libyaml C library functionality in Go. + +## Directory Overview + +The `internal/libyaml` package implements the core YAML processing stages: + +1. **Scanner** - Tokenizes YAML text into tokens +2. **Parser** - Converts tokens into events following YAML grammar rules +3. **Emitter** - Serializes events back into YAML text + +## File Organization + +### Main Source Files + +- **scanner.go** - YAML scanner/tokenizer implementation +- **parser.go** - YAML parser (tokens → events) +- **emitter.go** - YAML emitter (events → YAML output) +- **api.go** - Public API for Parser and Emitter types +- **yaml.go** - Core types and constants (Event, Token, enums) +- **reader.go** - Input handling and encoding detection +- **writer.go** - Output handling +- **util.go** - Internal types and helper functions + +### Test Files + +- **scanner_test.go** - Scanner tests +- **parser_test.go** - Parser tests +- **emitter_test.go** - Emitter tests +- **api_test.go** - API tests +- **yaml_test.go** - Utility function tests +- **reader_test.go** - Reader tests +- **writer_test.go** - Writer tests +- **util_test.go** - Character classification tests +- **loader_test.go** - Data loader scalar resolution tests +- **yamldatatest_test.go** - YAML test data loading framework +- **yamldatatest_loader.go** - YAML test data loader with scalar type + resolution (exported for reuse) + +### Test Data Files (in `testdata/`) + +- **scanner.yaml** - Scanner test cases +- **parser.yaml** - Parser test cases +- **emitter.yaml** - Emitter test cases +- **api.yaml** - API test cases +- **yaml.yaml** - Utility function test cases +- **reader.yaml** - Reader test cases +- **writer.yaml** - Writer test cases +- **util.yaml** - Character classification test cases +- **loader.yaml** - Data loader scalar resolution test cases + +## Processing Pipeline + +### 1. Scanner (scanner.go) + +The scanner converts YAML text into tokens. + +**Input**: Raw YAML text (string or []byte) +**Output**: Stream of tokens + +**Token types include**: +- `SCALAR_TOKEN` - Plain, quoted, or block scalar values +- `KEY_TOKEN`, `VALUE_TOKEN` - Mapping key/value indicators +- `BLOCK_MAPPING_START_TOKEN`, `FLOW_MAPPING_START_TOKEN` - Mapping delimiters +- `BLOCK_SEQUENCE_START_TOKEN`, `FLOW_SEQUENCE_START_TOKEN` - Sequence delimiters +- `ANCHOR_TOKEN`, `ALIAS_TOKEN` - Anchor definitions and references +- `TAG_TOKEN` - Type tags +- `DOCUMENT_START_TOKEN`, `DOCUMENT_END_TOKEN` - Document boundaries + +**Responsibilities**: +- Character encoding detection (UTF-8, UTF-16LE, UTF-16BE) +- Line break normalization +- Indentation tracking +- Quote and escape sequence handling + +### 2. Parser (parser.go) + +The parser converts tokens into events following YAML grammar rules. + +**Input**: Stream of tokens from Scanner +**Output**: Stream of events + +**Event types include**: +- `STREAM_START_EVENT`, `STREAM_END_EVENT` - Stream boundaries +- `DOCUMENT_START_EVENT`, `DOCUMENT_END_EVENT` - Document boundaries +- `SCALAR_EVENT` - Scalar values +- `MAPPING_START_EVENT`, `MAPPING_END_EVENT` - Mapping boundaries +- `SEQUENCE_START_EVENT`, `SEQUENCE_END_EVENT` - Sequence boundaries +- `ALIAS_EVENT` - Anchor references + +**Responsibilities**: +- Implementing YAML grammar and validation +- Managing document directives (%YAML, %TAG) +- Resolving anchors and aliases +- Tracking implicit vs explicit markers +- Style preservation (plain, single-quoted, double-quoted, literal, folded) + +### 3. Emitter (emitter.go) + +The emitter converts events back into YAML text. + +**Input**: Stream of events +**Output**: YAML text + +**Responsibilities**: +- Style selection (plain/quoted scalars, block/flow collections) +- Formatting control (canonical mode, indentation, line width) +- Character encoding +- Anchor and tag serialization +- Document marker generation (---, ...) + +**Configuration options**: +- `Canonical` - Emit in canonical YAML form +- `Indent` - Indentation width (2-9 spaces) +- `Width` - Line width (-1 for unlimited) +- `Unicode` - Enable Unicode character output +- `LineBreak` - Line break style (LN, CR, CRLN) + +## Testing Framework + +### Test Architecture + +The testing framework uses a data-driven approach: + +1. **Test data** is stored in YAML files in the `testdata/` directory +2. **Test logic** is implemented in Go files (`*_test.go`) +3. **One-to-one pairing**: Each `testdata/foo.yaml` has a corresponding + `foo_test.go` + +**Benefits**: +- Easy to add new test cases without writing Go code +- Test data is human-readable and self-documenting +- Test logic is reusable across many test cases +- Test data is separated from test code for clarity +- Tests can become a common suite for multiple YAML frameworks + +### Test Data Files + +Each YAML file contains test cases for a specific component: + +- **scanner.yaml** - Scanner/tokenization tests + - Token sequence verification + - Token property validation (value, style) + - Error detection + +- **parser.yaml** - Parser/event generation tests + - Event sequence verification + - Event property validation (anchor, tag, value, directives) + - Error detection + +- **emitter.yaml** - Emitter/serialization tests + - Event-to-YAML conversion + - Configuration options testing + - Roundtrip testing (parse → emit) + - Writer integration + +- **api.yaml** - API constructor and method tests + - Constructor validation + - Method behavior and state changes + - Panic conditions + - Cleanup verification + +- **yaml.yaml** - Utility function tests + - Enum String() methods + - Style accessor methods + +- **reader.yaml** - Reader/input handling tests + - Encoding detection (UTF-8, UTF-16LE, UTF-16BE) + - Buffer management + - Error handling + +- **writer.yaml** - Writer/output handling tests + - Buffer flushing + - Output handlers (string, io.Writer) + - Error conditions + +- **util.yaml** - Character classification tests + - Character type predicates (isAlpha, isDigit, isHex, etc.) + - Character conversion functions (asDigit, asHex, width) + - Unicode handling + +- **loader.yaml** - Data loader scalar resolution tests + - Numeric type resolution (integers, floats) + - Boolean and null value handling + - String vs numeric type disambiguation + - Mixed-type collections + +### Test Framework Implementation + +The test framework is implemented in `testdata_test.go`: + +**Core functions**: +- `LoadAny(data []byte) (interface{}, error)` - Parses YAML using production + loader with scalar type resolution (exported from loader.go) +- `UnmarshalStruct(target interface{}, data map[string]interface{}) error` - + Populates structs (exported) +- `LoadTestCases(filename string) ([]TestCase, error)` - Loads and parses + test YAML files + +**Core types**: +- `TestCase` struct - Umbrella structure containing fields for all test types + - Uses `interface{}` for flexible field types + - Post-processing converts generic fields to specific types + +**Post-processing**: +After loading, the framework processes test data: +- Converts `Want` (interface{}) to `WantEvents`, `WantTokens`, or `WantSpecs` + based on test type +- Converts `Want` (interface{}) to `WantContains` (handles both scalar and + sequence) +- Converts `Checks` to field validation specifications + +### Test Types + +#### Scanner Tests + +**scan-tokens** - Verify token sequence + +```yaml +- scan-tokens: + name: Simple scalar + yaml: |- + hello + want: + - STREAM_START_TOKEN + - SCALAR_TOKEN + - STREAM_END_TOKEN +``` + +**scan-tokens-detailed** - Verify token properties + +```yaml +- scan-tokens-detailed: + name: Single quoted scalar + yaml: |- + 'hello world' + want: + - STREAM_START_TOKEN + - SCALAR_TOKEN: + style: SINGLE_QUOTED_SCALAR_STYLE + value: hello world + - STREAM_END_TOKEN +``` + +**scan-error** - Verify error detection + +```yaml +- scan-error: + name: Invalid character + yaml: "\x01" +``` + +#### Parser Tests + +**parse-events** - Verify event sequence + +```yaml +- parse-events: + name: Simple mapping + yaml: | + key: value + want: + - STREAM_START_EVENT + - DOCUMENT_START_EVENT + - MAPPING_START_EVENT + - SCALAR_EVENT + - SCALAR_EVENT + - MAPPING_END_EVENT + - DOCUMENT_END_EVENT + - STREAM_END_EVENT +``` + +**parse-events-detailed** - Verify event properties + +```yaml +- parse-events-detailed: + name: Anchor and alias + yaml: | + - &anchor value + - *anchor + want: + - STREAM_START_EVENT + - DOCUMENT_START_EVENT + - SEQUENCE_START_EVENT + - SCALAR_EVENT: + anchor: anchor + value: value + - ALIAS_EVENT: + anchor: anchor + - SEQUENCE_END_EVENT + - DOCUMENT_END_EVENT + - STREAM_END_EVENT +``` + +**parse-error** - Verify error detection + +```yaml +- parse-error: + name: Error state + yaml: | + key: : invalid +``` + +#### Emitter Tests + +**emit** - Emit events and verify output contains expected strings + +```yaml +- emit: + name: Simple scalar + data: + - STREAM_START_EVENT: + encoding: UTF8_ENCODING + - DOCUMENT_START_EVENT: + implicit: true + - SCALAR_EVENT: + value: hello + implicit: true + style: PLAIN_SCALAR_STYLE + - DOCUMENT_END_EVENT: + implicit: true + - STREAM_END_EVENT + want: hello +``` + +**emit-config** - Emit with configuration + +```yaml +- emit-config: + name: Custom indent + conf: + indent: 4 + data: + - STREAM_START_EVENT: + encoding: UTF8_ENCODING + - DOCUMENT_START_EVENT: + implicit: true + - MAPPING_START_EVENT: + implicit: true + style: BLOCK_MAPPING_STYLE + # ... more events + want: key +``` + +**roundtrip** - Parse → emit, verify output + +```yaml +- roundtrip: + name: Roundtrip + yaml: | + key: value + list: + - item1 + - item2 + want: + - key + - value + - item1 +``` + +**emit-writer** - Emit to io.Writer + +```yaml +- emit-writer: + name: Writer + data: + - STREAM_START_EVENT: + encoding: UTF8_ENCODING + # ... more events + want: test +``` + +#### API Tests + +**api-new** - Test constructors + +```yaml +- api-new: + name: New parser + with: NewParser + test: + - nil: [raw-buffer, false] + - cap: [raw-buffer, 512] + - nil: [buffer, false] + - cap: [buffer, 1536] +``` + +**api-method** - Test methods and field state + +```yaml +- api-method: + name: Parser set input string + with: NewParser + byte: true + call: [SetInputString, 'key: value'] + test: + - eq: [input, 'key: value'] + - eq: [input-pos, 0] + - nil: [read-handler, false] +``` + +**api-panic** - Test methods that should panic + +```yaml +- api-panic: + name: Parser set input string twice + with: NewParser + byte: true + init: [SetInputString, first] + call: [SetInputString, second] + want: must set the input source only once +``` + +**api-delete** - Test cleanup + +```yaml +- api-delete: + name: Parser delete + with: NewParser + byte: true + init: [SetInputString, test] + test: + - len: [input, 0] + - len: [buffer, 0] +``` + +**api-new-event** - Test event constructors + +```yaml +- api-new-event: + name: New stream start event + call: [NewStreamStartEvent, UTF8_ENCODING] + test: + - eq: [Type, STREAM_START_EVENT] + - eq: [encoding, UTF8_ENCODING] +``` + +#### Utility Tests + +**enum-string** - Test String() methods of enums + +```yaml +- enum-string: + name: Scalar style plain + enum: [ScalarStyle, PLAIN_SCALAR_STYLE] + want: Plain +``` + +**style-accessor** - Test style accessor methods + +```yaml +- style-accessor: + name: Event scalar style + test: [ScalarStyle, DOUBLE_QUOTED_SCALAR_STYLE] +``` + +#### Loader Tests + +**scalar-resolution** - Test scalar type resolution + +```yaml +- scalar-resolution: + name: Positive integer + yaml: "42" + want: 42 + +- scalar-resolution: + name: Negative float + yaml: "-2.5" + want: -2.5 +``` + +**Resolution order**: +1. Boolean (true, false) +2. Null (null keyword only) +3. Hexadecimal integer (0x prefix) +4. Float (contains .) +5. Decimal integer +6. String (fallback) + +### Common Keys in Test YAML Files + +Test cases use a **type-as-key** format where the test type is the map key: + +```yaml +- test-type: + name: Test case name + # ... other fields +``` + +**Common fields**: +- **name** - Test case name (title case convention) +- **yaml** - Input YAML string to test +- **want** - Expected result (format varies by test type) + - For api-panic: string containing expected panic message substring + - For scan-error/parse-error: boolean (defaults to true if omitted; set to + false if no error expected) + - For enum-string: string representing expected String() output + - For other types: varies (may be sequence or scalar) +- **data** - For emitter tests: list of event specifications to emit +- **conf** - For emitter config tests: emitter configuration options +- **with** - For API tests: constructor name (NewParser, NewEmitter) +- **call** - For API tests: method call [MethodName, arg1, arg2, ...] +- **init** - For API panic tests: setup method call before main method +- **byte** - For API tests: boolean flag to convert string args to []byte +- **test** - For API tests: list of field validation checks in format + `operator: [field, value]` where operator is one of: nil, cap, len, eq, gte, + len-gt. +- **test** - For style-accessor tests: array of [Method, STYLE] where Method + is the accessor method (e.g., ScalarStyle) and STYLE is the style constant + (e.g., DOUBLE_QUOTED_SCALAR_STYLE). +- **enum** - For enum tests: array of [Type, Value] where Type is the enum + type (e.g., ScalarStyle) and Value is the constant (e.g., + PLAIN_SCALAR_STYLE) + +**Note on scalar type resolution**: Unquoted scalar values in test data are +automatically resolved to appropriate Go types (int, float64, bool, nil) by the +`LoadAny` function. +Quoted scalars remain as strings. + +### Running Tests + +```bash +# Run all tests in the package +go test ./internal/libyaml + +# Run specific test file +go test ./internal/libyaml -run TestScanner +go test ./internal/libyaml -run TestParser +go test ./internal/libyaml -run TestEmitter +go test ./internal/libyaml -run TestAPI +go test ./internal/libyaml -run TestYAML +go test ./internal/libyaml -run TestLoader + +# Run specific test case (using subtest name) +go test ./internal/libyaml -run TestScanner/Block_sequence +go test ./internal/libyaml -run TestParser/Anchor_and_alias +go test ./internal/libyaml -run TestEmitter/Flow_mapping +go test ./internal/libyaml -run TestLoader/Scientific_notation_lowercase_e + +# Run with verbose output +go test -v ./internal/libyaml + +# Run with coverage +go test -cover ./internal/libyaml +``` diff --git a/vendor/go.yaml.in/yaml/v4/internal/libyaml/composer.go b/vendor/go.yaml.in/yaml/v4/internal/libyaml/composer.go new file mode 100644 index 000000000..59d64cb03 --- /dev/null +++ b/vendor/go.yaml.in/yaml/v4/internal/libyaml/composer.go @@ -0,0 +1,418 @@ +// Copyright 2011-2019 Canonical Ltd +// Copyright 2025 The go-yaml Project Contributors +// SPDX-License-Identifier: Apache-2.0 + +// Composer stage: Builds a node tree from a libyaml event stream. +// Handles document structure, anchors, and comment attachment. + +package libyaml + +import ( + "fmt" + "io" +) + +// Composer produces a node tree out of a libyaml event stream. +type Composer struct { + Parser Parser + event Event + doc *Node + anchors map[string]*Node + doneInit bool + Textless bool + streamNodes bool // enable stream node emission + returnStream bool // flag to return stream node next + atStreamEnd bool // at stream end + encoding Encoding // stream encoding from STREAM_START + opts *Options // options for loading +} + +// NewComposer creates a new composer from a byte slice. +func NewComposer(b []byte, opts *Options) *Composer { + p := Composer{ + Parser: NewParser(), + opts: opts, + } + if len(b) == 0 { + b = []byte{'\n'} + } + p.Parser.SetInputString(b) + if opts != nil { + p.Parser.depthCheck = opts.DepthCheck + } + return &p +} + +// NewComposerFromReader creates a new composer from an [io.Reader]. +func NewComposerFromReader(r io.Reader, opts *Options) *Composer { + p := Composer{ + Parser: NewParser(), + opts: opts, + } + p.Parser.SetInputReader(r) + if opts != nil { + p.Parser.depthCheck = opts.DepthCheck + } + return &p +} + +// Compose composes the next YAML node from the event stream. +func (c *Composer) Compose() *Node { + c.init() + + // Handle stream nodes if enabled + if c.streamNodes { + // Check for stream end first + if c.peek() == STREAM_END_EVENT { + // If we haven't returned the final stream node yet, + // return it now + if !c.atStreamEnd { + c.atStreamEnd = true + return c.createStreamNode() + } + // Already returned final stream node + return nil + } + + // Check if we should return a stream node before the next + // document + if c.returnStream { + c.returnStream = false + n := c.createStreamNode() + // Capture directives from upcoming document + c.captureDirectives(n) + return n + } + } + + switch c.peek() { + case SCALAR_EVENT: + return c.scalar() + case ALIAS_EVENT: + return c.alias() + case MAPPING_START_EVENT: + return c.mapping() + case SEQUENCE_START_EVENT: + return c.sequence() + case DOCUMENT_START_EVENT: + return c.document() + case STREAM_END_EVENT: + // Happens when attempting to decode an empty buffer (when not + // using stream nodes). + return nil + case TAIL_COMMENT_EVENT: + panic("internal error: unexpected tail comment event (please report)") + default: + panic("internal error: attempted to parse unknown event (please report): " + c.event.Type.String()) + } +} + +// node creates a new node with the given kind, tag, and value, and attaches +// position and comment information from the current event. +func (c *Composer) node(kind Kind, tag, value string) *Node { + var style Style + if tag != "" && tag != "!" { + // Normalize tag to short form (e.g., tag:yaml.org,2002:str -> !!str) + tag = shortTag(tag) + style = TaggedStyle + } + // Note: Nodes without explicit tags are left with empty tags. + // Tag defaulting happens in a separate stage via Resolver. + n := &Node{ + Kind: kind, + Tag: tag, + Value: value, + Style: style, + } + if !c.Textless { + n.Line = c.event.StartMark.Line + n.Column = c.event.StartMark.Column + n.HeadComment = string(c.event.HeadComment) + n.LineComment = string(c.event.LineComment) + n.FootComment = string(c.event.FootComment) + } + return n +} + +// document composes a document node by parsing its content between +// DOCUMENT_START and DOCUMENT_END events. +func (c *Composer) document() *Node { + n := c.node(DocumentNode, "", "") + c.doc = n + c.expect(DOCUMENT_START_EVENT) + c.parseChild(n) + if c.peek() == DOCUMENT_END_EVENT { + n.FootComment = string(c.event.FootComment) + } + c.expect(DOCUMENT_END_EVENT) + + // If stream nodes enabled, prepare to return a stream node next + if c.streamNodes { + c.returnStream = true + } + + return n +} + +// createStreamNode creates a stream node with encoding information. +func (c *Composer) createStreamNode() *Node { + n := &Node{ + Kind: StreamNode, + Stream: &Stream{Encoding: c.encoding}, + } + if !c.Textless && c.event.Type != NO_EVENT { + n.Line = c.event.StartMark.Line + n.Column = c.event.StartMark.Column + if c.event.Type == STREAM_END_EVENT { + n.HeadComment = string(c.event.HeadComment) + n.LineComment = string(c.event.LineComment) + n.FootComment = string(c.event.FootComment) + } + } + return n +} + +// alias composes an alias node by resolving the referenced anchor. +func (c *Composer) alias() *Node { + n := c.node(AliasNode, "", string(c.event.Anchor)) + n.Alias = c.anchors[n.Value] + if n.Alias == nil { + msg := fmt.Sprintf("unknown anchor '%s' referenced", n.Value) + Fail(formatComposerError(msg, Mark{ + Line: n.Line, + Column: n.Column, + })) + } + c.expect(ALIAS_EVENT) + return n +} + +// scalar composes a scalar node with value, tag, and style information. +func (c *Composer) scalar() *Node { + parsedStyle := c.event.ScalarStyle() + var nodeStyle Style + switch { + case parsedStyle&DOUBLE_QUOTED_SCALAR_STYLE != 0: + nodeStyle = DoubleQuotedStyle + case parsedStyle&SINGLE_QUOTED_SCALAR_STYLE != 0: + nodeStyle = SingleQuotedStyle + case parsedStyle&LITERAL_SCALAR_STYLE != 0: + nodeStyle = LiteralStyle + case parsedStyle&FOLDED_SCALAR_STYLE != 0: + nodeStyle = FoldedStyle + } + nodeValue := string(c.event.Value) + nodeTag := string(c.event.Tag) + n := c.node(ScalarNode, nodeTag, nodeValue) + n.Style |= nodeStyle + c.anchor(n, c.event.Anchor) + c.expect(SCALAR_EVENT) + return n +} + +// sequence composes a sequence node by parsing elements between +// SEQUENCE_START and SEQUENCE_END events. +func (c *Composer) sequence() *Node { + n := c.node(SequenceNode, string(c.event.Tag), "") + if c.event.SequenceStyle()&FLOW_SEQUENCE_STYLE != 0 { + n.Style |= FlowStyle + } + c.anchor(n, c.event.Anchor) + c.expect(SEQUENCE_START_EVENT) + for c.peek() != SEQUENCE_END_EVENT { + c.parseChild(n) + } + n.LineComment = string(c.event.LineComment) + n.FootComment = string(c.event.FootComment) + c.expect(SEQUENCE_END_EVENT) + return n +} + +// mapping composes a mapping node by parsing key-value pairs between +// MAPPING_START and MAPPING_END events, handling foot comments appropriately. +func (c *Composer) mapping() *Node { + n := c.node(MappingNode, string(c.event.Tag), "") + block := true + if c.event.MappingStyle()&FLOW_MAPPING_STYLE != 0 { + block = false + n.Style |= FlowStyle + } + c.anchor(n, c.event.Anchor) + c.expect(MAPPING_START_EVENT) + for c.peek() != MAPPING_END_EVENT { + k := c.parseChild(n) + if block && k.FootComment != "" { + // Must be a foot comment for the prior value when being dedented. + if len(n.Content) > 2 { + n.Content[len(n.Content)-3].FootComment = k.FootComment + k.FootComment = "" + } + } + v := c.parseChild(n) + if k.FootComment == "" && v.FootComment != "" { + k.FootComment = v.FootComment + v.FootComment = "" + } + if c.peek() == TAIL_COMMENT_EVENT { + if k.FootComment == "" { + k.FootComment = string(c.event.FootComment) + } + c.expect(TAIL_COMMENT_EVENT) + } + } + n.LineComment = string(c.event.LineComment) + n.FootComment = string(c.event.FootComment) + if n.Style&FlowStyle == 0 && n.FootComment != "" && len(n.Content) > 1 { + n.Content[len(n.Content)-2].FootComment = n.FootComment + n.FootComment = "" + } + c.expect(MAPPING_END_EVENT) + return n +} + +// init initializes the composer by setting up the anchor map and consuming +// the STREAM_START event. +func (c *Composer) init() { + if c.doneInit { + return + } + c.anchors = make(map[string]*Node) + // Peek to get the encoding from STREAM_START_EVENT + if c.peek() == STREAM_START_EVENT { + c.encoding = c.event.GetEncoding() + } + c.expect(STREAM_START_EVENT) + c.doneInit = true + + // If stream nodes are enabled, prepare to return the first stream node + if c.streamNodes { + c.returnStream = true + } +} + +// Destroy cleans up the composer by deleting any pending event and the +// underlying parser. +func (c *Composer) Destroy() { + if c.event.Type != NO_EVENT { + c.event.Delete() + } + c.Parser.Delete() +} + +// SetStreamNodes enables or disables stream node emission. +func (c *Composer) SetStreamNodes(enable bool) { + c.streamNodes = enable +} + +// expect consumes an event from the event stream and +// checks that it's of the expected type. +func (c *Composer) expect(e EventType) { + if c.event.Type == NO_EVENT { + if err := c.Parser.Parse(&c.event); err != nil { + c.fail(err) + } + } + if c.event.Type == STREAM_END_EVENT { + Fail(formatComposerError( + "attempted to go past the end of stream; corrupted value?", + Mark{Line: c.event.StartMark.Line, Column: c.event.StartMark.Column}, + )) + } + if c.event.Type != e { + Fail(formatComposerError( + fmt.Sprintf("expected %s event but got %s", e, c.event.Type), + Mark{Line: c.event.StartMark.Line, Column: c.event.StartMark.Column}, + )) + } + c.event.Delete() + c.event.Type = NO_EVENT +} + +// peek peeks at the next event in the event stream, +// puts the results into c.event and returns the event type. +func (c *Composer) peek() EventType { + if c.event.Type != NO_EVENT { + return c.event.Type + } + // It's curious choice from the underlying API to generally return a + // positive result on success, but on this case return true in an error + // scenario. This was the source of bugs in the past (issue #666). + if err := c.Parser.Parse(&c.event); err != nil { + c.fail(err) + } + return c.event.Type +} + +// fail panics with the given error. +func (c *Composer) fail(err error) { + Fail(err) +} + +// anchor sets the anchor name on a node and records it in the anchor map. +func (c *Composer) anchor(n *Node, anchor []byte) { + if anchor != nil { + n.Anchor = string(anchor) + c.anchors[n.Anchor] = n + } +} + +// parseChild composes the next node and adds it as a child to the parent. +func (c *Composer) parseChild(parent *Node) *Node { + child := c.Compose() + parent.Content = append(parent.Content, child) + return child +} + +// captureDirectives captures version and tag directives from upcoming +// DOCUMENT_START. +// The node n must have Stream initialized (as created by createStreamNode). +func (c *Composer) captureDirectives(n *Node) { + if c.peek() == DOCUMENT_START_EVENT { + if vd := c.event.GetVersionDirective(); vd != nil { + n.Stream.Version = &StreamVersionDirective{ + Major: vd.Major(), + Minor: vd.Minor(), + } + } + if tds := c.event.GetTagDirectives(); len(tds) > 0 { + n.Stream.TagDirectives = make([]StreamTagDirective, len(tds)) + for i, td := range tds { + n.Stream.TagDirectives[i] = StreamTagDirective{ + Handle: td.GetHandle(), + Prefix: td.GetPrefix(), + } + } + } + } +} + +// Fail panics with a YAMLError wrapping the given error. +func Fail(err error) { + panic(&YAMLError{err}) +} + +// failf panics with a YAMLError containing a formatted error message. +func failf(format string, args ...any) { + panic(&YAMLError{fmt.Errorf("yaml: "+format, args...)}) +} + +// formatComposerError creates a LoadError for composer-stage errors. +func formatComposerError(message string, mark Mark) *LoadError { + return &LoadError{ + Stage: ComposerStage, + Mark: mark, + Message: message, + } +} + +// formatComposerErrorContext creates a LoadError with both context and +// problem information for composer-stage errors. +func formatComposerErrorContext(context string, contextMark Mark, message string, mark Mark) *LoadError { + return &LoadError{ + Stage: ComposerStage, + ContextMark: contextMark, + ContextMsg: context, + Mark: mark, + Message: message, + } +} diff --git a/vendor/go.yaml.in/yaml/v4/internal/libyaml/constructor.go b/vendor/go.yaml.in/yaml/v4/internal/libyaml/constructor.go new file mode 100644 index 000000000..1d96e914a --- /dev/null +++ b/vendor/go.yaml.in/yaml/v4/internal/libyaml/constructor.go @@ -0,0 +1,1152 @@ +// Copyright 2011-2019 Canonical Ltd +// Copyright 2025 The go-yaml Project Contributors +// SPDX-License-Identifier: Apache-2.0 + +// Constructor stage: Converts YAML nodes to Go values. +// Handles type resolution, custom unmarshalers, and struct field mapping. + +package libyaml + +import ( + "encoding" + "encoding/base64" + "fmt" + "math" + "reflect" + "time" +) + +// -------------------------------------------------------------------------- +// Types and Interfaces + +// legacyConstructor is the old-style unmarshaler interface. +// It's kept for backwards compatibility. +type legacyConstructor interface { + UnmarshalYAML(construct func(any) error) error +} + +// constructorAdapter is an interface that wraps the root package's Unmarshaler +// interface. +// This allows the constructor to call constructors that expect *yaml.Node +// instead of *libyaml.Node. +type constructorAdapter interface { + CallRootConstructor(n *Node) error +} + +// ScalarConstructFunc is the signature for tag-specific scalar constructor +// functions. +// Each function handles construction of a specific YAML tag to various Go +// types. +type ScalarConstructFunc func(c *Constructor, n *Node, resolved any, out reflect.Value) bool + +// Constructor state +type Constructor struct { + doc *Node + aliases map[*Node]bool + TypeErrors []*LoadError + + stringMapType reflect.Type + generalMapType reflect.Type + + KnownFields bool + UniqueKeys bool + constructCount int + aliasCount int + aliasDepth int + aliasCheck func(aliasCount, constructCount int) error + + mergedFields map[any]bool +} + +// NewConstructor creates a new Constructor initialized with the provided +// options. +func NewConstructor(opts *Options) *Constructor { + return &Constructor{ + stringMapType: stringMapType, + generalMapType: generalMapType, + KnownFields: opts.KnownFields, + UniqueKeys: opts.UniqueKeys, + aliases: make(map[*Node]bool), + aliasCheck: opts.AliasCheck, + } +} + +// -------------------------------------------------------------------------- +// Main Entry Point + +// Construct converts a YAML node into the Go value represented by out. +// It dispatches to the appropriate handler based on the node kind and +// handles alias expansion, custom unmarshalers, and type resolution. +// Returns true if the construction was successful. +func (c *Constructor) Construct(n *Node, out reflect.Value) (good bool) { + c.constructCount++ + if c.aliasDepth > 0 { + c.aliasCount++ + } + if c.aliasCheck != nil { + if err := c.aliasCheck(c.aliasCount, c.constructCount); err != nil { + Fail(formatConstructorError(err, Mark{Line: n.Line, Column: n.Column})) + } + } + if out.Type() == nodeType { + out.Set(reflect.ValueOf(n).Elem()) + return true + } + + switch n.Kind { + case DocumentNode: + return c.document(n, out) + case AliasNode: + return c.alias(n, out) + } + + out, constructed, good := c.prepare(n, out) + if constructed { + return good + } + + // When out type implements [encoding.TextUnmarshaler], ensure the node + // is a scalar. Otherwise, for example, constructing a YAML mapping + // into a struct having no exported fields, but implementing + // TextUnmarshaler would silently succeed, but do nothing. + // + // Note that this matches the behavior of both encoding/json and + // encoding/json/v2. + if n.Kind != ScalarNode && isTextUnmarshaler(out) { + err := fmt.Errorf("cannot construct %s into %s (TextUnmarshaler)", shortTag(n.Tag), out.Type()) + c.TypeErrors = append(c.TypeErrors, + formatConstructorError(err, Mark{Line: n.Line, Column: n.Column})) + return false + } + + switch n.Kind { + case ScalarNode: + good = c.scalar(n, out) + case MappingNode: + good = c.mapping(n, out) + case SequenceNode: + good = c.sequence(n, out) + case 0: + if n.IsZero() { + return c.null(out) + } + fallthrough + default: + Fail(formatConstructorError( + fmt.Errorf("cannot construct node with unknown kind: '%d'", n.Kind), + Mark{Line: n.Line, Column: n.Column}, + )) + } + return good +} + +// -------------------------------------------------------------------------- +// Package-level Variables and Constants +var ( + nodeType = reflect.TypeOf(Node{}) + durationType = reflect.TypeOf(time.Duration(0)) + stringMapType = reflect.TypeOf(map[string]any{}) + generalMapType = reflect.TypeOf(map[any]any{}) + ifaceType = generalMapType.Elem() +) + +// scalarConstructors maps YAML scalar tags to their constructor functions. +var scalarConstructors = map[string]ScalarConstructFunc{ + strTag: (*Constructor).constructStr, + intTag: (*Constructor).constructInt, + boolTag: (*Constructor).constructBool, + floatTag: (*Constructor).constructFloat, + nullTag: (*Constructor).constructNull, + timestampTag: (*Constructor).constructTimestamp, + binaryTag: (*Constructor).constructBinary, + mergeTag: (*Constructor).constructMerge, +} + +// -------------------------------------------------------------------------- +// Scalar tag constructors + +// constructStr constructs a !!str tagged value into various Go types. +func (c *Constructor) constructStr(n *Node, resolved any, out reflect.Value) bool { + switch out.Kind() { + case reflect.String: + out.SetString(n.Value) + return true + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + // Handle time.Duration parsing from strings like "3s", "1m", + // etc. + if out.Type() == durationType { + d, err := time.ParseDuration(n.Value) + if err == nil { + out.SetInt(int64(d)) + return true + } + } + case reflect.Bool: + // YAML 1.1 compatibility: allow string values like "y", "on", + // "Off" as bools + switch n.Value { + case "y", "Y", "yes", "Yes", "YES", "on", "On", "ON": + out.SetBool(true) + return true + case "n", "N", "no", "No", "NO", "off", "Off", "OFF": + out.SetBool(false) + return true + } + case reflect.Interface: + out.Set(reflect.ValueOf(resolved)) + return true + } + c.tagError(n, strTag, out) + return false +} + +// constructInt constructs a !!int tagged value into various Go types. +func (c *Constructor) constructInt(n *Node, resolved any, out reflect.Value) bool { + switch out.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + isDuration := out.Type() == durationType + + switch resolved := resolved.(type) { + case int: + if !isDuration && !out.OverflowInt(int64(resolved)) { + out.SetInt(int64(resolved)) + return true + } else if isDuration && resolved == 0 { + out.SetInt(0) + return true + } + case int64: + if !isDuration && !out.OverflowInt(resolved) { + out.SetInt(resolved) + return true + } + case uint64: + if !isDuration && resolved <= math.MaxInt64 { + intVal := int64(resolved) + if !out.OverflowInt(intVal) { + out.SetInt(intVal) + return true + } + } + case float64: + if !isDuration && resolved >= math.MinInt64 && resolved <= math.MaxInt64 { + intVal := int64(resolved) + // Verify conversion is lossless (handles + // floating-point precision) + if float64(intVal) == resolved && !out.OverflowInt(intVal) { + out.SetInt(intVal) + return true + } + } + case string: + if out.Type() == durationType { + d, err := time.ParseDuration(resolved) + if err == nil { + out.SetInt(int64(d)) + return true + } + } + } + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + switch resolved := resolved.(type) { + case int: + if resolved >= 0 && !out.OverflowUint(uint64(resolved)) { + out.SetUint(uint64(resolved)) + return true + } + case int64: + if resolved >= 0 && !out.OverflowUint(uint64(resolved)) { + out.SetUint(uint64(resolved)) + return true + } + case uint64: + if !out.OverflowUint(resolved) { + out.SetUint(resolved) + return true + } + case float64: + if resolved >= 0 && resolved <= math.MaxUint64 { + uintVal := uint64(resolved) + // Verify conversion is lossless (handles + // floating-point precision) + if float64(uintVal) == resolved && !out.OverflowUint(uintVal) { + out.SetUint(uintVal) + return true + } + } + } + case reflect.Float32, reflect.Float64: + // Allow int to float conversion + switch resolved := resolved.(type) { + case int: + out.SetFloat(float64(resolved)) + return true + case int64: + out.SetFloat(float64(resolved)) + return true + case uint64: + out.SetFloat(float64(resolved)) + return true + } + case reflect.String: + // Allow int to string conversion + out.SetString(n.Value) + return true + case reflect.Interface: + out.Set(reflect.ValueOf(resolved)) + return true + } + c.tagError(n, intTag, out) + return false +} + +// constructBool constructs a !!bool tagged value into various Go types. +func (c *Constructor) constructBool(n *Node, resolved any, out reflect.Value) bool { + switch out.Kind() { + case reflect.Bool: + switch resolved := resolved.(type) { + case bool: + out.SetBool(resolved) + return true + case string: + // This offers some compatibility with the 1.1 spec + // (https://yaml.org/type/bool.html). + // It only works if explicitly attempting to construct + // into a typed bool value. + switch resolved { + case "y", "Y", "yes", "Yes", "YES", "on", "On", "ON": + out.SetBool(true) + return true + case "n", "N", "no", "No", "NO", "off", "Off", "OFF": + out.SetBool(false) + return true + } + } + case reflect.String: + // Allow bool to be constructed as string (e.g., true -> "true") + out.SetString(n.Value) + return true + case reflect.Interface: + out.Set(reflect.ValueOf(resolved)) + return true + } + c.tagError(n, boolTag, out) + return false +} + +// constructFloat constructs a !!float tagged value into various Go types. +func (c *Constructor) constructFloat(n *Node, resolved any, out reflect.Value) bool { + switch out.Kind() { + case reflect.Float32, reflect.Float64: + switch resolved := resolved.(type) { + case int: + out.SetFloat(float64(resolved)) + return true + case int64: + out.SetFloat(float64(resolved)) + return true + case uint64: + out.SetFloat(float64(resolved)) + return true + case float64: + out.SetFloat(resolved) + return true + } + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + // Allow float to int conversion if lossless + if fval, ok := resolved.(float64); ok { + if fval >= math.MinInt64 && fval <= math.MaxInt64 { + intVal := int64(fval) + if float64(intVal) == fval && !out.OverflowInt(intVal) { + out.SetInt(intVal) + return true + } + } + } + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + // Allow float to uint conversion if lossless + if fval, ok := resolved.(float64); ok { + if fval >= 0 && fval <= math.MaxUint64 { + uintVal := uint64(fval) + if float64(uintVal) == fval && !out.OverflowUint(uintVal) { + out.SetUint(uintVal) + return true + } + } + } + case reflect.String: + out.SetString(n.Value) + return true + case reflect.Interface: + out.Set(reflect.ValueOf(resolved)) + return true + } + c.tagError(n, floatTag, out) + return false +} + +// constructTimestamp constructs a !!timestamp tagged value into various Go +// types. +func (c *Constructor) constructTimestamp(n *Node, resolved any, out reflect.Value) bool { + switch out.Kind() { + case reflect.Struct: + if resolvedv := reflect.ValueOf(resolved); out.Type() == resolvedv.Type() { + out.Set(resolvedv) + return true + } + case reflect.Interface: + out.Set(reflect.ValueOf(resolved)) + return true + } + c.tagError(n, timestampTag, out) + return false +} + +// constructBinary constructs a !!binary tagged value into various Go types. +func (c *Constructor) constructBinary(n *Node, resolved any, out reflect.Value) bool { + switch out.Kind() { + case reflect.String: + out.SetString(resolved.(string)) + return true + case reflect.Slice: + // allow decoding !!binary-tagged value into []byte specifically + if out.Type().Elem().Kind() == reflect.Uint8 { + out.SetBytes([]byte(resolved.(string))) + return true + } + case reflect.Interface: + out.Set(reflect.ValueOf(resolved)) + return true + } + c.tagError(n, binaryTag, out) + return false +} + +// constructNull constructs a !!null tagged value into various Go types. +func (c *Constructor) constructNull(n *Node, resolved any, out reflect.Value) bool { + return c.null(out) +} + +// constructMerge handles !!merge tagged keys. +// Merge keys are directives, not values, so construction always fails. +// They are handled specially by the mapping() function. +func (c *Constructor) constructMerge(n *Node, resolved any, out reflect.Value) bool { + return false +} + +// -------------------------------------------------------------------------- +// Node Kind Handlers + +// document constructs a DocumentNode by processing its single content node. +func (c *Constructor) document(n *Node, out reflect.Value) (good bool) { + if len(n.Content) == 1 { + c.doc = n + c.Construct(n.Content[0], out) + return true + } + return false +} + +// alias constructs an AliasNode by following the alias reference and +// tracking alias depth to detect circular references. +func (c *Constructor) alias(n *Node, out reflect.Value) (good bool) { + if c.aliases[n] { + // TODO this could actually be allowed in some circumstances. + Fail(formatComposerError( + fmt.Sprintf("anchor '%s' value contains itself", n.Value), + Mark{Line: n.Line, Column: n.Column}, + )) + } + c.aliases[n] = true + c.aliasDepth++ + good = c.Construct(n.Alias, out) + c.aliasDepth-- + delete(c.aliases, n) + return good +} + +// scalar constructs a ScalarNode by resolving its tag and value, then +// dispatching to the appropriate tag-specific constructor or using +// TextUnmarshaler if available. +func (c *Constructor) scalar(n *Node, out reflect.Value) bool { + // Resolve the tag and value + var tag string + var resolved any + if n.indicatedString() { + tag = strTag + resolved = n.Value + } else { + tag, resolved = resolve(n.Tag, n.Value) + if tag == binaryTag { + data, err := base64.StdEncoding.DecodeString(resolved.(string)) + if err != nil { + Fail(formatConstructorError( + fmt.Errorf("!!binary value contains invalid base64 data"), + Mark{Line: n.Line, Column: n.Column}, + )) + } + resolved = string(data) + } + } + + // Handle null + if resolved == nil { + return c.null(out) + } + + // Fast path: exact type match + if resolvedv := reflect.ValueOf(resolved); out.Type() == resolvedv.Type() { + out.Set(resolvedv) + return true + } + + // Handle TextUnmarshaler interface + if out.CanAddr() { + u, ok := out.Addr().Interface().(encoding.TextUnmarshaler) + if ok { + var text []byte + if tag == binaryTag { + text = []byte(resolved.(string)) + } else { + text = []byte(n.Value) + } + err := u.UnmarshalText(text) + if err != nil { + c.TypeErrors = append(c.TypeErrors, formatConstructorError(err, Mark{Line: n.Line, Column: n.Column})) + return false + } + return true + } + } + + // Dispatch to tag-specific constructor + if constructor, ok := scalarConstructors[tag]; ok { + return constructor(c, n, resolved, out) + } + + // Unknown tag - try some fallback behaviors + switch out.Kind() { + case reflect.Interface: + // For interface{} targets, accept any resolved value + out.Set(reflect.ValueOf(resolved)) + return true + case reflect.Struct: + // For struct targets with matching types, try direct assignment + if resolvedv := reflect.ValueOf(resolved); out.Type() == resolvedv.Type() { + out.Set(resolvedv) + return true + } + } + + // No constructor and no fallback worked + c.tagError(n, tag, out) + return false +} + +// sequence constructs a SequenceNode into a Go slice, array, or interface. +func (c *Constructor) sequence(n *Node, out reflect.Value) (good bool) { + l := len(n.Content) + + var iface reflect.Value + switch out.Kind() { + case reflect.Slice: + out.Set(reflect.MakeSlice(out.Type(), l, l)) + case reflect.Array: + if l != out.Len() { + Fail(formatConstructorError( + fmt.Errorf("invalid array: want %d elements but got %d", out.Len(), l), + Mark{Line: n.Line, Column: n.Column}, + )) + } + case reflect.Interface: + // No type hints. Will have to use a generic sequence. + iface = out + out = settableValueOf(make([]any, l)) + default: + c.tagError(n, seqTag, out) + return false + } + et := out.Type().Elem() + + j := 0 + for i := 0; i < l; i++ { + e := reflect.New(et).Elem() + if ok := c.Construct(n.Content[i], e); ok { + out.Index(j).Set(e) + j++ + } + } + if out.Kind() != reflect.Array { + out.Set(out.Slice(0, j)) + } + if iface.IsValid() { + iface.Set(out) + } + return true +} + +// mapping constructs a MappingNode into a Go map, struct, or interface. +// It handles key uniqueness checking, merge keys, and type-appropriate +// map construction (string-keyed vs general). +func (c *Constructor) mapping(n *Node, out reflect.Value) (good bool) { + l := len(n.Content) + if c.UniqueKeys { + nerrs := len(c.TypeErrors) + for i := 0; i < l; i += 2 { + ni := n.Content[i] + for j := i + 2; j < l; j += 2 { + nj := n.Content[j] + if ni.Kind == nj.Kind && ni.Value == nj.Value { + c.TypeErrors = append(c.TypeErrors, formatConstructorError( + fmt.Errorf("mapping key %#v already defined at line %d", nj.Value, ni.Line), + Mark{Line: nj.Line, Column: nj.Column}, + )) + } + } + } + if len(c.TypeErrors) > nerrs { + return false + } + } + switch out.Kind() { + case reflect.Struct: + return c.mappingStruct(n, out) + case reflect.Map: + // okay + case reflect.Interface: + iface := out + if isStringMap(n) { + out = reflect.MakeMap(c.stringMapType) + } else { + out = reflect.MakeMap(c.generalMapType) + } + iface.Set(out) + default: + c.tagError(n, mapTag, out) + return false + } + + outt := out.Type() + kt := outt.Key() + et := outt.Elem() + + stringMapType := c.stringMapType + generalMapType := c.generalMapType + if outt.Elem() == ifaceType { + if outt.Key().Kind() == reflect.String { + c.stringMapType = outt + } else if outt.Key() == ifaceType { + c.generalMapType = outt + } + } + + mergedFields := c.mergedFields + c.mergedFields = nil + + var mergeNode *Node + + mapIsNew := false + if out.IsNil() { + out.Set(reflect.MakeMap(outt)) + mapIsNew = true + } + for i := 0; i < l; i += 2 { + if isMerge(n.Content[i]) { + mergeNode = n.Content[i+1] + continue + } + k := reflect.New(kt).Elem() + if c.Construct(n.Content[i], k) { + if mergedFields != nil { + ki := k.Interface() + if c.getPossiblyUnhashableKey(mergedFields, ki, n.Content[i]) { + continue + } + c.setPossiblyUnhashableKey(mergedFields, ki, true, n.Content[i]) + } + kkind := k.Kind() + if kkind == reflect.Interface { + kkind = k.Elem().Kind() + } + if kkind == reflect.Map || kkind == reflect.Slice { + Fail(formatConstructorError( + fmt.Errorf("cannot use '%#v' as a map key; try decoding into yaml.Node", k.Interface()), + Mark{Line: n.Content[i].Line, Column: n.Content[i].Column}, + )) + } + e := reflect.New(et).Elem() + if c.Construct(n.Content[i+1], e) || n.Content[i+1].ShortTag() == nullTag && (mapIsNew || !out.MapIndex(k).IsValid()) { + out.SetMapIndex(k, e) + } + } + } + + c.mergedFields = mergedFields + if mergeNode != nil { + c.merge(n, mergeNode, out) + } + + c.stringMapType = stringMapType + c.generalMapType = generalMapType + return true +} + +// -------------------------------------------------------------------------- +// Mapping/Struct Support + +// mappingStruct constructs a MappingNode into a struct value. +// It handles field matching by name, inline fields, inline maps, merge keys, +// and enforces known fields and unique keys when configured. +func (c *Constructor) mappingStruct(n *Node, out reflect.Value) (good bool) { + sinfo, err := getStructInfo(out.Type()) + if err != nil { + panic(err) + } + + var inlineMap reflect.Value + var elemType reflect.Type + if sinfo.InlineMap != -1 { + inlineMap = out.Field(sinfo.InlineMap) + elemType = inlineMap.Type().Elem() + } + + for _, index := range sinfo.InlineConstructors { + field := c.fieldByIndex(n, out, index) + c.prepare(n, field) + } + + mergedFields := c.mergedFields + c.mergedFields = nil + var mergeNode *Node + var doneFields []bool + if c.UniqueKeys { + doneFields = make([]bool, len(sinfo.FieldsList)) + } + name := settableValueOf("") + l := len(n.Content) + for i := 0; i < l; i += 2 { + ni := n.Content[i] + if isMerge(ni) { + mergeNode = n.Content[i+1] + continue + } + if !c.Construct(ni, name) { + continue + } + sname := name.String() + if mergedFields != nil { + if mergedFields[sname] { + continue + } + mergedFields[sname] = true + } + if info, ok := sinfo.FieldsMap[sname]; ok { + if c.UniqueKeys { + if doneFields[info.Id] { + c.TypeErrors = append(c.TypeErrors, formatConstructorError( + fmt.Errorf("field %s already set in type %s", name.String(), out.Type()), + Mark{Line: ni.Line, Column: ni.Column}, + )) + continue + } + doneFields[info.Id] = true + } + var field reflect.Value + if info.Inline == nil { + field = out.Field(info.Num) + } else { + field = c.fieldByIndex(n, out, info.Inline) + } + c.Construct(n.Content[i+1], field) + } else if sinfo.InlineMap != -1 { + if inlineMap.IsNil() { + inlineMap.Set(reflect.MakeMap(inlineMap.Type())) + } + value := reflect.New(elemType).Elem() + c.Construct(n.Content[i+1], value) + inlineMap.SetMapIndex(name, value) + } else if c.KnownFields { + c.TypeErrors = append(c.TypeErrors, formatConstructorError( + fmt.Errorf("field %s not found in type %s", name.String(), out.Type()), + Mark{Line: ni.Line, Column: ni.Column}, + )) + } + } + + c.mergedFields = mergedFields + if mergeNode != nil { + c.merge(n, mergeNode, out) + } + return true +} + +// merge processes a merge key (<<) by constructing the merge value into out. +// The merge value can be a single mapping, an alias to a mapping, or a +// sequence of mappings. +// Fields from the parent mapping take precedence over merged fields. +func (c *Constructor) merge(parent *Node, merge *Node, out reflect.Value) { + mergedFields := c.mergedFields + if mergedFields == nil { + c.mergedFields = make(map[any]bool) + for i := 0; i < len(parent.Content); i += 2 { + k := reflect.New(ifaceType).Elem() + if c.Construct(parent.Content[i], k) { + c.setPossiblyUnhashableKey(c.mergedFields, k.Interface(), true, parent.Content[i]) + } + } + } + + switch merge.Kind { + case MappingNode: + c.Construct(merge, out) + case AliasNode: + if merge.Alias != nil && merge.Alias.Kind != MappingNode { + failWantMap(merge.Alias) + } + c.Construct(merge, out) + case SequenceNode: + for i := 0; i < len(merge.Content); i++ { + ni := merge.Content[i] + if ni.Kind == AliasNode { + if ni.Alias != nil && ni.Alias.Kind != MappingNode { + failWantMap(ni.Alias) + } + } else if ni.Kind != MappingNode { + failWantMap(ni) + } + c.Construct(ni, out) + } + default: + failWantMap(merge) + } + + c.mergedFields = mergedFields +} + +// isStringMap checks if a MappingNode has only string or merge keys. +// This determines whether to use map[string]any or map[any]any when +// constructing into an interface{}. +func isStringMap(n *Node) bool { + if n.Kind != MappingNode { + return false + } + l := len(n.Content) + for i := 0; i < l; i += 2 { + shortTag := n.Content[i].ShortTag() + if shortTag != strTag && shortTag != mergeTag { + return false + } + } + return true +} + +// isMerge checks if a node is a merge key (!!merge tag). +func isMerge(n *Node) bool { + return n.Kind == ScalarNode && shortTag(n.Tag) == mergeTag +} + +// failWantMap panics with an error message for invalid merge key values. +func failWantMap(n *Node) { + Fail(formatConstructorError( + fmt.Errorf("map merge requires map or sequence of maps as the value"), + Mark{Line: n.Line, Column: n.Column}, + )) +} + +// -------------------------------------------------------------------------- +// Utility Methods + +// prepare initializes and dereferences pointers and calls UnmarshalYAML +// if a value is found to implement it. +// It returns the initialized and dereferenced out value, whether +// construction was already done by UnmarshalYAML, and if so whether +// its types constructed appropriately. +// +// If n holds a null value, prepare returns before doing anything. +func (c *Constructor) prepare(n *Node, out reflect.Value) (newout reflect.Value, constructed, good bool) { + if n.ShortTag() == nullTag { + return out, false, false + } + again := true + for again { + again = false + if out.Kind() == reflect.Pointer { + if out.IsNil() { + out.Set(reflect.New(out.Type().Elem())) + } + out = out.Elem() + again = true + } + if out.CanAddr() { + // Try yaml.Unmarshaler (from root package) first + if called, good := c.tryCallYAMLConstructor(n, out); called { + return out, true, good + } + + outi := out.Addr().Interface() + // Check for libyaml.constructor + if u, ok := outi.(constructor); ok { + good = c.callConstructor(n, u) + return out, true, good + } + if u, ok := outi.(legacyConstructor); ok { + good = c.callLegacyConstructor(n, u) + return out, true, good + } + } + } + return out, false, false +} + +// fieldByIndex returns the struct field at the given index path, initializing +// any nil pointers along the way. +func (c *Constructor) fieldByIndex(n *Node, v reflect.Value, index []int) (field reflect.Value) { + if n.ShortTag() == nullTag { + return reflect.Value{} + } + for _, num := range index { + for { + if v.Kind() == reflect.Pointer { + if v.IsNil() { + v.Set(reflect.New(v.Type().Elem())) + } + v = v.Elem() + continue + } + break + } + v = v.Field(num) + } + return v +} + +// tryCallYAMLConstructor checks if the value has an UnmarshalYAML method that +// takes a *Node from an allowlisted v3 yaml package and calls it if found. +// This handles backward compatibility with types that implement the v3 +// yaml.Unmarshaler interface instead of the native libyaml.constructor. +func (c *Constructor) tryCallYAMLConstructor(n *Node, out reflect.Value) (called bool, good bool) { + if !out.CanAddr() { + return false, false + } + + addr := out.Addr() + // Check for UnmarshalYAML method + method := addr.MethodByName("UnmarshalYAML") + if !method.IsValid() { + return false, false + } + + // Check method signature: func(*yaml.Node) error + mtype := method.Type() + if mtype.NumIn() != 1 || mtype.NumOut() != 1 { + return false, false + } + + // Check if parameter is a pointer to a Node-like struct + paramType := mtype.In(0) + if paramType.Kind() != reflect.Ptr { + return false, false + } + + elemType := paramType.Elem() + if elemType.Kind() != reflect.Struct { + return false, false + } + + // Only accept *Node from allowlisted v3 yaml packages whose Node type + // is assumed to have a compatible memory layout with libyaml.Node. + // The unsafe pointer cast below is only safe for these packages. + if elemType.Name() != "Node" || !isYAMLNodePkg(elemType.PkgPath()) { + return false, false + } + + // Return type must be error + retType := mtype.Out(0) + if retType.Kind() != reflect.Interface || retType.Name() != "error" { + return false, false + } + + // Call the method with a converted node. + // The allowlisted v3 packages define their own Node type that is + // assumed to have a compatible memory layout with libyaml.Node. + nodeValue := reflect.NewAt(elemType, reflect.ValueOf(n).UnsafePointer()) + + results := method.Call([]reflect.Value{nodeValue}) + err := results[0].Interface() + + if err == nil { + return true, true + } + + switch e := err.(type) { + case *LoadErrors: + c.TypeErrors = append(c.TypeErrors, e.Errors...) + return true, false + default: + c.TypeErrors = append(c.TypeErrors, formatConstructorError( + err.(error), + Mark{Line: n.Line, Column: n.Column}, + )) + return true, false + } +} + +// callConstructor invokes the UnmarshalYAML method on a value implementing +// the constructor interface, handling errors appropriately. +func (c *Constructor) callConstructor(n *Node, u constructor) (good bool) { + err := u.UnmarshalYAML(n) + switch e := err.(type) { + case nil: + return true + case *LoadErrors: + c.TypeErrors = append(c.TypeErrors, e.Errors...) + return false + default: + c.TypeErrors = append(c.TypeErrors, formatConstructorError( + err, + Mark{Line: n.Line, Column: n.Column}, + )) + return false + } +} + +// callLegacyConstructor invokes the UnmarshalYAML method on a value +// implementing the old-style legacyConstructor interface. +func (c *Constructor) callLegacyConstructor(n *Node, u legacyConstructor) (good bool) { + terrlen := len(c.TypeErrors) + err := u.UnmarshalYAML(func(v any) (err error) { + defer handleErr(&err) + c.Construct(n, reflect.ValueOf(v)) + if len(c.TypeErrors) > terrlen { + issues := c.TypeErrors[terrlen:] + c.TypeErrors = c.TypeErrors[:terrlen] + return &LoadErrors{issues} + } + return nil + }) + switch e := err.(type) { + case nil: + return true + case *LoadErrors: + c.TypeErrors = append(c.TypeErrors, e.Errors...) + return false + default: + c.TypeErrors = append(c.TypeErrors, formatConstructorError( + err, + Mark{Line: n.Line, Column: n.Column}, + )) + return false + } +} + +// tagError records a type construction error indicating that a node with a +// given tag cannot be constructed into the target type. +func (c *Constructor) tagError(n *Node, tag string, out reflect.Value) { + if n.Tag != "" { + tag = n.Tag + } + value := n.Value + if tag != seqTag && tag != mapTag { + if len(value) > 10 { + value = " `" + value[:7] + "...`" + } else { + value = " `" + value + "`" + } + } + c.TypeErrors = append(c.TypeErrors, formatConstructorError( + fmt.Errorf("cannot construct %s%s into %s", shortTag(tag), value, out.Type()), + Mark{Line: n.Line, Column: n.Column}, + )) +} + +// null constructs a null value by setting the target to its zero value. +// Only works for nillable types (interface, pointer, map, slice). +func (c *Constructor) null(out reflect.Value) bool { + if out.CanAddr() { + switch out.Kind() { + case reflect.Interface, reflect.Pointer, reflect.Map, reflect.Slice: + out.Set(reflect.Zero(out.Type())) + return true + } + } + return false +} + +// isTextUnmarshaler checks if a value implements [encoding.TextUnmarshaler]. +// It dereferences pointers to check the underlying type. +func isTextUnmarshaler(out reflect.Value) bool { + // Dereference pointers to check the underlying type, + // similar to how prepare() handles Constructor checks. + for out.Kind() == reflect.Pointer { + if out.IsNil() { + // Create a new instance to check the type + out = reflect.New(out.Type().Elem()).Elem() + } else { + out = out.Elem() + } + } + if out.CanAddr() { + _, ok := out.Addr().Interface().(encoding.TextUnmarshaler) + return ok + } + return false +} + +// settableValueOf returns a settable [reflect.Value] for the given value. +func settableValueOf(i any) reflect.Value { + v := reflect.ValueOf(i) + sv := reflect.New(v.Type()).Elem() + sv.Set(v) + return sv +} + +// setPossiblyUnhashableKey sets a map key, recovering from panics if the key +// type is unhashable. +func (c *Constructor) setPossiblyUnhashableKey(m map[any]bool, key any, value bool, n *Node) { + defer func() { + if err := recover(); err != nil { + Fail(formatConstructorError( + fmt.Errorf("%v", err), + Mark{Line: n.Line, Column: n.Column}, + )) + } + }() + m[key] = value +} + +// getPossiblyUnhashableKey gets a map key value, recovering from panics if +// the key type is unhashable. +func (c *Constructor) getPossiblyUnhashableKey(m map[any]bool, key any, n *Node) bool { + defer func() { + if err := recover(); err != nil { + Fail(formatConstructorError( + fmt.Errorf("%v", err), + Mark{Line: n.Line, Column: n.Column}, + )) + } + }() + return m[key] +} + +// formatConstructorError creates a LoadError for constructor-stage errors. +func formatConstructorError(err error, mark Mark) *LoadError { + return &LoadError{ + Stage: ConstructorStage, + Mark: mark, + Message: err.Error(), + err: err, + } +} + +// formatConstructorErrorContext creates a LoadError with both context and +// problem information for constructor-stage errors. +func formatConstructorErrorContext(context string, contextMark Mark, err error, mark Mark) *LoadError { + return &LoadError{ + Stage: ConstructorStage, + ContextMark: contextMark, + ContextMsg: context, + Mark: mark, + Message: err.Error(), + err: err, + } +} diff --git a/vendor/go.yaml.in/yaml/v4/internal/libyaml/desolver.go b/vendor/go.yaml.in/yaml/v4/internal/libyaml/desolver.go new file mode 100644 index 000000000..9f406eebc --- /dev/null +++ b/vendor/go.yaml.in/yaml/v4/internal/libyaml/desolver.go @@ -0,0 +1,152 @@ +// Copyright 2025 The go-yaml Project Contributors +// SPDX-License-Identifier: Apache-2.0 + +// Desolver stage: Removes inferable tags from YAML nodes. +// This is the inverse of the Resolver - it walks a tagged node tree and +// removes tags that can be inferred during parsing, producing cleaner YAML +// output without unnecessary type annotations. + +package libyaml + +// Desolver handles tag removal for YAML nodes during serialization. +// It removes tags that would be automatically resolved to the same type +// during parsing, making the output cleaner and more readable. +type Desolver struct { + opts *Options +} + +// NewDesolver creates a new Desolver with the given options. +func NewDesolver(opts *Options) *Desolver { + return &Desolver{opts: opts} +} + +// Desolve walks the node tree and removes tags that can be inferred. +// This is the inverse of Resolver - it takes a fully-tagged node tree +// (from Representer) and removes unnecessary tags to produce clean output. +// +// For scalar nodes: if the value would resolve to the same tag when parsed, +// the tag is removed. For strings that would resolve differently, the tag is +// removed and quoting style is set to preserve the string type. +// +// For collection nodes (maps/sequences): default tags (!!map, !!seq) are +// removed since they're implied by the structure. +func (d *Desolver) Desolve(n *Node) { + if n == nil { + return + } + + switch n.Kind { + case ScalarNode: + d.desolveScalar(n) + case DocumentNode, SequenceNode, MappingNode: + d.desolveCollection(n) + // Recursively desolve children + for _, child := range n.Content { + d.Desolve(child) + } + case AliasNode: + // Alias nodes don't have tags to remove + } +} + +// desolveScalar removes tags from scalar nodes when they can be inferred. +func (d *Desolver) desolveScalar(n *Node) { + // If explicitly tagged by user (TaggedStyle), keep it + if n.Style&TaggedStyle != 0 { + return + } + + // Empty tag means it's already untagged - nothing to do + if n.Tag == "" { + return + } + + stag := shortTag(n.Tag) + + // Check if this is a standard scalar tag that we can potentially remove + isStandardTag := false + switch stag { + case nullTag, boolTag, strTag, intTag, floatTag, timestampTag: + isStandardTag = true + case binaryTag: + // Binary scalars are not implicitly resolvable - never remove. + return + case mergeTag: + // Elide the implicit !!merge tag when the value is the canonical + // merge key marker. The TaggedStyle early-return above already + // preserves !!merge when it was explicit in the source. + if n.Value == "<<" { + n.Tag = "" + } + return + default: + // Custom tag - preserve it + return + } + + // Only process standard tags from here + if !isStandardTag { + return + } + + // What tag would this value resolve to? + rtag, _ := resolve("", n.Value) + + // If resolved tag matches current tag, we can elide the tag + if rtag == stag { + // Tag can be inferred - remove it + n.Tag = "" + } else if stag == strTag { + // This is a string type, but would resolve to something else. + // Remove the tag and force quoting to preserve string type. + n.Tag = "" + // If not already quoted, set quote style based on content + if n.Style&(SingleQuotedStyle|DoubleQuotedStyle|LiteralStyle|FoldedStyle) == 0 { + // Determine quote style based on options or default to single quotes + if d.opts != nil { + // Convert ScalarStyle to Style + switch d.opts.QuotePreference.ScalarStyle() { + case DOUBLE_QUOTED_SCALAR_STYLE: + n.Style |= DoubleQuotedStyle + default: + n.Style |= SingleQuotedStyle + } + } else { + n.Style |= SingleQuotedStyle + } + } + } else if stag == floatTag || stag == intTag { + // For numeric type mismatches (like float64(1) → "1" with !!float tag): + // Elide the tag and let YAML resolve naturally. + // Without the tag, "1" resolves as !!int, which may change the type, + // but that's acceptable for cleaner output (and matches old behavior). + n.Tag = "" + } + // For other standard tags with mismatches, keep the tag to preserve type +} + +// desolveCollection removes default tags from collection nodes. +func (d *Desolver) desolveCollection(n *Node) { + // If explicitly tagged by user, keep it + if n.Style&TaggedStyle != 0 { + return + } + + stag := shortTag(n.Tag) + switch n.Kind { + case MappingNode: + // !!map is the default for mappings - remove it + if stag == mapTag { + n.Tag = "" + } + case SequenceNode: + // !!seq is the default for sequences - remove it + if stag == seqTag { + n.Tag = "" + } + case DocumentNode: + // Documents don't have tags in YAML output + n.Tag = "" + } + // For other tags, keep them - they're explicit type information +} diff --git a/vendor/go.yaml.in/yaml/v4/internal/libyaml/doc.go b/vendor/go.yaml.in/yaml/v4/internal/libyaml/doc.go new file mode 100644 index 000000000..7f8690575 --- /dev/null +++ b/vendor/go.yaml.in/yaml/v4/internal/libyaml/doc.go @@ -0,0 +1,8 @@ +// Copyright 2025 The go-yaml Project Contributors +// SPDX-License-Identifier: Apache-2.0 + +// Package libyaml contains internal helpers for working with YAML +// +// It's a reworked version of the original libyaml package from go-yaml v2/v3, +// adapted to work with Go specifications +package libyaml diff --git a/vendor/go.yaml.in/yaml/v4/internal/libyaml/dumper.go b/vendor/go.yaml.in/yaml/v4/internal/libyaml/dumper.go new file mode 100644 index 000000000..b65e81d7e --- /dev/null +++ b/vendor/go.yaml.in/yaml/v4/internal/libyaml/dumper.go @@ -0,0 +1,149 @@ +// Copyright 2025 The go-yaml Project Contributors +// SPDX-License-Identifier: Apache-2.0 + +// This file contains the Dumper API for writing YAML documents. +// +// Primary functions: +// - Dump: Encode value(s) to YAML (use WithAll for multi-doc) +// - NewDumper: Create a streaming dumper to io.Writer + +package libyaml + +import ( + "bytes" + "io" + "reflect" +) + +// A Dumper writes YAML values to an output stream with configurable options. +// It uses a 3-stage pipeline mirroring the Loader: +// 1. Representer: Go values → Tagged Node tree +// 2. Desolver: Remove inferable tags +// 3. Serializer: Node tree → Events → YAML +type Dumper struct { + representer *Representer + desolver *Desolver + serializer *Serializer + options *Options +} + +// NewDumper returns a new Dumper that writes to w with the given options. +// +// The Dumper should be closed after use to flush all data to w. +func NewDumper(w io.Writer, opts ...Option) (*Dumper, error) { + o, err := ApplyOptions(opts...) + if err != nil { + return nil, err + } + return &Dumper{ + representer: NewRepresenter(o), // No writer - builds nodes + desolver: NewDesolver(o), + serializer: NewSerializer(w, o), // Writer here - emits YAML + options: o, + }, nil +} + +// Dump encodes a value to YAML with the given options. +// +// By default, Dump encodes a single value as a single YAML document. +// +// Use WithAllDocuments() to encode multiple values as a multi-document stream: +// +// docs := []Config{config1, config2, config3} +// yaml.Dump(docs, yaml.WithAllDocuments()) +// +// When WithAllDocuments is used, in must be a slice. +// Each element is encoded as a separate YAML document with "---" separators. +// +// See [Marshal] for details about the conversion of Go values to YAML. +func Dump(in any, opts ...Option) (out []byte, err error) { + defer handleErr(&err) + + o, err := ApplyOptions(opts...) + if err != nil { + return nil, err + } + + var buf bytes.Buffer + d, err := NewDumper(&buf, func(opts *Options) error { + *opts = *o // Copy options + return nil + }) + if err != nil { + return nil, err + } + + if o.AllDocuments { + // Multi-document mode: in must be a slice + inVal := reflect.ValueOf(in) + if inVal.Kind() != reflect.Slice { + return nil, &DumpError{ + Stage: RepresenterStage, + Message: "WithAllDocuments requires a slice input", + } + } + + // Dump each element as a separate document + for i := 0; i < inVal.Len(); i++ { + if err := d.Dump(inVal.Index(i).Interface()); err != nil { + return nil, err + } + } + } else { + // Single-document mode + if err := d.Dump(in); err != nil { + return nil, err + } + } + + if err := d.Close(); err != nil { + return nil, err + } + return buf.Bytes(), nil +} + +// Dump writes the YAML encoding of v to the stream. +// +// If multiple values are dumped to the stream, the second and subsequent +// documents will be preceded with a "---" document separator. +// +// See the documentation for [Marshal] for details about the conversion of Go +// values to YAML. +func (d *Dumper) Dump(v any) (err error) { + defer handleErr(&err) + + // Stage 1: Represent - Go values → Tagged Node tree + node := d.representer.Represent("", reflect.ValueOf(v)) + + // Stage 2: Desolve - Remove inferable tags + d.desolver.Desolve(node) + + // Stage 3: Serialize - Node tree → Events → YAML + d.serializer.Serialize(node) + + return nil +} + +// Close closes the Dumper by writing any remaining data. +// It does not write a stream terminating string "...". +func (d *Dumper) Close() (err error) { + defer handleErr(&err) + d.serializer.Finish() + return nil +} + +// SetIndent changes the indentation used when encoding. +// This is used by the legacy Encoder.SetIndent() method. +func (d *Dumper) SetIndent(spaces int) { + if spaces < 0 { + failDumpf(SerializerStage, "cannot indent to a negative number of spaces") + } + // Set on serializer's emitter + d.serializer.Emitter.BestIndent = spaces +} + +// SetCompactSeqIndent controls whether '- ' is considered part of the indentation. +// This is used by the legacy Encoder methods. +func (d *Dumper) SetCompactSeqIndent(compact bool) { + d.serializer.Emitter.CompactSequenceIndent = compact +} diff --git a/vendor/go.yaml.in/yaml/v4/internal/libyaml/emitter.go b/vendor/go.yaml.in/yaml/v4/internal/libyaml/emitter.go new file mode 100644 index 000000000..e603a196c --- /dev/null +++ b/vendor/go.yaml.in/yaml/v4/internal/libyaml/emitter.go @@ -0,0 +1,2371 @@ +// Copyright 2006-2010 Kirill Simonov +// Copyright 2011-2019 Canonical Ltd +// Copyright 2025 The go-yaml Project Contributors +// SPDX-License-Identifier: Apache-2.0 AND MIT + +// Emitter stage: Generates YAML output from events. +// Handles formatting, indentation, line wrapping, and output buffering. + +package libyaml + +import ( + "bytes" + "fmt" + "io" +) + +// WriteHandler 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 +// yamlEmitter.setOutput(). +// @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 WriteHandler func(emitter *Emitter, buffer []byte) error + +// EmitterState represents the current state of the emitter. +type EmitterState int + +// The emitter states. +const ( + // Expect STREAM-START. + EMIT_STREAM_START_STATE EmitterState = iota + + EMIT_FIRST_DOCUMENT_START_STATE // Expect the first DOCUMENT-START or STREAM-END. + EMIT_DOCUMENT_START_STATE // Expect DOCUMENT-START or STREAM-END. + EMIT_DOCUMENT_CONTENT_STATE // Expect the content of a document. + EMIT_DOCUMENT_END_STATE // Expect DOCUMENT-END. + EMIT_FLOW_SEQUENCE_FIRST_ITEM_STATE // Expect the first item of a flow sequence. + EMIT_FLOW_SEQUENCE_TRAIL_ITEM_STATE // Expect the next item of a flow sequence, with the comma already written out + EMIT_FLOW_SEQUENCE_ITEM_STATE // Expect an item of a flow sequence. + EMIT_FLOW_MAPPING_FIRST_KEY_STATE // Expect the first key of a flow mapping. + EMIT_FLOW_MAPPING_TRAIL_KEY_STATE // Expect the next key of a flow mapping, with the comma already written out + EMIT_FLOW_MAPPING_KEY_STATE // Expect a key of a flow mapping. + EMIT_FLOW_MAPPING_SIMPLE_VALUE_STATE // Expect a value for a simple key of a flow mapping. + EMIT_FLOW_MAPPING_VALUE_STATE // Expect a value of a flow mapping. + EMIT_BLOCK_SEQUENCE_FIRST_ITEM_STATE // Expect the first item of a block sequence. + EMIT_BLOCK_SEQUENCE_ITEM_STATE // Expect an item of a block sequence. + EMIT_BLOCK_MAPPING_FIRST_KEY_STATE // Expect the first key of a block mapping. + EMIT_BLOCK_MAPPING_KEY_STATE // Expect the key of a block mapping. + EMIT_BLOCK_MAPPING_SIMPLE_VALUE_STATE // Expect a value for a simple key of a block mapping. + EMIT_BLOCK_MAPPING_VALUE_STATE // Expect a value of a block mapping. + EMIT_END_STATE // Expect nothing. +) + +// Emitter holds all information about the current state of the emitter. +type Emitter struct { + // Writer stuff + + write_handler WriteHandler // 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. + + encoding Encoding // The stream encoding. + + // Emitter stuff + + canonical bool // If the output is in the canonical style? + BestIndent 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 LineBreak // The preferred line break. + quotePreference QuoteStyle // Preferred quote style when quoting is required. + + state EmitterState // The current emitter state. + states []EmitterState // The stack of states. + + events []Event // The event queue. + events_head int // The head of the event queue. + + indents []int // The stack of indentation levels. + + tag_directives []TagDirective // The list of tag directives. + + indent int // The current indentation level. + + CompactSequenceIndent bool // Is '- ' is considered part of the indentation for sequence elements? + + 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 (' ', '-', '?', ':')? + OpenEnded 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 expressed 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 ScalarStyle // The output style. + } + + // Comments + HeadComment []byte + LineComment []byte + FootComment []byte + TailComment []byte + + key_line_comment []byte + + // Representer 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. +} + +// NewEmitter creates a new emitter object. +func NewEmitter() Emitter { + return Emitter{ + buffer: make([]byte, output_buffer_size), + states: make([]EmitterState, 0, initial_stack_size), + events: make([]Event, 0, initial_queue_size), + best_width: -1, + } +} + +// Emit an event. +func (emitter *Emitter) Emit(event *Event) error { + emitter.events = append(emitter.events, *event) + for !emitter.needMoreEvents() { + event := &emitter.events[emitter.events_head] + if err := emitter.analyzeEvent(event); err != nil { + return err + } + if err := emitter.stateMachine(event); err != nil { + return err + } + event.Delete() + emitter.events_head++ + } + return nil +} + +// Delete an emitter object. +func (emitter *Emitter) Delete() { + *emitter = Emitter{} +} + +// String write handler. +func yamlStringWriteHandler(emitter *Emitter, buffer []byte) error { + *emitter.output_buffer = append(*emitter.output_buffer, buffer...) + return nil +} + +// yamlWriterWriteHandler uses emitter.output_writer to write the +// emitted text. +func yamlWriterWriteHandler(emitter *Emitter, buffer []byte) error { + _, err := emitter.output_writer.Write(buffer) + return err +} + +// SetOutputString sets a string output. +func (emitter *Emitter) SetOutputString(output_buffer *[]byte) { + if emitter.write_handler != nil { + panic("must set the output target only once") + } + emitter.write_handler = yamlStringWriteHandler + emitter.output_buffer = output_buffer +} + +// SetOutputWriter sets a file output. +func (emitter *Emitter) SetOutputWriter(w io.Writer) { + if emitter.write_handler != nil { + panic("must set the output target only once") + } + emitter.write_handler = yamlWriterWriteHandler + emitter.output_writer = w +} + +// SetEncoding sets the output encoding. +func (emitter *Emitter) SetEncoding(encoding Encoding) { + if emitter.encoding != ANY_ENCODING { + panic("must set the output encoding only once") + } + emitter.encoding = encoding +} + +// SetCanonical sets the canonical output style. +func (emitter *Emitter) SetCanonical(canonical bool) { + emitter.canonical = canonical +} + +// SetIndent sets the indentation increment. +func (emitter *Emitter) SetIndent(indent int) { + if indent < 2 || indent > 9 { + indent = 2 + } + emitter.BestIndent = indent +} + +// SetWidth sets the preferred line width. +func (emitter *Emitter) SetWidth(width int) { + if width < 0 { + width = -1 + } + emitter.best_width = width +} + +// SetUnicode sets if unescaped non-ASCII characters are allowed. +func (emitter *Emitter) SetUnicode(unicode bool) { + emitter.unicode = unicode +} + +// SetLineBreak sets the preferred line break character. +func (emitter *Emitter) SetLineBreak(line_break LineBreak) { + emitter.line_break = line_break +} + +// State dispatcher. +func (emitter *Emitter) stateMachine(event *Event) error { + switch emitter.state { + default: + case EMIT_STREAM_START_STATE: + return emitter.emitStreamStart(event) + + case EMIT_FIRST_DOCUMENT_START_STATE: + return emitter.emitDocumentStart(event, true) + + case EMIT_DOCUMENT_START_STATE: + return emitter.emitDocumentStart(event, false) + + case EMIT_DOCUMENT_CONTENT_STATE: + return emitter.emitDocumentContent(event) + + case EMIT_DOCUMENT_END_STATE: + return emitter.emitDocumentEnd(event) + + case EMIT_FLOW_SEQUENCE_FIRST_ITEM_STATE: + return emitter.emitFlowSequenceItem(event, true, false) + + case EMIT_FLOW_SEQUENCE_TRAIL_ITEM_STATE: + return emitter.emitFlowSequenceItem(event, false, true) + + case EMIT_FLOW_SEQUENCE_ITEM_STATE: + return emitter.emitFlowSequenceItem(event, false, false) + + case EMIT_FLOW_MAPPING_FIRST_KEY_STATE: + return emitter.emitFlowMappingKey(event, true, false) + + case EMIT_FLOW_MAPPING_TRAIL_KEY_STATE: + return emitter.emitFlowMappingKey(event, false, true) + + case EMIT_FLOW_MAPPING_KEY_STATE: + return emitter.emitFlowMappingKey(event, false, false) + + case EMIT_FLOW_MAPPING_SIMPLE_VALUE_STATE: + return emitter.emitFlowMappingValue(event, true) + + case EMIT_FLOW_MAPPING_VALUE_STATE: + return emitter.emitFlowMappingValue(event, false) + + case EMIT_BLOCK_SEQUENCE_FIRST_ITEM_STATE: + return emitter.emitBlockSequenceItem(event, true) + + case EMIT_BLOCK_SEQUENCE_ITEM_STATE: + return emitter.emitBlockSequenceItem(event, false) + + case EMIT_BLOCK_MAPPING_FIRST_KEY_STATE: + return emitter.emitBlockMappingKey(event, true) + + case EMIT_BLOCK_MAPPING_KEY_STATE: + return emitter.emitBlockMappingKey(event, false) + + case EMIT_BLOCK_MAPPING_SIMPLE_VALUE_STATE: + return emitter.emitBlockMappingValue(event, true) + + case EMIT_BLOCK_MAPPING_VALUE_STATE: + return emitter.emitBlockMappingValue(event, false) + + case EMIT_END_STATE: + return EmitterError{ + Message: "expected nothing after STREAM-END", + } + } + panic("invalid emitter state") +} + +// Check if we need to accumulate more events before emitting. +// +// We accumulate extra +// - 1 event for DOCUMENT-START +// - 2 events for SEQUENCE-START +// - 3 events for MAPPING-START +func (emitter *Emitter) needMoreEvents() bool { + if emitter.events_head == len(emitter.events) { + return true + } + var accumulate int + switch emitter.events[emitter.events_head].Type { + case DOCUMENT_START_EVENT: + accumulate = 1 + case SEQUENCE_START_EVENT: + accumulate = 2 + case MAPPING_START_EVENT: + accumulate = 3 + default: + return false + } + if len(emitter.events)-emitter.events_head > accumulate { + return false + } + var level int + for i := emitter.events_head; i < len(emitter.events); i++ { + switch emitter.events[i].Type { + case STREAM_START_EVENT, DOCUMENT_START_EVENT, SEQUENCE_START_EVENT, MAPPING_START_EVENT: + level++ + case STREAM_END_EVENT, DOCUMENT_END_EVENT, SEQUENCE_END_EVENT, MAPPING_END_EVENT: + level-- + } + if level == 0 { + return false + } + } + return true +} + +// Expect STREAM-START. +func (emitter *Emitter) emitStreamStart(event *Event) error { + if event.Type != STREAM_START_EVENT { + return EmitterError{ + Message: "expected STREAM-START", + } + } + if emitter.encoding == ANY_ENCODING { + emitter.encoding = event.encoding + if emitter.encoding == ANY_ENCODING { + emitter.encoding = UTF8_ENCODING + } + } + if emitter.BestIndent < 2 || emitter.BestIndent > 9 { + emitter.BestIndent = 2 + } + if emitter.best_width >= 0 && emitter.best_width <= emitter.BestIndent*2 { + emitter.best_width = 80 + } + if emitter.best_width < 0 { + emitter.best_width = 1<<31 - 1 + } + if emitter.line_break == ANY_BREAK { + emitter.line_break = LN_BREAK + } + + emitter.indent = -1 + emitter.line = 0 + emitter.column = 0 + emitter.whitespace = true + emitter.indention = true + emitter.space_above = true + emitter.foot_indent = -1 + + if emitter.encoding != UTF8_ENCODING { + if err := emitter.writeBom(); err != nil { + return err + } + } + emitter.state = EMIT_FIRST_DOCUMENT_START_STATE + return nil +} + +// Expect DOCUMENT-START or STREAM-END. +func (emitter *Emitter) emitDocumentStart(event *Event, first bool) error { + if event.Type == DOCUMENT_START_EVENT { + + if event.versionDirective != nil { + if err := emitter.analyzeVersionDirective(event.versionDirective); err != nil { + return err + } + } + + for i := 0; i < len(event.tagDirectives); i++ { + tag_directive := &event.tagDirectives[i] + if err := emitter.analyzeTagDirective(tag_directive); err != nil { + return err + } + if err := emitter.appendTagDirective(tag_directive, false); err != nil { + return err + } + } + + for i := 0; i < len(default_tag_directives); i++ { + tag_directive := &default_tag_directives[i] + if err := emitter.appendTagDirective(tag_directive, true); err != nil { + return err + } + } + + implicit := event.Implicit + if !first || emitter.canonical { + implicit = false + } + + if emitter.OpenEnded && (event.versionDirective != nil || len(event.tagDirectives) > 0) { + if err := emitter.writeIndicator([]byte("..."), true, false, false); err != nil { + return err + } + if err := emitter.writeIndent(); err != nil { + return err + } + } + + if event.versionDirective != nil { + implicit = false + if err := emitter.writeIndicator([]byte("%YAML"), true, false, false); err != nil { + return err + } + if err := emitter.writeIndicator([]byte("1.1"), true, false, false); err != nil { + return err + } + if err := emitter.writeIndent(); err != nil { + return err + } + } + + if len(event.tagDirectives) > 0 { + implicit = false + for i := 0; i < len(event.tagDirectives); i++ { + tag_directive := &event.tagDirectives[i] + if err := emitter.writeIndicator([]byte("%TAG"), true, false, false); err != nil { + return err + } + if err := emitter.writeTagHandle(tag_directive.handle); err != nil { + return err + } + if err := emitter.writeTagContent(tag_directive.prefix, true); err != nil { + return err + } + if err := emitter.writeIndent(); err != nil { + return err + } + } + } + + if emitter.checkEmptyDocument() { + implicit = false + } + if !implicit { + if err := emitter.writeIndent(); err != nil { + return err + } + if err := emitter.writeIndicator([]byte("---"), true, false, false); err != nil { + return err + } + if emitter.canonical || true { + if err := emitter.writeIndent(); err != nil { + return err + } + } + } + + if len(emitter.HeadComment) > 0 { + if err := emitter.processHeadComment(); err != nil { + return err + } + if err := emitter.putLineBreak(); err != nil { + return err + } + } + + emitter.state = EMIT_DOCUMENT_CONTENT_STATE + return nil + } + + if event.Type == STREAM_END_EVENT { + if emitter.OpenEnded { + if err := emitter.writeIndicator([]byte("..."), true, false, false); err != nil { + return err + } + if err := emitter.writeIndent(); err != nil { + return err + } + } + if err := emitter.flush(); err != nil { + return err + } + emitter.state = EMIT_END_STATE + return nil + } + + return EmitterError{ + Message: "expected DOCUMENT-START or STREAM-END", + } +} + +// Expect the root node. +func (emitter *Emitter) emitDocumentContent(event *Event) error { + emitter.states = append(emitter.states, EMIT_DOCUMENT_END_STATE) + + if err := emitter.processHeadComment(); err != nil { + return err + } + if err := emitter.emitNode(event, true, false, false, false); err != nil { + return err + } + if err := emitter.processLineComment(); err != nil { + return err + } + if err := emitter.processFootComment(); err != nil { + return err + } + return nil +} + +// Expect DOCUMENT-END. +func (emitter *Emitter) emitDocumentEnd(event *Event) error { + if event.Type != DOCUMENT_END_EVENT { + return EmitterError{ + Message: "expected DOCUMENT-END", + } + } + // [Go] Force document foot separation. + emitter.foot_indent = 0 + if err := emitter.processFootComment(); err != nil { + return err + } + emitter.foot_indent = -1 + if err := emitter.writeIndent(); err != nil { + return err + } + if !event.Implicit { + // [Go] Allocate the slice elsewhere. + if err := emitter.writeIndicator([]byte("..."), true, false, false); err != nil { + return err + } + if err := emitter.writeIndent(); err != nil { + return err + } + } + if err := emitter.flush(); err != nil { + return err + } + emitter.state = EMIT_DOCUMENT_START_STATE + emitter.tag_directives = emitter.tag_directives[:0] + return nil +} + +// Expect a flow item node. +func (emitter *Emitter) emitFlowSequenceItem(event *Event, first, trail bool) error { + if first { + if err := emitter.writeIndicator([]byte{'['}, true, true, false); err != nil { + return err + } + if err := emitter.increaseIndent(true, false); err != nil { + return err + } + emitter.flow_level++ + } + + if event.Type == SEQUENCE_END_EVENT { + if emitter.canonical && !first && !trail { + if err := emitter.writeIndicator([]byte{','}, false, false, false); err != nil { + return err + } + } + emitter.flow_level-- + emitter.indent = emitter.indents[len(emitter.indents)-1] + emitter.indents = emitter.indents[:len(emitter.indents)-1] + if emitter.column == 0 || emitter.canonical && !first { + if err := emitter.writeIndent(); err != nil { + return err + } + } + if err := emitter.writeIndicator([]byte{']'}, false, false, false); err != nil { + return err + } + if err := emitter.processLineComment(); err != nil { + return err + } + if err := emitter.processFootComment(); err != nil { + return err + } + emitter.state = emitter.states[len(emitter.states)-1] + emitter.states = emitter.states[:len(emitter.states)-1] + + return nil + } + + if !first && !trail { + if err := emitter.writeIndicator([]byte{','}, false, false, false); err != nil { + return err + } + } + + if err := emitter.processHeadComment(); err != nil { + return err + } + if emitter.column == 0 { + if err := emitter.writeIndent(); err != nil { + return err + } + } + + if emitter.canonical || emitter.column > emitter.best_width { + if err := emitter.writeIndent(); err != nil { + return err + } + } + if len(emitter.LineComment)+len(emitter.FootComment)+len(emitter.TailComment) > 0 { + emitter.states = append(emitter.states, EMIT_FLOW_SEQUENCE_TRAIL_ITEM_STATE) + } else { + emitter.states = append(emitter.states, EMIT_FLOW_SEQUENCE_ITEM_STATE) + } + if err := emitter.emitNode(event, false, true, false, false); err != nil { + return err + } + if len(emitter.LineComment)+len(emitter.FootComment)+len(emitter.TailComment) > 0 { + if err := emitter.writeIndicator([]byte{','}, false, false, false); err != nil { + return err + } + } + if err := emitter.processLineComment(); err != nil { + return err + } + if err := emitter.processFootComment(); err != nil { + return err + } + return nil +} + +// Expect a flow key node. +func (emitter *Emitter) emitFlowMappingKey(event *Event, first, trail bool) error { + if first { + if err := emitter.writeIndicator([]byte{'{'}, true, true, false); err != nil { + return err + } + if err := emitter.increaseIndent(true, false); err != nil { + return err + } + emitter.flow_level++ + } + + if event.Type == MAPPING_END_EVENT { + if (emitter.canonical || + len(emitter.HeadComment)+len(emitter.FootComment)+len(emitter.TailComment) > 0) && + !first && !trail { + if err := emitter.writeIndicator([]byte{','}, false, false, false); err != nil { + return err + } + } + if err := emitter.processHeadComment(); err != nil { + return err + } + emitter.flow_level-- + emitter.indent = emitter.indents[len(emitter.indents)-1] + emitter.indents = emitter.indents[:len(emitter.indents)-1] + if emitter.canonical && !first { + if err := emitter.writeIndent(); err != nil { + return err + } + } + if err := emitter.writeIndicator([]byte{'}'}, false, false, false); err != nil { + return err + } + if err := emitter.processLineComment(); err != nil { + return err + } + if err := emitter.processFootComment(); err != nil { + return err + } + emitter.state = emitter.states[len(emitter.states)-1] + emitter.states = emitter.states[:len(emitter.states)-1] + return nil + } + + if !first && !trail { + if err := emitter.writeIndicator([]byte{','}, false, false, false); err != nil { + return err + } + } + + if err := emitter.processHeadComment(); err != nil { + return err + } + + if emitter.column == 0 { + if err := emitter.writeIndent(); err != nil { + return err + } + } + + if emitter.canonical || emitter.column > emitter.best_width { + if err := emitter.writeIndent(); err != nil { + return err + } + } + + if !emitter.canonical && emitter.checkSimpleKey() { + emitter.states = append(emitter.states, EMIT_FLOW_MAPPING_SIMPLE_VALUE_STATE) + return emitter.emitNode(event, false, false, true, true) + } + if err := emitter.writeIndicator([]byte{'?'}, true, false, false); err != nil { + return err + } + emitter.states = append(emitter.states, EMIT_FLOW_MAPPING_VALUE_STATE) + return emitter.emitNode(event, false, false, true, false) +} + +// Expect a flow value node. +func (emitter *Emitter) emitFlowMappingValue(event *Event, simple bool) error { + if simple { + if err := emitter.writeIndicator([]byte{':'}, false, false, false); err != nil { + return err + } + } else { + if emitter.canonical || emitter.column > emitter.best_width { + if err := emitter.writeIndent(); err != nil { + return err + } + } + if err := emitter.writeIndicator([]byte{':'}, true, false, false); err != nil { + return err + } + } + if len(emitter.LineComment)+len(emitter.FootComment)+len(emitter.TailComment) > 0 { + emitter.states = append(emitter.states, EMIT_FLOW_MAPPING_TRAIL_KEY_STATE) + } else { + emitter.states = append(emitter.states, EMIT_FLOW_MAPPING_KEY_STATE) + } + if err := emitter.emitNode(event, false, false, true, false); err != nil { + return err + } + if len(emitter.LineComment)+len(emitter.FootComment)+len(emitter.TailComment) > 0 { + if err := emitter.writeIndicator([]byte{','}, false, false, false); err != nil { + return err + } + } + if err := emitter.processLineComment(); err != nil { + return err + } + if err := emitter.processFootComment(); err != nil { + return err + } + return nil +} + +// Expect a block item node. +func (emitter *Emitter) emitBlockSequenceItem(event *Event, first bool) error { + if first { + // emitter.mapping context tells us if we are currently in a + // mapping context. emitter.column tells us which column we + // are in the yaml output. 0 is the first char of the column. + // emitter.indentation tells us if the last character was an + // indentation character. + // emitter.compact_sequence_indent tells us if '- ' is + // considered part of the indentation for sequence elements. + // So, `seq` means that we are in a mapping context, and we are + // either at the first char of the column or the last character + // was not an indentation character, and we consider '- ' part + // of the indentation for sequence elements. + seq := emitter.mapping_context && (emitter.column == 0 || !emitter.indention) && + emitter.CompactSequenceIndent + if err := emitter.increaseIndentCompact(false, false, seq); err != nil { + return err + } + } + if event.Type == SEQUENCE_END_EVENT { + emitter.indent = emitter.indents[len(emitter.indents)-1] + emitter.indents = emitter.indents[:len(emitter.indents)-1] + emitter.state = emitter.states[len(emitter.states)-1] + emitter.states = emitter.states[:len(emitter.states)-1] + return nil + } + if err := emitter.processHeadComment(); err != nil { + return err + } + if err := emitter.writeIndent(); err != nil { + return err + } + if err := emitter.writeIndicator([]byte{'-'}, true, false, true); err != nil { + return err + } + emitter.states = append(emitter.states, EMIT_BLOCK_SEQUENCE_ITEM_STATE) + if err := emitter.emitNode(event, false, true, false, false); err != nil { + return err + } + if err := emitter.processLineComment(); err != nil { + return err + } + if err := emitter.processFootComment(); err != nil { + return err + } + return nil +} + +// Expect a block key node. +func (emitter *Emitter) emitBlockMappingKey(event *Event, first bool) error { + if first { + if err := emitter.increaseIndent(false, false); err != nil { + return err + } + } + if err := emitter.processHeadComment(); err != nil { + return err + } + if event.Type == MAPPING_END_EVENT { + emitter.indent = emitter.indents[len(emitter.indents)-1] + emitter.indents = emitter.indents[:len(emitter.indents)-1] + emitter.state = emitter.states[len(emitter.states)-1] + emitter.states = emitter.states[:len(emitter.states)-1] + return nil + } + if err := emitter.writeIndent(); err != nil { + return err + } + if len(emitter.LineComment) > 0 { + // [Go] A line comment was provided for the key. That's unusual as the + // scanner associates line comments with the value. Either way, + // save the line comment and render it appropriately later. + emitter.key_line_comment = emitter.LineComment + emitter.LineComment = nil + } + if emitter.checkSimpleKey() { + emitter.states = append(emitter.states, EMIT_BLOCK_MAPPING_SIMPLE_VALUE_STATE) + if err := emitter.emitNode(event, false, false, true, true); err != nil { + return err + } + + if event.Type == ALIAS_EVENT { + // make sure there's a space after the alias + return emitter.put(' ') + } + + return nil + } + if err := emitter.writeIndicator([]byte{'?'}, true, false, true); err != nil { + return err + } + emitter.states = append(emitter.states, EMIT_BLOCK_MAPPING_VALUE_STATE) + return emitter.emitNode(event, false, false, true, false) +} + +// Expect a block value node. +func (emitter *Emitter) emitBlockMappingValue(event *Event, simple bool) error { + if simple { + if err := emitter.writeIndicator([]byte{':'}, false, false, false); err != nil { + return err + } + } else { + if err := emitter.writeIndent(); err != nil { + return err + } + if err := emitter.writeIndicator([]byte{':'}, true, false, true); err != nil { + return err + } + } + if len(emitter.key_line_comment) > 0 { + // [Go] Line comments are generally associated with the value, + // but when there's no value on the same line as a mapping key + // they end up attached to the key itself. + if event.Type == SCALAR_EVENT { + if len(emitter.LineComment) == 0 { + // A scalar is coming and it has no line + // comments by itself yet, so just let it + // handle the line comment as usual. If it has + // a line comment, we can't have both so the + // one from the key is lost. + emitter.LineComment = emitter.key_line_comment + emitter.key_line_comment = nil + } + } else if event.SequenceStyle() != FLOW_SEQUENCE_STYLE && + (event.Type == MAPPING_START_EVENT || event.Type == SEQUENCE_START_EVENT) { + // An indented block follows, so write the comment + // right now. + emitter.LineComment, emitter.key_line_comment = emitter.key_line_comment, emitter.LineComment + if err := emitter.processLineComment(); err != nil { + return err + } + emitter.LineComment, emitter.key_line_comment = emitter.key_line_comment, emitter.LineComment + } + } + emitter.states = append(emitter.states, EMIT_BLOCK_MAPPING_KEY_STATE) + if err := emitter.emitNode(event, false, false, true, false); err != nil { + return err + } + if err := emitter.processLineComment(); err != nil { + return err + } + if err := emitter.processFootComment(); err != nil { + return err + } + return nil +} + +// Expect a node. +func (emitter *Emitter) emitNode(event *Event, + root bool, sequence bool, mapping bool, simple_key bool, +) error { + emitter.root_context = root + emitter.sequence_context = sequence + emitter.mapping_context = mapping + emitter.simple_key_context = simple_key + + switch event.Type { + case ALIAS_EVENT: + return emitter.emitAlias(event) + case SCALAR_EVENT: + return emitter.emitScalar(event) + case SEQUENCE_START_EVENT: + return emitter.emitSequenceStart(event) + case MAPPING_START_EVENT: + return emitter.emitMappingStart(event) + default: + return EmitterError{ + Message: fmt.Sprintf("expected SCALAR, SEQUENCE-START, MAPPING-START, or ALIAS, but got %v", event.Type), + } + } +} + +// Expect ALIAS. +func (emitter *Emitter) emitAlias(event *Event) error { + if err := emitter.processAnchor(); err != nil { + return err + } + emitter.state = emitter.states[len(emitter.states)-1] + emitter.states = emitter.states[:len(emitter.states)-1] + return nil +} + +// Expect SCALAR. +func (emitter *Emitter) emitScalar(event *Event) error { + if err := emitter.selectScalarStyle(event); err != nil { + return err + } + if err := emitter.processAnchor(); err != nil { + return err + } + if err := emitter.processTag(); err != nil { + return err + } + if err := emitter.increaseIndent(true, false); err != nil { + return err + } + if err := emitter.processScalar(); err != nil { + return err + } + emitter.indent = emitter.indents[len(emitter.indents)-1] + emitter.indents = emitter.indents[:len(emitter.indents)-1] + emitter.state = emitter.states[len(emitter.states)-1] + emitter.states = emitter.states[:len(emitter.states)-1] + return nil +} + +// Expect SEQUENCE-START. +func (emitter *Emitter) emitSequenceStart(event *Event) error { + if err := emitter.processAnchor(); err != nil { + return err + } + if err := emitter.processTag(); err != nil { + return err + } + if emitter.flow_level > 0 || emitter.canonical || + event.SequenceStyle() == FLOW_SEQUENCE_STYLE || + emitter.checkEmptySequence() { + emitter.state = EMIT_FLOW_SEQUENCE_FIRST_ITEM_STATE + } else { + emitter.state = EMIT_BLOCK_SEQUENCE_FIRST_ITEM_STATE + } + return nil +} + +// Expect MAPPING-START. +func (emitter *Emitter) emitMappingStart(event *Event) error { + if err := emitter.processAnchor(); err != nil { + return err + } + if err := emitter.processTag(); err != nil { + return err + } + if emitter.flow_level > 0 || emitter.canonical || + event.MappingStyle() == FLOW_MAPPING_STYLE || + emitter.checkEmptyMapping() { + emitter.state = EMIT_FLOW_MAPPING_FIRST_KEY_STATE + } else { + emitter.state = EMIT_BLOCK_MAPPING_FIRST_KEY_STATE + } + return nil +} + +// Check if the document content is an empty scalar. +func (emitter *Emitter) checkEmptyDocument() bool { + return false // [Go] Huh? +} + +// Check if the next events represent an empty sequence. +func (emitter *Emitter) checkEmptySequence() bool { + if len(emitter.events)-emitter.events_head < 2 { + return false + } + return emitter.events[emitter.events_head].Type == SEQUENCE_START_EVENT && + emitter.events[emitter.events_head+1].Type == SEQUENCE_END_EVENT +} + +// Check if the next events represent an empty mapping. +func (emitter *Emitter) checkEmptyMapping() bool { + if len(emitter.events)-emitter.events_head < 2 { + return false + } + return emitter.events[emitter.events_head].Type == MAPPING_START_EVENT && + emitter.events[emitter.events_head+1].Type == MAPPING_END_EVENT +} + +// Check if the next node can be expressed as a simple key. +func (emitter *Emitter) checkSimpleKey() bool { + length := 0 + switch emitter.events[emitter.events_head].Type { + case ALIAS_EVENT: + length += len(emitter.anchor_data.anchor) + case SCALAR_EVENT: + if emitter.scalar_data.multiline { + return false + } + length += len(emitter.anchor_data.anchor) + + len(emitter.tag_data.handle) + + len(emitter.tag_data.suffix) + + len(emitter.scalar_data.value) + case SEQUENCE_START_EVENT: + if !emitter.checkEmptySequence() { + return false + } + length += len(emitter.anchor_data.anchor) + + len(emitter.tag_data.handle) + + len(emitter.tag_data.suffix) + case MAPPING_START_EVENT: + if !emitter.checkEmptyMapping() { + return false + } + length += len(emitter.anchor_data.anchor) + + len(emitter.tag_data.handle) + + len(emitter.tag_data.suffix) + default: + return false + } + return length <= 128 +} + +// Write an anchor. +func (emitter *Emitter) processAnchor() error { + if emitter.anchor_data.anchor == nil { + return nil + } + c := []byte{'&'} + if emitter.anchor_data.alias { + c[0] = '*' + } + if err := emitter.writeIndicator(c, true, false, false); err != nil { + return err + } + return emitter.writeAnchor(emitter.anchor_data.anchor) +} + +// Write a tag. +func (emitter *Emitter) processTag() error { + if len(emitter.tag_data.handle) == 0 && len(emitter.tag_data.suffix) == 0 { + return nil + } + if len(emitter.tag_data.handle) > 0 { + if err := emitter.writeTagHandle(emitter.tag_data.handle); err != nil { + return err + } + if len(emitter.tag_data.suffix) > 0 { + if err := emitter.writeTagContent(emitter.tag_data.suffix, false); err != nil { + return err + } + } + } else { + // [Go] Allocate these slices elsewhere. + if err := emitter.writeIndicator([]byte("!<"), true, false, false); err != nil { + return err + } + if err := emitter.writeTagContent(emitter.tag_data.suffix, false); err != nil { + return err + } + if err := emitter.writeIndicator([]byte{'>'}, false, false, false); err != nil { + return err + } + } + return nil +} + +// Write a scalar. +func (emitter *Emitter) processScalar() error { + switch emitter.scalar_data.style { + case PLAIN_SCALAR_STYLE: + return emitter.writePlainScalar(emitter.scalar_data.value, !emitter.simple_key_context) + + case SINGLE_QUOTED_SCALAR_STYLE: + return emitter.writeSingleQuotedScalar(emitter.scalar_data.value, !emitter.simple_key_context) + + case DOUBLE_QUOTED_SCALAR_STYLE: + return emitter.writeDoubleQuotedScalar(emitter.scalar_data.value, !emitter.simple_key_context) + + case LITERAL_SCALAR_STYLE: + return emitter.writeLiteralScalar(emitter.scalar_data.value) + + case FOLDED_SCALAR_STYLE: + return emitter.writeFoldedScalar(emitter.scalar_data.value) + } + panic("unknown scalar style") +} + +// Write a head comment. +func (emitter *Emitter) processHeadComment() error { + if len(emitter.TailComment) > 0 { + if err := emitter.writeIndent(); err != nil { + return err + } + if err := emitter.writeComment(emitter.TailComment); err != nil { + return err + } + emitter.TailComment = emitter.TailComment[:0] + emitter.foot_indent = emitter.indent + if emitter.foot_indent < 0 { + emitter.foot_indent = 0 + } + } + + if len(emitter.HeadComment) == 0 { + return nil + } + if err := emitter.writeIndent(); err != nil { + return err + } + if err := emitter.writeComment(emitter.HeadComment); err != nil { + return err + } + emitter.HeadComment = emitter.HeadComment[:0] + return nil +} + +// processLineComment preserves the original signature and delegates to +// processLineCommentLinebreak passing false for linebreak +func (emitter *Emitter) processLineComment() error { + return emitter.processLineCommentLinebreak(false) +} + +// Write a line comment. +func (emitter *Emitter) processLineCommentLinebreak(linebreak bool) error { + if len(emitter.LineComment) == 0 { + // The next 3 lines are needed to resolve an issue with leading newlines + // See https://github.com/go-yaml/yaml/issues/755 + // When linebreak is set to true, put_break will be called and will add + // the needed newline. + if linebreak { + if err := emitter.putLineBreak(); err != nil { + return err + } + } + return nil + } + if !emitter.whitespace { + if err := emitter.put(' '); err != nil { + return err + } + } + if err := emitter.writeComment(emitter.LineComment); err != nil { + return err + } + emitter.LineComment = emitter.LineComment[:0] + return nil +} + +// Write a foot comment. +func (emitter *Emitter) processFootComment() error { + if len(emitter.FootComment) == 0 { + return nil + } + if err := emitter.writeIndent(); err != nil { + return err + } + if err := emitter.writeComment(emitter.FootComment); err != nil { + return err + } + emitter.FootComment = emitter.FootComment[:0] + emitter.foot_indent = emitter.indent + if emitter.foot_indent < 0 { + emitter.foot_indent = 0 + } + return nil +} + +// Check if a %YAML directive is valid. +func (emitter *Emitter) analyzeVersionDirective(version_directive *VersionDirective) error { + if version_directive.major != 1 || version_directive.minor != 1 { + return EmitterError{ + Message: "incompatible %YAML directive", + } + } + return nil +} + +// Check if a %TAG directive is valid. +func (emitter *Emitter) analyzeTagDirective(tag_directive *TagDirective) error { + handle := tag_directive.handle + prefix := tag_directive.prefix + if len(handle) == 0 { + return EmitterError{ + Message: "tag handle must not be empty", + } + } + if handle[0] != '!' { + return EmitterError{ + Message: "tag handle must start with '!'", + } + } + if handle[len(handle)-1] != '!' { + return EmitterError{ + Message: "tag handle must end with '!'", + } + } + for i := 1; i < len(handle)-1; i += width(handle[i]) { + if !isAlpha(handle, i) { + return EmitterError{ + Message: "tag handle must contain alphanumerical characters only", + } + } + } + if len(prefix) == 0 { + return EmitterError{ + Message: "tag prefix must not be empty", + } + } + return nil +} + +// Check if an anchor is valid. +func (emitter *Emitter) analyzeAnchor(anchor []byte, alias bool) error { + if len(anchor) == 0 { + problem := "anchor value must not be empty" + if alias { + problem = "alias value must not be empty" + } + return EmitterError{ + Message: problem, + } + } + for i := 0; i < len(anchor); i += width(anchor[i]) { + if !isAnchorChar(anchor, i) { + problem := "anchor value must contain valid characters only" + if alias { + problem = "alias value must contain valid characters only" + } + return EmitterError{ + Message: problem, + } + } + } + emitter.anchor_data.anchor = anchor + emitter.anchor_data.alias = alias + return nil +} + +// Check if a tag is valid. +func (emitter *Emitter) analyzeTag(tag []byte) error { + if len(tag) == 0 { + return EmitterError{ + Message: "tag value must not be empty", + } + } + for i := 0; i < len(emitter.tag_directives); i++ { + tag_directive := &emitter.tag_directives[i] + if bytes.HasPrefix(tag, tag_directive.prefix) { + emitter.tag_data.handle = tag_directive.handle + emitter.tag_data.suffix = tag[len(tag_directive.prefix):] + return nil + } + } + emitter.tag_data.suffix = tag + return nil +} + +// Check if a scalar is valid. +func (emitter *Emitter) analyzeScalar(value []byte) error { + var block_indicators, + flow_indicators, + line_breaks, + special_characters, + tab_characters, + + leading_space, + leading_break, + trailing_space, + trailing_break, + break_space, + space_break, + + preceded_by_whitespace, + followed_by_whitespace, + previous_space, + previous_break bool + + emitter.scalar_data.value = value + + if len(value) == 0 { + emitter.scalar_data.multiline = false + emitter.scalar_data.flow_plain_allowed = false + emitter.scalar_data.block_plain_allowed = true + emitter.scalar_data.single_quoted_allowed = true + emitter.scalar_data.block_allowed = false + return nil + } + + if len(value) >= 3 && + ((value[0] == '-' && value[1] == '-' && value[2] == '-') || + (value[0] == '.' && value[1] == '.' && value[2] == '.')) { + block_indicators = true + flow_indicators = true + } + + preceded_by_whitespace = true + for i, w := 0, 0; i < len(value); i += w { + w = width(value[i]) + followed_by_whitespace = i+w >= len(value) || isBlank(value, i+w) + + if i == 0 { + switch value[i] { + case '#', ',', '[', ']', '{', '}', '&', '*', '!', + '|', '>', '\'', '"', '%', '@', '`': + flow_indicators = true + block_indicators = true + case '?', ':': + flow_indicators = true + if followed_by_whitespace { + block_indicators = true + } + case '-': + if followed_by_whitespace { + flow_indicators = true + block_indicators = true + } + } + } else { + switch value[i] { + case ',', '?', '[', ']', '{', '}': + flow_indicators = true + case ':': + flow_indicators = true + if followed_by_whitespace { + block_indicators = true + } + case '#': + if preceded_by_whitespace { + flow_indicators = true + block_indicators = true + } + } + } + + if value[i] == '\t' { + tab_characters = true + } else if !isPrintable(value, i) || !isASCII(value, i) && !emitter.unicode { + special_characters = true + } + if isSpace(value, i) { + if i == 0 { + leading_space = true + } + if i+width(value[i]) == len(value) { + trailing_space = true + } + if previous_break { + break_space = true + } + previous_space = true + previous_break = false + } else if isLineBreak(value, i) { + line_breaks = true + if i == 0 { + leading_break = true + } + if i+width(value[i]) == len(value) { + trailing_break = true + } + if previous_space { + space_break = true + } + previous_space = false + previous_break = true + } else { + previous_space = false + previous_break = false + } + + // [Go]: Why 'z'? Couldn't be the end of the string as that's + // the loop condition. + preceded_by_whitespace = isBlankOrZero(value, i) + } + + emitter.scalar_data.multiline = line_breaks + emitter.scalar_data.flow_plain_allowed = true + emitter.scalar_data.block_plain_allowed = true + emitter.scalar_data.single_quoted_allowed = true + emitter.scalar_data.block_allowed = true + + if leading_space || leading_break || trailing_space || trailing_break { + emitter.scalar_data.flow_plain_allowed = false + emitter.scalar_data.block_plain_allowed = false + } + if trailing_space { + emitter.scalar_data.block_allowed = false + } + if break_space { + emitter.scalar_data.flow_plain_allowed = false + emitter.scalar_data.block_plain_allowed = false + emitter.scalar_data.single_quoted_allowed = false + } + if space_break || tab_characters || special_characters { + emitter.scalar_data.flow_plain_allowed = false + emitter.scalar_data.block_plain_allowed = false + emitter.scalar_data.single_quoted_allowed = false + } + if space_break || special_characters { + emitter.scalar_data.block_allowed = false + } + if line_breaks { + emitter.scalar_data.flow_plain_allowed = false + emitter.scalar_data.block_plain_allowed = false + } + if flow_indicators { + emitter.scalar_data.flow_plain_allowed = false + } + if block_indicators { + emitter.scalar_data.block_plain_allowed = false + } + return nil +} + +// Check if the event data is valid. +func (emitter *Emitter) analyzeEvent(event *Event) error { + emitter.anchor_data.anchor = nil + emitter.tag_data.handle = nil + emitter.tag_data.suffix = nil + emitter.scalar_data.value = nil + + if len(event.HeadComment) > 0 { + emitter.HeadComment = event.HeadComment + } + if len(event.LineComment) > 0 { + emitter.LineComment = event.LineComment + } + if len(event.FootComment) > 0 { + emitter.FootComment = event.FootComment + } + if len(event.TailComment) > 0 { + emitter.TailComment = event.TailComment + } + + switch event.Type { + case ALIAS_EVENT: + if err := emitter.analyzeAnchor(event.Anchor, true); err != nil { + return err + } + + case SCALAR_EVENT: + if len(event.Anchor) > 0 { + if err := emitter.analyzeAnchor(event.Anchor, false); err != nil { + return err + } + } + if len(event.Tag) > 0 && (emitter.canonical || + (!event.Implicit && !event.quoted_implicit)) { + if err := emitter.analyzeTag(event.Tag); err != nil { + return err + } + } + if err := emitter.analyzeScalar(event.Value); err != nil { + return err + } + + case SEQUENCE_START_EVENT: + if len(event.Anchor) > 0 { + if err := emitter.analyzeAnchor(event.Anchor, false); err != nil { + return err + } + } + if len(event.Tag) > 0 && (emitter.canonical || !event.Implicit) { + if err := emitter.analyzeTag(event.Tag); err != nil { + return err + } + } + + case MAPPING_START_EVENT: + if len(event.Anchor) > 0 { + if err := emitter.analyzeAnchor(event.Anchor, false); err != nil { + return err + } + } + if len(event.Tag) > 0 && (emitter.canonical || !event.Implicit) { + if err := emitter.analyzeTag(event.Tag); err != nil { + return err + } + } + } + return nil +} + +// Determine an acceptable scalar style. +func (emitter *Emitter) selectScalarStyle(event *Event) error { + no_tag := len(emitter.tag_data.handle) == 0 && len(emitter.tag_data.suffix) == 0 + if no_tag && !event.Implicit && !event.quoted_implicit { + return EmitterError{ + Message: "neither tag nor implicit flags are specified", + } + } + + style := event.ScalarStyle() + if style == ANY_SCALAR_STYLE { + style = PLAIN_SCALAR_STYLE + } + if emitter.canonical { + style = DOUBLE_QUOTED_SCALAR_STYLE + } + if emitter.simple_key_context && emitter.scalar_data.multiline { + style = DOUBLE_QUOTED_SCALAR_STYLE + } + + if style == PLAIN_SCALAR_STYLE { + if emitter.flow_level > 0 && !emitter.scalar_data.flow_plain_allowed || + emitter.flow_level == 0 && !emitter.scalar_data.block_plain_allowed { + style = emitter.requiredQuoteStyle() + } + if len(emitter.scalar_data.value) == 0 && + (emitter.flow_level > 0 || emitter.simple_key_context) { + style = emitter.requiredQuoteStyle() + } + if no_tag && !event.Implicit { + style = emitter.requiredQuoteStyle() + } + } + if style == SINGLE_QUOTED_SCALAR_STYLE { + if !emitter.scalar_data.single_quoted_allowed { + style = DOUBLE_QUOTED_SCALAR_STYLE + } + } + if style == LITERAL_SCALAR_STYLE || style == FOLDED_SCALAR_STYLE { + if !emitter.scalar_data.block_allowed || + emitter.flow_level > 0 || emitter.simple_key_context { + style = DOUBLE_QUOTED_SCALAR_STYLE + } + } + + if no_tag && !event.quoted_implicit && style != PLAIN_SCALAR_STYLE { + emitter.tag_data.handle = []byte{'!'} + } + emitter.scalar_data.style = style + return nil +} + +// Write the BOM character. +func (emitter *Emitter) writeBom() error { + if err := emitter.flushIfNeeded(); err != nil { + return err + } + pos := emitter.buffer_pos + emitter.buffer[pos+0] = '\xEF' + emitter.buffer[pos+1] = '\xBB' + emitter.buffer[pos+2] = '\xBF' + emitter.buffer_pos += 3 + return nil +} + +// writeIndent writes the appropriate indentation to the output. +func (emitter *Emitter) writeIndent() error { + indent := emitter.indent + if indent < 0 { + indent = 0 + } + if !emitter.indention || emitter.column > indent || + (emitter.column == indent && !emitter.whitespace) { + if err := emitter.putLineBreak(); err != nil { + return err + } + } + if emitter.foot_indent == indent { + if err := emitter.putLineBreak(); err != nil { + return err + } + } + for emitter.column < indent { + if err := emitter.put(' '); err != nil { + return err + } + } + emitter.whitespace = true + emitter.space_above = false + emitter.foot_indent = -1 + return nil +} + +// writeIndicator writes a YAML indicator (like ':', '-', '?') to the output. +func (emitter *Emitter) writeIndicator(indicator []byte, need_whitespace, is_whitespace, is_indention bool) error { + if need_whitespace && !emitter.whitespace { + if err := emitter.put(' '); err != nil { + return err + } + } + if err := emitter.writeAll(indicator); err != nil { + return err + } + emitter.whitespace = is_whitespace + emitter.indention = (emitter.indention && is_indention) + emitter.OpenEnded = false + return nil +} + +// writeAnchor writes an anchor name to the output. +func (emitter *Emitter) writeAnchor(value []byte) error { + if err := emitter.writeAll(value); err != nil { + return err + } + emitter.whitespace = false + emitter.indention = false + return nil +} + +// writeTagHandle writes a tag handle to the output. +func (emitter *Emitter) writeTagHandle(value []byte) error { + if !emitter.whitespace { + if err := emitter.put(' '); err != nil { + return err + } + } + if err := emitter.writeAll(value); err != nil { + return err + } + emitter.whitespace = false + emitter.indention = false + return nil +} + +// writeTagContent writes a tag URI to the output, URL-encoding special +// characters as needed. +func (emitter *Emitter) writeTagContent(value []byte, need_whitespace bool) error { + if need_whitespace && !emitter.whitespace { + if err := emitter.put(' '); err != nil { + return err + } + } + for i := 0; i < len(value); { + var must_write bool + switch value[i] { + case ';', '/', '?', ':', '@', '&', '=', '+', '$', ',', + '_', '.', '~', '*', '\'', '(', ')', '[', ']': + must_write = true + default: + must_write = isAlpha(value, i) + } + if must_write { + if err := emitter.write(value, &i); err != nil { + return err + } + } else { + w := width(value[i]) + for k := 0; k < w; k++ { + octet := value[i] + i++ + if err := emitter.put('%'); err != nil { + return err + } + + c := octet >> 4 + if c < 10 { + c += '0' + } else { + c += 'A' - 10 + } + if err := emitter.put(c); err != nil { + return err + } + + c = octet & 0x0f + if c < 10 { + c += '0' + } else { + c += 'A' - 10 + } + if err := emitter.put(c); err != nil { + return err + } + } + } + } + emitter.whitespace = false + emitter.indention = false + return nil +} + +// writePlainScalar writes a plain (unquoted) scalar to the output, handling +// line breaks and wrapping as needed. +func (emitter *Emitter) writePlainScalar(value []byte, allow_breaks bool) error { + if len(value) > 0 && !emitter.whitespace { + if err := emitter.put(' '); err != nil { + return err + } + } + + spaces := false + breaks := false + for i := 0; i < len(value); { + if isSpace(value, i) { + if allow_breaks && !spaces && + emitter.column > emitter.best_width && + !isSpace(value, i+1) { + if err := emitter.writeIndent(); err != nil { + return err + } + i += width(value[i]) + } else { + if err := emitter.write(value, &i); err != nil { + return err + } + } + spaces = true + } else if isLineBreak(value, i) { + if !breaks && value[i] == '\n' { + if err := emitter.putLineBreak(); err != nil { + return err + } + } + if err := emitter.writeLineBreak(value, &i); err != nil { + return err + } + breaks = true + } else { + if breaks { + if err := emitter.writeIndent(); err != nil { + return err + } + } + if err := emitter.write(value, &i); err != nil { + return err + } + emitter.indention = false + spaces = false + breaks = false + } + } + + if len(value) > 0 { + emitter.whitespace = false + } + emitter.indention = false + if emitter.root_context { + emitter.OpenEnded = true + } + + return nil +} + +// writeSingleQuotedScalar writes a single-quoted scalar to the output, +// escaping single quotes and handling line breaks. +func (emitter *Emitter) writeSingleQuotedScalar(value []byte, allow_breaks bool) error { + if err := emitter.writeIndicator([]byte{'\''}, true, false, false); err != nil { + return err + } + + spaces := false + breaks := false + for i := 0; i < len(value); { + if isSpace(value, i) { + if allow_breaks && !spaces && + emitter.column > emitter.best_width && + i > 0 && i < len(value)-1 && !isSpace(value, i+1) { + if err := emitter.writeIndent(); err != nil { + return err + } + i += width(value[i]) + } else { + if err := emitter.write(value, &i); err != nil { + return err + } + } + spaces = true + } else if isLineBreak(value, i) { + if !breaks && value[i] == '\n' { + if err := emitter.putLineBreak(); err != nil { + return err + } + } + if err := emitter.writeLineBreak(value, &i); err != nil { + return err + } + breaks = true + } else { + if breaks { + if err := emitter.writeIndent(); err != nil { + return err + } + } + if value[i] == '\'' { + if err := emitter.put('\''); err != nil { + return err + } + } + if err := emitter.write(value, &i); err != nil { + return err + } + emitter.indention = false + spaces = false + breaks = false + } + } + if err := emitter.writeIndicator([]byte{'\''}, false, false, false); err != nil { + return err + } + emitter.whitespace = false + emitter.indention = false + return nil +} + +// writeDoubleQuotedScalar writes a double-quoted scalar to the output, +// escaping special characters and handling Unicode. +func (emitter *Emitter) writeDoubleQuotedScalar(value []byte, allow_breaks bool) error { + spaces := false + if err := emitter.writeIndicator([]byte{'"'}, true, false, false); err != nil { + return err + } + + for i := 0; i < len(value); { + if !isPrintable(value, i) || (!emitter.unicode && !isASCII(value, i)) || + isBOM(value, i) || isLineBreak(value, i) || + value[i] == '"' || value[i] == '\\' { + + octet := value[i] + + var w int + var v rune + switch { + case octet&0x80 == 0x00: + w, v = 1, rune(octet&0x7F) + case octet&0xE0 == 0xC0: + w, v = 2, rune(octet&0x1F) + case octet&0xF0 == 0xE0: + w, v = 3, rune(octet&0x0F) + case octet&0xF8 == 0xF0: + w, v = 4, rune(octet&0x07) + } + for k := 1; k < w; k++ { + octet = value[i+k] + v = (v << 6) + (rune(octet) & 0x3F) + } + i += w + + if err := emitter.put('\\'); err != nil { + return err + } + + var err error + switch v { + case 0x00: + err = emitter.put('0') + case 0x07: + err = emitter.put('a') + case 0x08: + err = emitter.put('b') + case 0x09: + err = emitter.put('t') + case 0x0A: + err = emitter.put('n') + case 0x0b: + err = emitter.put('v') + case 0x0c: + err = emitter.put('f') + case 0x0d: + err = emitter.put('r') + case 0x1b: + err = emitter.put('e') + case 0x22: + err = emitter.put('"') + case 0x5c: + err = emitter.put('\\') + case 0x85: + err = emitter.put('N') + case 0xA0: + err = emitter.put('_') + case 0x2028: + err = emitter.put('L') + case 0x2029: + err = emitter.put('P') + default: + if v <= 0xFF { + err = emitter.put('x') + w = 2 + } else if v <= 0xFFFF { + err = emitter.put('u') + w = 4 + } else { + err = emitter.put('U') + w = 8 + } + for k := (w - 1) * 4; err == nil && k >= 0; k -= 4 { + digit := byte((v >> uint(k)) & 0x0F) + if digit < 10 { + err = emitter.put(digit + '0') + } else { + err = emitter.put(digit + 'A' - 10) + } + } + } + if err != nil { + return err + } + spaces = false + } else if isSpace(value, i) { + if allow_breaks && !spaces && + emitter.column > emitter.best_width && + i > 0 && i < len(value)-1 { + if err := emitter.writeIndent(); err != nil { + return err + } + if isSpace(value, i+1) { + if err := emitter.put('\\'); err != nil { + return err + } + } + i += width(value[i]) + } else if err := emitter.write(value, &i); err != nil { + return err + } + spaces = true + } else { + if err := emitter.write(value, &i); err != nil { + return err + } + spaces = false + } + } + if err := emitter.writeIndicator([]byte{'"'}, false, false, false); err != nil { + return err + } + emitter.whitespace = false + emitter.indention = false + return nil +} + +// writeBlockScalarHints writes the indentation and chomping indicators for +// block scalars. +func (emitter *Emitter) writeBlockScalarHints(value []byte) error { + if isSpace(value, 0) { + // https://github.com/yaml/go-yaml/issues/65 + // isLineBreak(value, 0) removed as the linebreak will only + // write the indentation value. + indent_hint := []byte{'0' + byte(emitter.BestIndent)} + if err := emitter.writeIndicator(indent_hint, false, false, false); err != nil { + return err + } + } + + emitter.OpenEnded = false + + var chomp_hint [1]byte + if len(value) == 0 { + chomp_hint[0] = '-' + } else { + i := len(value) - 1 + for value[i]&0xC0 == 0x80 { + i-- + } + if !isLineBreak(value, i) { + chomp_hint[0] = '-' + } else if i == 0 { + chomp_hint[0] = '+' + emitter.OpenEnded = true + } else { + i-- + for value[i]&0xC0 == 0x80 { + i-- + } + if isLineBreak(value, i) { + chomp_hint[0] = '+' + emitter.OpenEnded = true + } + } + } + if chomp_hint[0] != 0 { + if err := emitter.writeIndicator(chomp_hint[:], false, false, false); err != nil { + return err + } + } + return nil +} + +// writeLiteralScalar writes a literal block scalar (|) to the output, +// preserving line breaks exactly. +func (emitter *Emitter) writeLiteralScalar(value []byte) error { + if err := emitter.writeIndicator([]byte{'|'}, true, false, false); err != nil { + return err + } + if err := emitter.writeBlockScalarHints(value); err != nil { + return err + } + if err := emitter.processLineCommentLinebreak(true); err != nil { + return err + } + emitter.whitespace = true + breaks := true + for i := 0; i < len(value); { + if isLineBreak(value, i) { + if err := emitter.writeLineBreak(value, &i); err != nil { + return err + } + breaks = true + } else { + if breaks { + if err := emitter.writeIndent(); err != nil { + return err + } + } + if err := emitter.write(value, &i); err != nil { + return err + } + emitter.indention = false + breaks = false + } + } + + return nil +} + +// writeFoldedScalar writes a folded block scalar (>) to the output, folding +// long lines at appropriate breaks. +func (emitter *Emitter) writeFoldedScalar(value []byte) error { + if err := emitter.writeIndicator([]byte{'>'}, true, false, false); err != nil { + return err + } + if err := emitter.writeBlockScalarHints(value); err != nil { + return err + } + if err := emitter.processLineCommentLinebreak(true); err != nil { + return err + } + + emitter.whitespace = true + + breaks := true + leading_spaces := true + for i := 0; i < len(value); { + if isLineBreak(value, i) { + if !breaks && !leading_spaces && value[i] == '\n' { + k := 0 + for isLineBreak(value, k) { + k += width(value[k]) + } + if !isBlankOrZero(value, k) { + if err := emitter.putLineBreak(); err != nil { + return err + } + } + } + if err := emitter.writeLineBreak(value, &i); err != nil { + return err + } + breaks = true + } else { + if breaks { + if err := emitter.writeIndent(); err != nil { + return err + } + leading_spaces = isBlank(value, i) + } + if !breaks && isSpace(value, i) && + !isSpace(value, i+1) && + emitter.column > emitter.best_width { + if err := emitter.writeIndent(); err != nil { + return err + } + i += width(value[i]) + } else { + if err := emitter.write(value, &i); err != nil { + return err + } + } + emitter.indention = false + breaks = false + } + } + return nil +} + +// writeComment writes a comment to the output, ensuring each line starts +// with '#' and handling line breaks appropriately. +func (emitter *Emitter) writeComment(comment []byte) error { + breaks := false + pound := false + for i := 0; i < len(comment); { + if isLineBreak(comment, i) { + if err := emitter.writeLineBreak(comment, &i); err != nil { + return err + } + breaks = true + pound = false + } else { + if breaks { + if err := emitter.writeIndent(); err != nil { + return err + } + } + if !pound { + if comment[i] != '#' { + if err := emitter.put('#'); err != nil { + return err + } + if err := emitter.put(' '); err != nil { + return err + } + } + pound = true + } + if err := emitter.write(comment, &i); err != nil { + return err + } + emitter.indention = false + breaks = false + } + } + if !breaks { + if err := emitter.putLineBreak(); err != nil { + return err + } + } + + emitter.whitespace = true + return nil +} + +// Flush the buffer if needed. +func (emitter *Emitter) flushIfNeeded() error { + if emitter.buffer_pos+5 >= len(emitter.buffer) { + return emitter.flush() + } + return nil +} + +// Put a character to the output buffer. +func (emitter *Emitter) put(value byte) error { + if emitter.buffer_pos+5 >= len(emitter.buffer) { + if err := emitter.flush(); err != nil { + return err + } + } + emitter.buffer[emitter.buffer_pos] = value + emitter.buffer_pos++ + emitter.column++ + return nil +} + +// Put a line break to the output buffer. +func (emitter *Emitter) putLineBreak() error { + if emitter.buffer_pos+5 >= len(emitter.buffer) { + if err := emitter.flush(); err != nil { + return err + } + } + switch emitter.line_break { + case CR_BREAK: + emitter.buffer[emitter.buffer_pos] = '\r' + emitter.buffer_pos += 1 + case LN_BREAK: + emitter.buffer[emitter.buffer_pos] = '\n' + emitter.buffer_pos += 1 + case CRLN_BREAK: + emitter.buffer[emitter.buffer_pos+0] = '\r' + emitter.buffer[emitter.buffer_pos+1] = '\n' + emitter.buffer_pos += 2 + default: + panic("unknown line break setting") + } + if emitter.column == 0 { + emitter.space_above = true + } + emitter.column = 0 + emitter.line++ + // [Go] Do this here and below and drop from everywhere else (see + // commented lines). + emitter.indention = true + return nil +} + +// Copy a character from a string into buffer. +func (emitter *Emitter) write(s []byte, i *int) error { + if emitter.buffer_pos+5 >= len(emitter.buffer) { + if err := emitter.flush(); err != nil { + return err + } + } + p := emitter.buffer_pos + w := width(s[*i]) + switch w { + case 4: + emitter.buffer[p+3] = s[*i+3] + fallthrough + case 3: + emitter.buffer[p+2] = s[*i+2] + fallthrough + case 2: + emitter.buffer[p+1] = s[*i+1] + fallthrough + case 1: + emitter.buffer[p+0] = s[*i+0] + default: + panic("unknown character width") + } + emitter.column++ + emitter.buffer_pos += w + *i += w + return nil +} + +// Write a whole string into buffer. +func (emitter *Emitter) writeAll(s []byte) error { + for i := 0; i < len(s); { + if err := emitter.write(s, &i); err != nil { + return err + } + } + return nil +} + +// Copy a line break character from a string into buffer. +func (emitter *Emitter) writeLineBreak(s []byte, i *int) error { + if s[*i] == '\n' { + if err := emitter.putLineBreak(); err != nil { + return err + } + *i++ + } else { + if err := emitter.write(s, i); err != nil { + return err + } + if emitter.column == 0 { + emitter.space_above = true + } + emitter.column = 0 + emitter.line++ + // [Go] Do this here and above and drop from everywhere else + // (see commented lines). + emitter.indention = true + } + return nil +} + +// Append a directive to the directives stack. +func (emitter *Emitter) appendTagDirective(value *TagDirective, allow_duplicates bool) error { + for i := 0; i < len(emitter.tag_directives); i++ { + if bytes.Equal(value.handle, emitter.tag_directives[i].handle) { + if allow_duplicates { + return nil + } + return EmitterError{ + Message: "duplicate %TAG directive", + } + } + } + + // [Go] Do we actually need to copy this given garbage collection + // and the lack of deallocating destructors? + tag_copy := TagDirective{ + handle: make([]byte, len(value.handle)), + prefix: make([]byte, len(value.prefix)), + } + copy(tag_copy.handle, value.handle) + copy(tag_copy.prefix, value.prefix) + emitter.tag_directives = append(emitter.tag_directives, tag_copy) + return nil +} + +// Increase the indentation level. +func (emitter *Emitter) increaseIndentCompact(flow, indentless bool, compact_seq bool) error { + emitter.indents = append(emitter.indents, emitter.indent) + if emitter.indent < 0 { + if flow { + emitter.indent = emitter.BestIndent + } else { + emitter.indent = 0 + } + } else if !indentless { + // [Go] This was changed so that indentations are more regular. + if emitter.states[len(emitter.states)-1] == EMIT_BLOCK_SEQUENCE_ITEM_STATE { + // The first indent inside a sequence will just skip + // the "- " indicator. + emitter.indent += 2 + } else { + // Everything else aligns to the chosen indentation. + emitter.indent = emitter.BestIndent * + ((emitter.indent + emitter.BestIndent) / emitter.BestIndent) + if compact_seq { + // The value compact_seq passed in is almost + // always set to `false` when this function is + // called, except when we are dealing with + // sequence nodes. So this gets triggered to + // subtract 2 only when we are increasing the + // indent to account for sequence nodes, which + // will be correct because we need to subtract + // 2 to account for the - at the beginning of + // the sequence node. + emitter.indent = emitter.indent - 2 + } + } + } + return nil +} + +// emitter preserves the original signature and delegates to +// increaseIndentCompact without compact-sequence indentation +func (emitter *Emitter) increaseIndent(flow, indentless bool) error { + return emitter.increaseIndentCompact(flow, indentless, false) +} + +// silentNilEvent checks if an event represents an implicit null scalar that +// can be omitted in non-canonical mode. +func (emitter *Emitter) silentNilEvent(event *Event) bool { + return event.Type == SCALAR_EVENT && event.Implicit && !emitter.canonical && len(emitter.scalar_data.value) == 0 +} + +// requiredQuoteStyle returns the appropriate quote style based on the +// emitter's quotePreference setting. +func (emitter *Emitter) requiredQuoteStyle() ScalarStyle { + if emitter.quotePreference == QuoteDouble { + return DOUBLE_QUOTED_SCALAR_STYLE + } + return SINGLE_QUOTED_SCALAR_STYLE +} diff --git a/vendor/go.yaml.in/yaml/v4/internal/libyaml/errors.go b/vendor/go.yaml.in/yaml/v4/internal/libyaml/errors.go new file mode 100644 index 000000000..a33cc7767 --- /dev/null +++ b/vendor/go.yaml.in/yaml/v4/internal/libyaml/errors.go @@ -0,0 +1,259 @@ +// Copyright 2025 The go-yaml Project Contributors +// SPDX-License-Identifier: Apache-2.0 + +// Error types for YAML parsing and emitting. +// Provides structured error reporting with line/column information. + +package libyaml + +import ( + "errors" + "fmt" + "strings" +) + +// Stage identifies the processing stage where an error occurred during YAML +// loading or dumping. +type Stage string + +const ( + // Load stages + ReaderStage Stage = "reader" // Input reading and encoding + ScannerStage Stage = "scanner" // Tokenization + ParserStage Stage = "parser" // Event stream parsing + ComposerStage Stage = "composer" // Node tree construction + ResolverStage Stage = "resolver" // Tag resolution + ConstructorStage Stage = "constructor" // Go value construction + + // Dump stages + RepresenterStage Stage = "representer" // Go value to Node tree + SerializerStage Stage = "serializer" // Node tree to events + EmitterStage Stage = "emitter" // Events to YAML bytes + WriterStage Stage = "writer" // Output writing +) + +// LoadError represents an error that occurred while loading a YAML document. +// +// It provides detailed location information and identifies the processing +// stage where the error occurred. +type LoadError struct { + Stage Stage // Processing stage where error occurred + Message string // Error description + + // Position information + Mark Mark // Primary error position + ContextMark Mark // Optional context position (e.g., start of construct) + ContextMsg string // Optional context message + + // Error chaining + err error // Underlying error (for Unwrap support) +} + +// Error returns the error message with stage and position information. +// Format: "go-yaml load error in at L:C: " +// Or with context: "go-yaml load error in () at L:C-L:C: " +func (e *LoadError) Error() string { + if len(e.ContextMsg) > 0 { + return fmt.Sprintf("go-yaml load error in %s (%s) at %s: %s", + e.Stage, e.ContextMsg, e.ContextMark.rangeString(e.Mark), e.Message) + } + return fmt.Sprintf("go-yaml load error in %s at %s: %s", + e.Stage, e.Mark.shortString(), e.Message) +} + +// simpleError returns the error message without the "yaml: Load error (in stage)" prefix. +// Used for formatting errors within LoadErrors collections. +// Format: "line L: " (backwards compatible - no column info) +func (e *LoadError) simpleError() string { + var builder strings.Builder + if len(e.ContextMsg) > 0 { + fmt.Fprintf(&builder, "%s at %s: ", e.ContextMsg, e.ContextMark) + } + if len(e.ContextMsg) == 0 || e.ContextMark != e.Mark { + if e.Mark.Line > 0 { + fmt.Fprintf(&builder, "line %d: ", e.Mark.Line) + } else { + builder.WriteString(": ") + } + } + builder.WriteString(e.Message) + return builder.String() +} + +// Unwrap returns the underlying error. +func (e *LoadError) Unwrap() error { + return e.err +} + +// NewLoadError creates a LoadError with an underlying cause. +// The cause is accessible via Unwrap for use with [errors.Is] and [errors.As]. +func NewLoadError(stage Stage, message string, mark Mark, cause error) *LoadError { + return &LoadError{ + Stage: stage, + Message: message, + Mark: mark, + err: cause, + } +} + +// DumpError represents an error that occurred while dumping a YAML document. +// +// It identifies the processing stage where the error occurred and provides +// an optional underlying cause via Unwrap. +type DumpError struct { + Stage Stage // Processing stage where error occurred + Message string // Error description + + // Error chaining + err error // Underlying error (for Unwrap support) +} + +// Error returns the error message with stage information. +// Format: "go-yaml dump error in : " +func (e *DumpError) Error() string { + return fmt.Sprintf("go-yaml dump error in %s: %s", e.Stage, e.Message) +} + +// Unwrap returns the underlying error. +func (e *DumpError) Unwrap() error { + return e.err +} + +// NewDumpError creates a DumpError with an underlying cause. +// The cause is accessible via Unwrap for use with [errors.Is] and [errors.As]. +func NewDumpError(stage Stage, message string, cause error) *DumpError { + return &DumpError{Stage: stage, Message: message, err: cause} +} + +// failDump panics with a YAMLError wrapping a DumpError for the given stage. +// If err is exactly a *DumpError it is passed through unchanged to avoid +// double-wrapping (e.g. a user MarshalYAML that returns yaml.NewDumpError). +// Errors that merely wrap a *DumpError are treated as ordinary errors so that +// the outer wrapper's message and context are preserved. +func failDump(stage Stage, err error) { + if de, ok := err.(*DumpError); ok { + panic(&YAMLError{de}) + } + panic(&YAMLError{&DumpError{Stage: stage, Message: err.Error(), err: err}}) +} + +// failDumpf panics with a YAMLError wrapping a formatted DumpError. +func failDumpf(stage Stage, format string, args ...any) { + panic(&YAMLError{&DumpError{Stage: stage, Message: fmt.Sprintf(format, args...)}}) +} + +// EmitterError represents an error that occurred during emitting. +type EmitterError struct { + Message string +} + +// Error returns the error message. +func (e EmitterError) Error() string { + return fmt.Sprintf("yaml: %s", e.Message) +} + +// WriterError represents an error that occurred while writing output. +type WriterError struct { + Err error +} + +// Error returns the error message. +func (e WriterError) Error() string { + return fmt.Sprintf("yaml: %s", e.Err) +} + +// Unwrap returns the underlying error. +func (e WriterError) Unwrap() error { + return e.Err +} + +// LoadErrors is returned when one or more fields cannot be properly decoded. +type LoadErrors struct { + Errors []*LoadError +} + +// Error returns a formatted error message listing all construct errors. +func (e *LoadErrors) Error() string { + var b strings.Builder + b.WriteString("yaml: construct errors: ") + for i, err := range e.Errors { + if i > 0 { + b.WriteString("; ") + } + b.WriteString(err.simpleError()) + } + return b.String() +} + +// As implements [errors.As] for Go versions prior to 1.20 that don't support +// the Unwrap() []error interface. It allows [LoadErrors] to match against +// *LoadError or *TypeError targets. +func (e *LoadErrors) As(target any) bool { + switch t := target.(type) { + case **LoadError: + if len(e.Errors) == 0 { + return false + } + *t = e.Errors[0] + return true + case **TypeError: + var msgs []string + for _, err := range e.Errors { + msgs = append(msgs, err.simpleError()) + } + *t = &TypeError{Errors: msgs} + return true + } + return false +} + +// Is implements [errors.Is] for Go versions prior to 1.20 that don't support +// the Unwrap() []error interface. It checks if any wrapped error matches +// the target error. +func (e *LoadErrors) Is(target error) bool { + for _, err := range e.Errors { + if errors.Is(err, target) { + return true + } + } + return false +} + +// TypeError is a legacy error type retained for compatibility. +// +// 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. +// +// Deprecated: Use [LoadErrors] instead. +type TypeError struct { + Errors []string +} + +// Error returns a formatted error message listing all unmarshal errors. +func (e *TypeError) Error() string { + return fmt.Sprintf("yaml: unmarshal errors: %s", strings.Join(e.Errors, "; ")) +} + +// YAMLError is an internal error wrapper type. +type YAMLError struct { + Err error +} + +// Error returns the error message. +func (e *YAMLError) Error() string { + return e.Err.Error() +} + +// handleErr recovers from panics caused by yaml errors. +// It's used in defer statements to convert YAMLError panics into regular errors. +func handleErr(err *error) { + if v := recover(); v != nil { + if e, ok := v.(*YAMLError); ok { + *err = e.Err + } else { + panic(v) + } + } +} diff --git a/vendor/go.yaml.in/yaml/v4/internal/libyaml/loader.go b/vendor/go.yaml.in/yaml/v4/internal/libyaml/loader.go new file mode 100644 index 000000000..6b8081245 --- /dev/null +++ b/vendor/go.yaml.in/yaml/v4/internal/libyaml/loader.go @@ -0,0 +1,301 @@ +// Copyright 2025 The go-yaml Project Contributors +// SPDX-License-Identifier: Apache-2.0 + +// This file contains the Loader API for reading YAML documents. +// +// Primary functions: +// - Load: Decode YAML document(s) into a value (use WithAll for multi-doc) +// - NewLoader: Create a streaming loader from io.Reader + +package libyaml + +import ( + "bytes" + "errors" + "io" + "reflect" +) + +// A Loader reads and loads YAML values from an input stream with configurable +// options. +type Loader struct { + composer *Composer + resolver *Resolver + constructor *Constructor + options *Options + docCount int +} + +// NewLoader returns a new Loader that reads from r with the given options. +// +// The Loader introduces its own buffering and may read data from r beyond the +// YAML values requested. +func NewLoader(r io.Reader, opts ...Option) (*Loader, error) { + o, err := ApplyOptions(opts...) + if err != nil { + return nil, err + } + c := NewComposerFromReader(r, o) + c.SetStreamNodes(o.StreamNodes) + return &Loader{ + composer: c, + resolver: NewResolver(o), + constructor: NewConstructor(o), + options: o, + }, nil +} + +// Load loads YAML document(s) with the given options. +// +// By default, Load requires exactly one document in the input. +// If zero documents are found, it returns an error. +// If multiple documents are found, it returns an error. +// +// Use WithAllDocuments() to load all documents into a slice: +// +// var configs []Config +// yaml.Load(multiDocYAML, &configs, yaml.WithAllDocuments()) +// +// When WithAllDocuments is used, out must be a pointer to a slice. +// Each document is loaded into the slice element type. +// Zero documents results in an empty slice (no error). +// +// 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. The out parameter +// must not be nil. +// +// The type of the loaded values should be compatible with the respective +// values in out. If one or more values cannot be loaded due to type +// mismatches, decoding continues partially until the end of the YAML +// content, and a *yaml.LoadErrors is returned with details for all +// missed values. +// +// Struct fields are only loaded if they are exported (have an upper case +// first letter), and are loaded 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 control the loading and dumping behavior. +// +// For example: +// +// type T struct { +// F int `yaml:"a,omitempty"` +// B int +// } +// var t T +// yaml.Load([]byte("a: 1\nb: 2"), &t) +// +// See the documentation of Dump for the format of tags and a list of +// supported tag options. +func Load(in []byte, out any, opts ...Option) error { + o, err := ApplyOptions(opts...) + if err != nil { + return err + } + + if o.AllDocuments { + // Multi-document mode: out must be pointer to slice + return loadAll(in, out, o) + } + + // Single-document mode: exactly one document required + return loadSingle(in, out, o) +} + +// Load reads the next YAML-encoded document from its input and stores it +// in the value pointed to by v. +// +// Returns [io.EOF] when there are no more documents to read. +// If WithSingleDocument option was set and a document was already read, +// subsequent calls return [io.EOF]. +// +// Maps and pointers (to a struct, string, int, etc) are accepted as v +// values. If an internal pointer within a struct is not initialized, +// the yaml package will initialize it if necessary. The v parameter +// must not be nil. +// +// Struct fields are only loaded if they are exported (have an upper case +// first letter), and are loaded 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 control the loading and dumping behavior. +// +// See the documentation of the package-level Load function for more details +// about YAML to Go conversion and tag options. +func (l *Loader) Load(v any) (err error) { + defer handleErr(&err) + if l.options.SingleDocument && l.docCount > 0 { + return io.EOF + } + + // Stage 1: Compose - parse events into node tree (unresolved tags) + node := l.composer.Compose() // *Node + if node == nil { + return io.EOF + } + l.docCount++ + + // Stage 2: Resolve - determine implicit types for untagged scalars + l.resolver.Resolve(node) + + // Stage 3: Construct - convert node tree to Go values + out := reflect.ValueOf(v) + if out.Kind() == reflect.Pointer && !out.IsNil() { + out = out.Elem() + } + l.constructor.Construct(node, out) + if len(l.constructor.TypeErrors) > 0 { + typeErrors := l.constructor.TypeErrors + l.constructor.TypeErrors = nil + return &LoadErrors{Errors: typeErrors} + } + return nil +} + +// loadAll loads all documents from the input into a slice. +// The out parameter must be a non-nil pointer to a slice. +// Each document is appended to the slice as an element. +func loadAll(in []byte, out any, opts *Options) error { + outVal := reflect.ValueOf(out) + if outVal.Kind() != reflect.Pointer || outVal.IsNil() { + msg := "yaml: WithAllDocuments requires a non-nil pointer to a slice" + return &LoadErrors{Errors: []*LoadError{{ + Stage: ConstructorStage, + Message: msg, + err: errors.New(msg), + }}} + } + + sliceVal := outVal.Elem() + if sliceVal.Kind() != reflect.Slice { + msg := "yaml: WithAllDocuments requires a pointer to a slice" + return &LoadErrors{Errors: []*LoadError{{ + Stage: ConstructorStage, + Message: msg, + err: errors.New(msg), + }}} + } + + // Create a new slice (clear existing content) + sliceVal.Set(reflect.MakeSlice(sliceVal.Type(), 0, 0)) + + l, err := NewLoader(bytes.NewReader(in), func(o *Options) error { + *o = *opts // Copy options + return nil + }) + if err != nil { + return err + } + + elemType := sliceVal.Type().Elem() + for { + // Create new element of slice's element type + elemPtr := reflect.New(elemType) + err := l.Load(elemPtr.Interface()) + if err == io.EOF { + break + } + if err != nil { + return err + } + // Append loaded element to slice + sliceVal.Set(reflect.Append(sliceVal, elemPtr.Elem())) + } + + return nil +} + +// loadSingle loads exactly one document from the input. +// Returns an error if the input contains zero or multiple documents +// (unless FromLegacy option is set for backward compatibility). +func loadSingle(in []byte, out any, opts *Options) error { + l, err := NewLoader(bytes.NewReader(in), func(o *Options) error { + *o = *opts // Copy options + return nil + }) + if err != nil { + return err + } + + // Load first document + err = l.Load(out) + if err == io.EOF { + if opts.FromLegacy { + return nil + } + msg := "yaml: no documents in stream" + return &LoadErrors{Errors: []*LoadError{{ + Stage: ConstructorStage, + Message: msg, + err: errors.New(msg), + }}} + } + if err != nil { + return err + } + + // Skip trailing document check for legacy Unmarshal() compatibility + if opts.FromLegacy { + return nil + } + + // Check for additional documents + var dummy any + err = l.Load(&dummy) + if err != io.EOF { + if err != nil { + // Some other error occurred + return err + } + // Successfully loaded a second document - this is an error in strict mode + msg := "yaml: expected single document, found multiple" + return &LoadErrors{Errors: []*LoadError{{ + Stage: ConstructorStage, + Message: msg, + err: errors.New(msg), + }}} + } + + return nil +} + +// SetKnownFields enables or disables strict field checking for subsequent Load +// calls. +// This is used by the legacy Decoder.KnownFields() method. +func (l *Loader) SetKnownFields(enable bool) { + l.constructor.KnownFields = enable +} + +// ComposeAndResolve composes and resolves the next document from the input +// and returns the node without constructing Go values. This is used by +// Unmarshal() to support the Unmarshaler interface. +func (l *Loader) ComposeAndResolve() *Node { + if l.options.SingleDocument && l.docCount > 0 { + return nil + } + + // Stage 1: Compose - parse events into node tree (unresolved tags) + node := l.composer.Compose() + if node == nil { + return nil + } + l.docCount++ + + // Stage 2: Resolve - determine implicit types for untagged scalars + l.resolver.Resolve(node) + + return node +} + +// LoadAny parses YAML data into generic Go structures (map[string]any, []any). +// +// Useful for test data loading where the structure is unknown at compile time. +// This is a convenience wrapper around Load with an any target. +func LoadAny(data []byte) (any, error) { + var result any + if err := Load(data, &result); err != nil { + return nil, err + } + return result, nil +} diff --git a/vendor/go.yaml.in/yaml/v4/internal/libyaml/node.go b/vendor/go.yaml.in/yaml/v4/internal/libyaml/node.go new file mode 100644 index 000000000..4fee73b39 --- /dev/null +++ b/vendor/go.yaml.in/yaml/v4/internal/libyaml/node.go @@ -0,0 +1,469 @@ +// Copyright 2011-2019 Canonical Ltd +// Copyright 2025 The go-yaml Project Contributors +// SPDX-License-Identifier: Apache-2.0 + +// Node types and constants for YAML tree representation. +// Defines Kind, Style, and Node structure for intermediate YAML representation. + +package libyaml + +import ( + "reflect" + "strings" + "unicode" + "unicode/utf8" +) + +// Tag constants for YAML types +const ( + nullTag = "!!null" + boolTag = "!!bool" + strTag = "!!str" + intTag = "!!int" + floatTag = "!!float" + timestampTag = "!!timestamp" + seqTag = "!!seq" + mapTag = "!!map" + binaryTag = "!!binary" + mergeTag = "!!merge" +) + +// longTagPrefix is the standard YAML tag prefix for core types. +const longTagPrefix = "tag:yaml.org,2002:" + +// longTags maps short tags to their long form representations. +// shortTags maps long tags to their short form representations. +var ( + longTags = make(map[string]string) + shortTags = make(map[string]string) +) + +// init initializes the tag conversion maps. +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 + } +} + +// shortTag converts a long-form tag to its short form (e.g., "tag:yaml.org,2002:str" to "!!str"). +func shortTag(tag string) string { + if strings.HasPrefix(tag, longTagPrefix) { + if stag, ok := shortTags[tag]; ok { + return stag + } + return "!!" + tag[len(longTagPrefix):] + } + return tag +} + +// longTag converts a short-form tag to its long form (e.g., "!!str" to "tag:yaml.org,2002:str"). +func longTag(tag string) string { + if strings.HasPrefix(tag, "!!") { + if ltag, ok := longTags[tag]; ok { + return ltag + } + return longTagPrefix + tag[2:] + } + return tag +} + +// Kind represents the type of YAML node +type Kind uint32 + +// Kind constants define the different types of YAML nodes. +const ( + DocumentNode Kind = 1 << iota + SequenceNode + MappingNode + ScalarNode + AliasNode + StreamNode +) + +// Style represents the formatting style of a YAML node +type Style uint32 + +// Style constants define different formatting styles for YAML nodes. +const ( + TaggedStyle Style = 1 << iota + DoubleQuotedStyle + SingleQuotedStyle + LiteralStyle + FoldedStyle + FlowStyle +) + +// StreamVersionDirective represents a YAML %YAML version directive for stream nodes. +type StreamVersionDirective struct { + Major int + Minor int +} + +// StreamTagDirective represents a YAML %TAG directive for stream nodes. +type StreamTagDirective struct { + Handle string + Prefix string +} + +// Stream holds stream-level metadata for StreamNode. +// This includes encoding, version directive, and tag directives. +type Stream struct { + Encoding Encoding + Version *StreamVersionDirective + TagDirectives []StreamTagDirective +} + +// 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, columns, and comments, the content when re-encoded will not +// have its original textual representation preserved. An effort is made to +// render the data pleasantly, 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 appearance 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 representation 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 + + // Stream holds stream metadata (non-nil only when Kind == StreamNode). + Stream *Stream +} + +// 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 && + n.Stream == nil +} + +// 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: + return strTag + case 0: + // Special case to make the zero value convenient. + if n.IsZero() { + return nullTag + } + } + return "" + } + return shortTag(n.Tag) +} + +// indicatedString returns true if the node's style explicitly indicates a string type. +func (n *Node) indicatedString() bool { + return n.Kind == ScalarNode && + (shortTag(n.Tag) == strTag || + (n.Tag == "" || n.Tag == "!") && n.Style&(SingleQuotedStyle|DoubleQuotedStyle|LiteralStyle|FoldedStyle) != 0) +} + +// shouldUseLiteralStyle determines if a string should use literal style. +// It returns true if the string contains newlines AND meets additional criteria: +// - is at least 2 characters long +// - contains at least one non-whitespace character +func shouldUseLiteralStyle(s string) bool { + if !strings.Contains(s, "\n") || len(s) < 2 { + return false + } + // Must contain at least one non-whitespace character + for _, r := range s { + if !unicode.IsSpace(r) { + return true + } + } + return false +} + +// 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 shouldUseLiteralStyle(n.Value) { + n.Style = LiteralStyle + } +} + +// 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 any) (err error) { + d := NewConstructor(DefaultOptions) + defer handleErr(&err) + out := reflect.ValueOf(v) + if out.Kind() == reflect.Pointer && !out.IsNil() { + out = out.Elem() + } + d.Construct(n, out) + if len(d.TypeErrors) > 0 { + return &LoadErrors{Errors: d.TypeErrors} + } + return nil +} + +// Load decodes the node and stores its data into the value pointed to by v, +// applying the given options. +// +// This method is useful when you need to preserve options like WithKnownFields() +// inside custom UnmarshalYAML implementations. +// +// Maps and pointers (to a struct, string, int, etc) are accepted as v +// values. If an internal pointer within a struct is not initialized, +// the yaml package will initialize it if necessary. The v parameter +// must not be nil. +// +// See the documentation of the package-level Load function for details +// about YAML to Go conversion and tag options. +func (n *Node) Load(v any, opts ...Option) (err error) { + defer handleErr(&err) + o, err := ApplyOptions(opts...) + if err != nil { + return err + } + d := NewConstructor(o) + out := reflect.ValueOf(v) + if out.Kind() == reflect.Pointer && !out.IsNil() { + out = out.Elem() + } + d.Construct(n, out) + if len(d.TypeErrors) > 0 { + return &LoadErrors{Errors: d.TypeErrors} + } + 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 any) (err error) { + defer handleErr(&err) + // Use the 3-stage dump pipeline with round-trip to preserve styles + r := NewRepresenter(DefaultOptions) + node := r.Represent("", reflect.ValueOf(v)) + d := NewDesolver(DefaultOptions) + d.Desolve(node) + s := NewSerializer(nil, DefaultOptions) + var out []byte + s.Emitter.SetOutputString(&out) + s.Serialize(node) + s.Finish() + // Parse back to get styles + p := NewComposer(out, nil) + p.Textless = true + defer p.Destroy() + doc := p.Compose() + NewResolver(nil).Resolve(doc) + *n = *doc.Content[0] + return nil +} + +// Dump encodes value v and stores its representation in n, +// applying the given options. +// +// This method is useful when you need to apply specific encoding options +// while building Node trees programmatically. +// +// See the documentation for Marshal for details about the +// conversion of Go values into YAML. +func (n *Node) Dump(v any, opts ...Option) (err error) { + defer handleErr(&err) + o, err := ApplyOptions(opts...) + if err != nil { + return err + } + // Use the 3-stage dump pipeline with round-trip to preserve styles + r := NewRepresenter(o) + node := r.Represent("", reflect.ValueOf(v)) + d := NewDesolver(o) + d.Desolve(node) + s := NewSerializer(nil, o) + var out []byte + s.Emitter.SetOutputString(&out) + s.Serialize(node) + s.Finish() + // Parse back to get styles + p := NewComposer(out, nil) + p.Textless = true + defer p.Destroy() + doc := p.Compose() + NewResolver(nil).Resolve(doc) + *n = *doc.Content[0] + return nil +} + +// Marshaler interface may be implemented by types to customize their +// behavior when being marshaled into a YAML document. +type Marshaler interface { + MarshalYAML() (any, error) +} + +// Unmarshaler is the interface implemented by types that can unmarshal +// a YAML description of themselves. +type Unmarshaler interface { + UnmarshalYAML(node *Node) error +} + +// 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 +} + +// FromYAMLNode is a new interface that types can implement to customize +// their unmarshaling behavior. It receives a Node directly and modifies +// the receiver in place. +// This is the preferred interface for new code. +// +// Note: This interface is reserved for the v4 API and is not yet fully +// integrated into the current implementation. +type FromYAMLNode interface { + FromYAMLNode(*Node) error +} + +// ToYAMLNode is a new interface that types can implement to customize +// their marshaling behavior. It returns a Node directly. +// This is the preferred interface for new code. +// +// Note: This interface is reserved for the v4 API and is not yet fully +// integrated into the current implementation. +type ToYAMLNode interface { + ToYAMLNode() (*Node, error) +} + +// isZero reports whether v represents the zero value for its type. +// If v implements the IsZeroer interface, IsZero() is called. +// Otherwise, zero is determined by checking type-specific conditions. +// This is used to determine omitempty behavior when marshaling. +func isZero(v reflect.Value) bool { + kind := v.Kind() + if z, ok := v.Interface().(IsZeroer); ok { + if (kind == reflect.Pointer || kind == reflect.Interface) && v.IsNil() { + return true + } + return z.IsZero() + } + switch kind { + case reflect.String: + return len(v.String()) == 0 + case reflect.Interface, reflect.Pointer: + 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 +} diff --git a/vendor/go.yaml.in/yaml/v4/internal/libyaml/options.go b/vendor/go.yaml.in/yaml/v4/internal/libyaml/options.go new file mode 100644 index 000000000..c2a3ac4db --- /dev/null +++ b/vendor/go.yaml.in/yaml/v4/internal/libyaml/options.go @@ -0,0 +1,449 @@ +// Copyright 2025 The go-yaml Project Contributors +// SPDX-License-Identifier: Apache-2.0 + +// Options configuration for loading and dumping YAML. +// Provides centralized control for indentation, line width, strictness, and +// more. + +package libyaml + +import ( + "errors" + "fmt" +) + +// Options holds configuration for both loading and dumping YAML. +type Options struct { + // Loading options + KnownFields bool // Enforce known fields in structs + SingleDocument bool // Only load first document + UniqueKeys bool // Enforce unique keys in mappings + StreamNodes bool // Enable stream node emission + AllDocuments bool // Load/Dump all documents in multi-document streams + + // Dumping options + Indent int // Indentation spaces (2-9) + CompactSeqIndent bool // Whether '- ' counts as indentation + LineWidth int // Preferred line width (-1 for unlimited) + Unicode bool // Allow non-ASCII characters + Canonical bool // Canonical YAML output + LineBreak LineBreak // Line ending style + ExplicitStart bool // Always emit --- + ExplicitEnd bool // Always emit ... + FlowSimpleCollections bool // Use flow style for simple collections + QuotePreference QuoteStyle // Preferred quote style when quoting is required + + // Safety limit checks (set by ApplyOptions or WithPlugin(limit.New(...))) + DepthCheck func(depth int, ctx *DepthContext) error + AliasCheck func(aliasCount, constructCount int) error + + // Private options (not exported, used internally) + FromLegacy bool // Indicates legacy Unmarshal()/Decoder path (check Unmarshaler, allow trailing content) +} + +// Option allows configuring YAML loading and dumping operations. +type Option func(*Options) error + +// DepthKind represents the type of nesting (flow or block). +type DepthKind string + +// DepthKindFlow and DepthKindBlock are the possible values of DepthContext.Kind. +const ( + DepthKindFlow DepthKind = "flow" + DepthKindBlock DepthKind = "block" +) + +// DepthContext holds context about a nesting depth check. +type DepthContext struct { + Kind DepthKind +} + +// DefaultDepthCheck is the default depth check function. +// It returns an error when depth exceeds 10000. +func DefaultDepthCheck(depth int, ctx *DepthContext) error { + const maxDepth = 10000 + if depth > maxDepth { + return fmt.Errorf("exceeded max depth of %d", maxDepth) + } + return nil +} + +// DefaultAliasCheck is the default alias check function. +// It uses a ratio-based heuristic to prevent DoS attacks via excessive aliasing. +func DefaultAliasCheck(aliasCount, constructCount int) error { + const ( + aliasRatioRangeLow = 400000 + aliasRatioRangeHigh = 4000000 + aliasRatioRange = float64(aliasRatioRangeHigh - aliasRatioRangeLow) + ) + if aliasCount <= 100 || constructCount <= 1000 { + return nil + } + var allowed float64 + switch { + case constructCount <= aliasRatioRangeLow: + allowed = 0.99 + case constructCount >= aliasRatioRangeHigh: + allowed = 0.10 + default: + allowed = 0.99 - 0.89*(float64(constructCount-aliasRatioRangeLow)/aliasRatioRange) + } + if float64(aliasCount)/float64(constructCount) > allowed { + return errors.New("document contains excessive aliasing") + } + return nil +} + +// WithIndent sets the number of spaces to use for indentation when +// dumping YAML content. +// +// Valid values are 2-9. Common choices: 2 (compact), 4 (readable). +func WithIndent(indent int) Option { + return func(o *Options) error { + if indent < 2 || indent > 9 { + return errors.New("yaml: indent must be between 2 and 9 spaces") + } + o.Indent = indent + return nil + } +} + +// WithCompactSeqIndent configures whether the sequence indicator '- ' is +// considered part of the indentation when dumping YAML content. +// +// If compact is true, '- ' is treated as part of the indentation. +// If compact is false, '- ' is not treated as part of the indentation. +// When called without arguments, defaults to true. +func WithCompactSeqIndent(compact ...bool) Option { + if len(compact) > 1 { + return func(o *Options) error { + return errors.New("yaml: WithCompactSeqIndent accepts at most one argument") + } + } + val := len(compact) == 0 || compact[0] + return func(o *Options) error { + o.CompactSeqIndent = val + return nil + } +} + +// WithKnownFields enables or disables strict field checking during YAML loading. +// +// When enabled, loading will return an error if the YAML input contains fields +// that do not correspond to any fields in the target struct. +// When called without arguments, defaults to true. +func WithKnownFields(knownFields ...bool) Option { + if len(knownFields) > 1 { + return func(o *Options) error { + return errors.New("yaml: WithKnownFields accepts at most one argument") + } + } + val := len(knownFields) == 0 || knownFields[0] + return func(o *Options) error { + o.KnownFields = val + return nil + } +} + +// WithSingleDocument configures the Loader to only process the first document +// in a YAML stream. After the first document is loaded, subsequent calls to +// Load will return [io.EOF]. +// +// When called without arguments, defaults to true. +// +// This is useful when you expect exactly one document and want behavior +// similar to [Unmarshal]. +func WithSingleDocument(singleDocument ...bool) Option { + if len(singleDocument) > 1 { + return func(o *Options) error { + return errors.New("yaml: WithSingleDocument accepts at most one argument") + } + } + val := len(singleDocument) == 0 || singleDocument[0] + return func(o *Options) error { + o.SingleDocument = val + return nil + } +} + +// WithStreamNodes enables returning stream boundary nodes when loading YAML. +// +// When enabled, Loader.Load returns an interleaved sequence of StreamNode and +// DocumentNode values: +// +// [StreamNode, DocNode, StreamNode, DocNode, ..., StreamNode] +// +// StreamNodes contain metadata about the stream including: +// - Encoding (UTF-8, UTF-16LE, UTF-16BE) +// - YAML version directive (%YAML) +// - Tag directives (%TAG) +// - Position information (Line, Column) +// +// An empty YAML stream returns a single StreamNode. +// When called without arguments, defaults to true. +// +// The default is false. +func WithStreamNodes(enable ...bool) Option { + if len(enable) > 1 { + return func(o *Options) error { + return errors.New("yaml: WithStreamNodes accepts at most one argument") + } + } + val := len(enable) == 0 || enable[0] + return func(o *Options) error { + o.StreamNodes = val + return nil + } +} + +// WithAllDocuments enables multi-document mode for Load and Dump operations. +// +// When used with Load, the target must be a pointer to a slice. +// All documents in the YAML stream will be decoded into the slice. +// Zero documents results in an empty slice (no error). +// +// When used with Dump, the input must be a slice. +// Each element will be encoded as a separate YAML document +// with "---" separators. +// +// When called without arguments, defaults to true. +// +// The default is false (single-document mode). +func WithAllDocuments(all ...bool) Option { + if len(all) > 1 { + return func(o *Options) error { + return errors.New("yaml: WithAllDocuments accepts at most one argument") + } + } + val := len(all) == 0 || all[0] + return func(o *Options) error { + o.AllDocuments = val + return nil + } +} + +// WithLineWidth sets the preferred line width for YAML output. +// +// When encoding long strings, the encoder will attempt to wrap them at this +// width using literal block style (|). Set to -1 or 0 for unlimited width. +// +// The default is 80 characters. +func WithLineWidth(width int) Option { + return func(o *Options) error { + if width < 0 { + width = -1 + } + o.LineWidth = width + return nil + } +} + +// WithUnicode controls whether non-ASCII characters are allowed in YAML output. +// +// When true, non-ASCII characters appear as-is (e.g., "café"). +// When false, non-ASCII characters are escaped (e.g., "caf\u00e9"). +// When called without arguments, defaults to true. +// +// The default is true. +func WithUnicode(unicode ...bool) Option { + if len(unicode) > 1 { + return func(o *Options) error { + return errors.New("yaml: WithUnicode accepts at most one argument") + } + } + val := len(unicode) == 0 || unicode[0] + return func(o *Options) error { + o.Unicode = val + return nil + } +} + +// WithUniqueKeys enables or disables duplicate key detection during YAML loading. +// +// When enabled, loading will return an error if the YAML input contains +// duplicate keys in any mapping. This is a security feature that prevents +// key override attacks. +// When called without arguments, defaults to true. +// +// The default is true. +func WithUniqueKeys(uniqueKeys ...bool) Option { + if len(uniqueKeys) > 1 { + return func(o *Options) error { + return errors.New("yaml: WithUniqueKeys accepts at most one argument") + } + } + val := len(uniqueKeys) == 0 || uniqueKeys[0] + return func(o *Options) error { + o.UniqueKeys = val + return nil + } +} + +// WithCanonical forces canonical YAML output format. +// +// When enabled, the encoder outputs strictly canonical YAML with explicit +// tags for all values. This produces verbose output primarily useful for +// debugging and YAML spec compliance testing. +// When called without arguments, defaults to true. +// +// The default is false. +func WithCanonical(canonical ...bool) Option { + if len(canonical) > 1 { + return func(o *Options) error { + return errors.New("yaml: WithCanonical accepts at most one argument") + } + } + val := len(canonical) == 0 || canonical[0] + return func(o *Options) error { + o.Canonical = val + return nil + } +} + +// WithLineBreak sets the line ending style for YAML output. +// +// Available options: +// - LineBreakLN: Unix-style \n (default) +// - LineBreakCR: Old Mac-style \r +// - LineBreakCRLN: Windows-style \r\n +// +// The default is LineBreakLN. +func WithLineBreak(lineBreak LineBreak) Option { + return func(o *Options) error { + o.LineBreak = lineBreak + return nil + } +} + +// WithExplicitStart controls whether document start markers (---) are always emitted. +// +// When true, every document begins with an explicit "---" marker. +// When false (default), the marker is omitted for the first document. +// When called without arguments, defaults to true. +func WithExplicitStart(explicit ...bool) Option { + if len(explicit) > 1 { + return func(o *Options) error { + return errors.New("yaml: WithExplicitStart accepts at most one argument") + } + } + val := len(explicit) == 0 || explicit[0] + return func(o *Options) error { + o.ExplicitStart = val + return nil + } +} + +// WithExplicitEnd controls whether document end markers (...) are always emitted. +// +// When true, every document ends with an explicit "..." marker. +// When false (default), the marker is omitted. +// When called without arguments, defaults to true. +func WithExplicitEnd(explicit ...bool) Option { + if len(explicit) > 1 { + return func(o *Options) error { + return errors.New("yaml: WithExplicitEnd accepts at most one argument") + } + } + val := len(explicit) == 0 || explicit[0] + return func(o *Options) error { + o.ExplicitEnd = val + return nil + } +} + +// WithFlowSimpleCollections controls whether simple collections use flow style. +// +// When true, sequences and mappings containing only scalar values (no nested +// collections) are rendered in flow style if they fit within the line width. +// Example: {name: test, count: 42} or [a, b, c] +// When called without arguments, defaults to true. +// +// When false (default), all collections use block style. +func WithFlowSimpleCollections(flow ...bool) Option { + if len(flow) > 1 { + return func(o *Options) error { + return errors.New("yaml: WithFlowSimpleCollections accepts at most one argument") + } + } + val := len(flow) == 0 || flow[0] + return func(o *Options) error { + o.FlowSimpleCollections = val + return nil + } +} + +// WithQuotePreference sets the preferred quote style for strings that require +// quoting. +// +// This option only affects strings that require quoting per the YAML spec. +// Plain strings that don't need quoting remain unquoted regardless of this +// setting. Quoting is required for: +// - Strings that look like other YAML types (true, false, null, 123, etc.) +// - Strings with leading/trailing whitespace +// - Strings containing special YAML syntax characters +// - Empty strings in certain contexts +// +// Quote styles: +// - QuoteSingle: Use single quotes (v4 default) +// - QuoteDouble: Use double quotes +// - QuoteLegacy: Legacy v2/v3 behavior (mixed quoting) +func WithQuotePreference(style QuoteStyle) Option { + return func(o *Options) error { + switch style { + case QuoteSingle, QuoteDouble, QuoteLegacy: + o.QuotePreference = style + return nil + default: + return fmt.Errorf("invalid QuoteStyle value: %d", style) + } + } +} + +// CombineOptions combines multiple options into a single Option. +// This is useful for creating option presets or combining version defaults +// with custom options. +func CombineOptions(opts ...Option) Option { + return func(o *Options) error { + for _, opt := range opts { + if err := opt(o); err != nil { + return err + } + } + return nil + } +} + +// ApplyOptions applies the given options to a new options struct. +// Starts with v4 defaults. +func ApplyOptions(opts ...Option) (*Options, error) { + o := &Options{ + Canonical: false, + LineBreak: LN_BREAK, + + // v4 defaults + Indent: 2, + CompactSeqIndent: true, + LineWidth: 80, + Unicode: true, + UniqueKeys: true, + + // Default safety limits + DepthCheck: DefaultDepthCheck, + AliasCheck: DefaultAliasCheck, + } + for _, opt := range opts { + if err := opt(o); err != nil { + return nil, err + } + } + return o, nil +} + +// DefaultOptions holds the default options for APIs that don't accept options. +var DefaultOptions = &Options{ + Indent: 4, + LineWidth: -1, + Unicode: true, + UniqueKeys: true, + QuotePreference: QuoteLegacy, +} diff --git a/vendor/go.yaml.in/yaml/v4/internal/libyaml/parser.go b/vendor/go.yaml.in/yaml/v4/internal/libyaml/parser.go new file mode 100644 index 000000000..64daaaf24 --- /dev/null +++ b/vendor/go.yaml.in/yaml/v4/internal/libyaml/parser.go @@ -0,0 +1,1641 @@ +// Copyright 2006-2010 Kirill Simonov +// Copyright 2011-2019 Canonical Ltd +// Copyright 2025 The go-yaml Project Contributors +// SPDX-License-Identifier: Apache-2.0 AND MIT + +// Parser stage: Transforms token stream into event stream. +// Implements a recursive-descent parser (LL(1)) following the YAML grammar +// specification. +// +// The parser implements the following grammar: +// +// stream ::= STREAM-START implicit_document? explicit_document* STREAM-END +// implicit_document ::= block_node DOCUMENT-END* +// explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END* +// block_node_or_indentless_sequence ::= +// ALIAS +// | properties (block_content | indentless_block_sequence)? +// | block_content +// | indentless_block_sequence +// block_node ::= ALIAS +// | properties block_content? +// | block_content +// flow_node ::= ALIAS +// | properties flow_content? +// | flow_content +// properties ::= TAG ANCHOR? | ANCHOR TAG? +// block_content ::= block_collection | flow_collection | SCALAR +// flow_content ::= flow_collection | SCALAR +// block_collection ::= block_sequence | block_mapping +// flow_collection ::= flow_sequence | flow_mapping +// block_sequence ::= BLOCK-SEQUENCE-START (BLOCK-ENTRY block_node?)* BLOCK-END +// indentless_sequence ::= (BLOCK-ENTRY block_node?)+ +// block_mapping ::= BLOCK-MAPPING_START +// ((KEY block_node_or_indentless_sequence?)? +// (VALUE block_node_or_indentless_sequence?)?)* +// BLOCK-END +// flow_sequence ::= FLOW-SEQUENCE-START +// (flow_sequence_entry FLOW-ENTRY)* +// flow_sequence_entry? +// FLOW-SEQUENCE-END +// flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? +// flow_mapping ::= FLOW-MAPPING-START +// (flow_mapping_entry FLOW-ENTRY)* +// flow_mapping_entry? +// FLOW-MAPPING-END +// flow_mapping_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? + +package libyaml + +import ( + "bytes" + "io" + "strings" +) + +// ReadHandler is called by the [Parser] when it needs to read more bytes +// from the input source. The handler should fill the provided buffer with +// up to len(buffer) bytes from the input source. +// +// The arguments are as follows: +// +// [in] parser The parser object. +// [out] buffer The buffer for reading. +// [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 ReadHandler func(parser *Parser, buffer []byte) (n int, err error) + +// SimpleKey holds information about a potential simple key. +type SimpleKey struct { + flow_level int // What flow level is the key at? + required bool // Is a simple key required? + token_number int // The number of the token. + mark Mark // The position mark. +} + +// ParserState represents the state of the parser. +type ParserState int + +// Parser state constants define the different states the parser can be in. +const ( + PARSE_STREAM_START_STATE ParserState = iota + + PARSE_IMPLICIT_DOCUMENT_START_STATE // Expect the beginning of an implicit document. + PARSE_DOCUMENT_START_STATE // Expect DOCUMENT-START. + PARSE_DOCUMENT_CONTENT_STATE // Expect the content of a document. + PARSE_DOCUMENT_END_STATE // Expect DOCUMENT-END. + PARSE_BLOCK_NODE_STATE // Expect a block node. + PARSE_BLOCK_SEQUENCE_FIRST_ENTRY_STATE // Expect the first entry of a block sequence. + PARSE_BLOCK_SEQUENCE_ENTRY_STATE // Expect an entry of a block sequence. + PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE // Expect an entry of an indentless sequence. + PARSE_BLOCK_MAPPING_FIRST_KEY_STATE // Expect the first key of a block mapping. + PARSE_BLOCK_MAPPING_KEY_STATE // Expect a block mapping key. + PARSE_BLOCK_MAPPING_VALUE_STATE // Expect a block mapping value. + PARSE_FLOW_SEQUENCE_FIRST_ENTRY_STATE // Expect the first entry of a flow sequence. + PARSE_FLOW_SEQUENCE_ENTRY_STATE // Expect an entry of a flow sequence. + PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_KEY_STATE // Expect a key of an ordered mapping. + PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_VALUE_STATE // Expect a value of an ordered mapping. + PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_END_STATE // Expect the and of an ordered mapping entry. + PARSE_FLOW_MAPPING_FIRST_KEY_STATE // Expect the first key of a flow mapping. + PARSE_FLOW_MAPPING_KEY_STATE // Expect a key of a flow mapping. + PARSE_FLOW_MAPPING_VALUE_STATE // Expect a value of a flow mapping. + PARSE_FLOW_MAPPING_EMPTY_VALUE_STATE // Expect an empty value of a flow mapping. + PARSE_END_STATE // Expect nothing. +) + +// String returns a string representation of the parser state. +func (ps ParserState) String() string { + switch ps { + case PARSE_STREAM_START_STATE: + return "PARSE_STREAM_START_STATE" + case PARSE_IMPLICIT_DOCUMENT_START_STATE: + return "PARSE_IMPLICIT_DOCUMENT_START_STATE" + case PARSE_DOCUMENT_START_STATE: + return "PARSE_DOCUMENT_START_STATE" + case PARSE_DOCUMENT_CONTENT_STATE: + return "PARSE_DOCUMENT_CONTENT_STATE" + case PARSE_DOCUMENT_END_STATE: + return "PARSE_DOCUMENT_END_STATE" + case PARSE_BLOCK_NODE_STATE: + return "PARSE_BLOCK_NODE_STATE" + case PARSE_BLOCK_SEQUENCE_FIRST_ENTRY_STATE: + return "PARSE_BLOCK_SEQUENCE_FIRST_ENTRY_STATE" + case PARSE_BLOCK_SEQUENCE_ENTRY_STATE: + return "PARSE_BLOCK_SEQUENCE_ENTRY_STATE" + case PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE: + return "PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE" + case PARSE_BLOCK_MAPPING_FIRST_KEY_STATE: + return "PARSE_BLOCK_MAPPING_FIRST_KEY_STATE" + case PARSE_BLOCK_MAPPING_KEY_STATE: + return "PARSE_BLOCK_MAPPING_KEY_STATE" + case PARSE_BLOCK_MAPPING_VALUE_STATE: + return "PARSE_BLOCK_MAPPING_VALUE_STATE" + case PARSE_FLOW_SEQUENCE_FIRST_ENTRY_STATE: + return "PARSE_FLOW_SEQUENCE_FIRST_ENTRY_STATE" + case PARSE_FLOW_SEQUENCE_ENTRY_STATE: + return "PARSE_FLOW_SEQUENCE_ENTRY_STATE" + case PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_KEY_STATE: + return "PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_KEY_STATE" + case PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_VALUE_STATE: + return "PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_VALUE_STATE" + case PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_END_STATE: + return "PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_END_STATE" + case PARSE_FLOW_MAPPING_FIRST_KEY_STATE: + return "PARSE_FLOW_MAPPING_FIRST_KEY_STATE" + case PARSE_FLOW_MAPPING_KEY_STATE: + return "PARSE_FLOW_MAPPING_KEY_STATE" + case PARSE_FLOW_MAPPING_VALUE_STATE: + return "PARSE_FLOW_MAPPING_VALUE_STATE" + case PARSE_FLOW_MAPPING_EMPTY_VALUE_STATE: + return "PARSE_FLOW_MAPPING_EMPTY_VALUE_STATE" + case PARSE_END_STATE: + return "PARSE_END_STATE" + } + return "" +} + +// AliasData holds information about aliases. +type AliasData struct { + anchor []byte // The anchor. + index int // The node id. + mark Mark // The anchor mark. +} + +// Comment holds information about a comment in the YAML stream. +type Comment struct { + ScanMark Mark // Position where scanning for comments started + TokenMark Mark // Position after which tokens will be associated with this comment + StartMark Mark // Position of '#' comment mark + EndMark Mark // Position where comment terminated + + Head []byte + Line []byte + Foot []byte +} + +// Parser structure holds all information about the current +// state of the parser. +type Parser struct { + lastError error + + // Reader stuff + read_handler ReadHandler // 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 Encoding // The input encoding. + + offset int // The offset of the current position (in bytes). + mark Mark // The mark of the current position. + + // Comments + + HeadComment []byte // The current head comments + LineComment []byte // The current line comments + FootComment []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 []Comment // The folded comments for all parsed tokens + comments_head int + + skip_comments bool // Skip comment scanning for performance + + // 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 []Token // 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_key_possible bool // Is the current simple key possible? + simple_key SimpleKey // The current simple key. + simple_key_stack []SimpleKey // The stack of simple keys. + + depthCheck func(int, *DepthContext) error // Depth limit check function + + // Parser stuff + + state ParserState // The current parser state. + states []ParserState // The parser states stack. + marks []Mark // The stack of marks. + tag_directives []TagDirective // The list of TAG directives. + + // Representer stuff + + aliases []AliasData // The alias data. +} + +// NewParser creates a new parser object. +func NewParser() Parser { + return Parser{ + raw_buffer: make([]byte, 0, input_raw_buffer_size), + buffer: make([]byte, 0, input_buffer_size), + mark: Mark{Line: 1, Column: 1}, + depthCheck: DefaultDepthCheck, + } +} + +// Parse gets the next event. +func (parser *Parser) Parse(event *Event) error { + // Erase the event object. + *event = Event{} + + if parser.lastError != nil { + return parser.lastError + } + + // No events after the end of the stream or error. + if parser.stream_end_produced || parser.state == PARSE_END_STATE { + return io.EOF + } + + // Generate the next event. + if err := parser.stateMachine(event); err != nil { + parser.lastError = err + return err + } + + return nil +} + +// Delete a parser object. +func (parser *Parser) Delete() { + *parser = Parser{} +} + +// String read handler. +func yamlStringReadHandler(parser *Parser, 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 yamlReaderReadHandler(parser *Parser, buffer []byte) (n int, err error) { + return parser.input_reader.Read(buffer) +} + +// SetInputString sets a string input. +func (parser *Parser) SetInputString(input []byte) { + if parser.read_handler != nil { + panic("must set the input source only once") + } + parser.read_handler = yamlStringReadHandler + parser.input = input + parser.input_pos = 0 +} + +// SetInputReader sets a file input. +func (parser *Parser) SetInputReader(r io.Reader) { + if parser.read_handler != nil { + panic("must set the input source only once") + } + parser.read_handler = yamlReaderReadHandler + parser.input_reader = r +} + +// SetEncoding sets the source encoding. +func (parser *Parser) SetEncoding(encoding Encoding) { + if parser.encoding != ANY_ENCODING { + panic("must set the encoding only once") + } + parser.encoding = encoding +} + +// GetPendingComments returns the parser's comment queue for CLI access. +func (parser *Parser) GetPendingComments() []Comment { + return parser.comments +} + +// GetCommentsHead returns the current position in the comment queue. +func (parser *Parser) GetCommentsHead() int { + return parser.comments_head +} + +// SetSkipComments enables or disables comment scanning. +// When enabled, the scanner skips comment tokens for better performance. +func (parser *Parser) SetSkipComments(skip bool) { + parser.skip_comments = skip +} + +// default_tag_directives defines the standard tag directives (! and !!) +// that are implicitly available in all YAML documents. +var default_tag_directives = []TagDirective{ + {[]byte("!"), []byte("!")}, + {[]byte("!!"), []byte("tag:yaml.org,2002:")}, +} + +// State dispatcher. +func (parser *Parser) stateMachine(event *Event) error { + // trace("yaml_parser_state_machine", "state:", parser.state.String()) + + switch parser.state { + case PARSE_STREAM_START_STATE: + return parser.parseStreamStart(event) + + case PARSE_IMPLICIT_DOCUMENT_START_STATE: + return parser.parseDocumentStart(event, true) + + case PARSE_DOCUMENT_START_STATE: + return parser.parseDocumentStart(event, false) + + case PARSE_DOCUMENT_CONTENT_STATE: + return parser.parseDocumentContent(event) + + case PARSE_DOCUMENT_END_STATE: + return parser.parseDocumentEnd(event) + + case PARSE_BLOCK_NODE_STATE: + return parser.parseNode(event, true, false) + + case PARSE_BLOCK_SEQUENCE_FIRST_ENTRY_STATE: + return parser.parseBlockSequenceEntry(event, true) + + case PARSE_BLOCK_SEQUENCE_ENTRY_STATE: + return parser.parseBlockSequenceEntry(event, false) + + case PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE: + return parser.parseIndentlessSequenceEntry(event) + + case PARSE_BLOCK_MAPPING_FIRST_KEY_STATE: + return parser.parseBlockMappingKey(event, true) + + case PARSE_BLOCK_MAPPING_KEY_STATE: + return parser.parseBlockMappingKey(event, false) + + case PARSE_BLOCK_MAPPING_VALUE_STATE: + return parser.parseBlockMappingValue(event) + + case PARSE_FLOW_SEQUENCE_FIRST_ENTRY_STATE: + return parser.parseFlowSequenceEntry(event, true) + + case PARSE_FLOW_SEQUENCE_ENTRY_STATE: + return parser.parseFlowSequenceEntry(event, false) + + case PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_KEY_STATE: + return parser.parseFlowSequenceEntryMappingKey(event) + + case PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_VALUE_STATE: + return parser.parseFlowSequenceEntryMappingValue(event) + + case PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_END_STATE: + return parser.parseFlowSequenceEntryMappingEnd(event) + + case PARSE_FLOW_MAPPING_FIRST_KEY_STATE: + return parser.parseFlowMappingKey(event, true) + + case PARSE_FLOW_MAPPING_KEY_STATE: + return parser.parseFlowMappingKey(event, false) + + case PARSE_FLOW_MAPPING_VALUE_STATE: + return parser.parseFlowMappingValue(event, false) + + case PARSE_FLOW_MAPPING_EMPTY_VALUE_STATE: + return parser.parseFlowMappingValue(event, true) + + default: + panic("invalid parser state") + } +} + +// Parse the production: +// +// stream ::= STREAM-START implicit_document? explicit_document* STREAM-END +// ************ +func (parser *Parser) parseStreamStart(event *Event) error { + var token *Token + if err := parser.peekToken(&token); err != nil { + return err + } + if token.Type != STREAM_START_TOKEN { + return formatParserError("did not find expected ", token.StartMark) + } + parser.state = PARSE_IMPLICIT_DOCUMENT_START_STATE + *event = Event{ + Type: STREAM_START_EVENT, + StartMark: token.StartMark, + EndMark: token.EndMark, + encoding: token.encoding, + } + parser.skipToken() + return nil +} + +// Parse the productions: +// +// implicit_document ::= block_node DOCUMENT-END* +// * +// explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END* +// ************************* +func (parser *Parser) parseDocumentStart(event *Event, implicit bool) error { + var token *Token + if err := parser.peekToken(&token); err != nil { + return err + } + + // Parse extra document end indicators. + for token.Type == DOCUMENT_END_TOKEN { + parser.skipToken() + if err := parser.peekToken(&token); err != nil { + return err + } + } + + if implicit && token.Type != VERSION_DIRECTIVE_TOKEN && + token.Type != TAG_DIRECTIVE_TOKEN && + token.Type != DOCUMENT_START_TOKEN && + token.Type != STREAM_END_TOKEN { + // Parse an implicit document. + if err := parser.processDirectives(nil, nil); err != nil { + return err + } + parser.states = append(parser.states, PARSE_DOCUMENT_END_STATE) + parser.state = PARSE_BLOCK_NODE_STATE + + var head_comment []byte + if len(parser.HeadComment) > 0 { + // [Go] Scan the header comment backwards, and if an + // empty line is found, break the header so the part + // before the last empty line goes into the document + // header, while the bottom of it goes into a follow up + // event. + for i := len(parser.HeadComment) - 1; i > 0; i-- { + if parser.HeadComment[i] == '\n' { + if i == len(parser.HeadComment)-1 { + head_comment = parser.HeadComment[:i] + parser.HeadComment = parser.HeadComment[i+1:] + break + } else if parser.HeadComment[i-1] == '\n' { + head_comment = parser.HeadComment[:i-1] + parser.HeadComment = parser.HeadComment[i+1:] + break + } + } + } + } + + *event = Event{ + Type: DOCUMENT_START_EVENT, + StartMark: token.StartMark, + EndMark: token.EndMark, + Implicit: true, + + HeadComment: head_comment, + } + + } else if token.Type != STREAM_END_TOKEN { + // Parse an explicit document. + var version_directive *VersionDirective + var tag_directives []TagDirective + start_mark := token.StartMark + if err := parser.processDirectives(&version_directive, &tag_directives); err != nil { + return err + } + if err := parser.peekToken(&token); err != nil { + return err + } + if token.Type != DOCUMENT_START_TOKEN { + return formatParserError( + "did not find expected ", token.StartMark) + } + parser.states = append(parser.states, PARSE_DOCUMENT_END_STATE) + parser.state = PARSE_DOCUMENT_CONTENT_STATE + end_mark := token.EndMark + + *event = Event{ + Type: DOCUMENT_START_EVENT, + StartMark: start_mark, + EndMark: end_mark, + versionDirective: version_directive, + tagDirectives: tag_directives, + Implicit: false, + } + parser.skipToken() + + } else { + // Parse the stream end. + parser.state = PARSE_END_STATE + *event = Event{ + Type: STREAM_END_EVENT, + StartMark: token.StartMark, + EndMark: token.EndMark, + } + parser.setEventComments(event) + parser.skipToken() + } + + return nil +} + +// Parse the productions: +// +// explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END* +// *********** +func (parser *Parser) parseDocumentContent(event *Event) error { + var token *Token + if err := parser.peekToken(&token); err != nil { + return err + } + + if token.Type == VERSION_DIRECTIVE_TOKEN || + token.Type == TAG_DIRECTIVE_TOKEN || + token.Type == DOCUMENT_START_TOKEN || + token.Type == DOCUMENT_END_TOKEN || + token.Type == STREAM_END_TOKEN { + parser.state = parser.states[len(parser.states)-1] + parser.states = parser.states[:len(parser.states)-1] + return parser.processEmptyScalar(event, + token.StartMark) + } + return parser.parseNode(event, true, false) +} + +// Parse the productions: +// +// implicit_document ::= block_node DOCUMENT-END* +// ************* +// explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END* +func (parser *Parser) parseDocumentEnd(event *Event) error { + var token *Token + if err := parser.peekToken(&token); err != nil { + return err + } + + start_mark := token.StartMark + end_mark := token.StartMark + + implicit := true + if token.Type == DOCUMENT_END_TOKEN { + end_mark = token.EndMark + parser.skipToken() + implicit = false + } + + parser.tag_directives = parser.tag_directives[:0] + + parser.state = PARSE_DOCUMENT_START_STATE + *event = Event{ + Type: DOCUMENT_END_EVENT, + StartMark: start_mark, + EndMark: end_mark, + Implicit: implicit, + } + parser.setEventComments(event) + if len(event.HeadComment) > 0 && len(event.FootComment) == 0 { + event.FootComment = event.HeadComment + event.HeadComment = nil + } + return nil +} + +// Parse directives. +func (parser *Parser) processDirectives(version_directive_ref **VersionDirective, tag_directives_ref *[]TagDirective) error { + var version_directive *VersionDirective + var tag_directives []TagDirective + + var token *Token + if err := parser.peekToken(&token); err != nil { + return err + } + + for token.Type == VERSION_DIRECTIVE_TOKEN || token.Type == TAG_DIRECTIVE_TOKEN { + switch token.Type { + case VERSION_DIRECTIVE_TOKEN: + if version_directive != nil { + return formatParserError( + "found duplicate %YAML directive", token.StartMark) + } + if token.major != 1 || token.minor != 1 { + return formatParserError( + "found incompatible YAML document", token.StartMark) + } + version_directive = &VersionDirective{ + major: token.major, + minor: token.minor, + } + case TAG_DIRECTIVE_TOKEN: + value := TagDirective{ + handle: token.Value, + prefix: token.prefix, + } + if err := parser.appendTagDirective(value, false, token.StartMark); err != nil { + return err + } + tag_directives = append(tag_directives, value) + } + + parser.skipToken() + if err := parser.peekToken(&token); err != nil { + return err + } + } + + for i := range default_tag_directives { + if err := parser.appendTagDirective(default_tag_directives[i], true, token.StartMark); err != nil { + return err + } + } + + if version_directive_ref != nil { + *version_directive_ref = version_directive + } + if tag_directives_ref != nil { + *tag_directives_ref = tag_directives + } + return nil +} + +// Append a tag directive to the directives stack. +func (parser *Parser) appendTagDirective(value TagDirective, allow_duplicates bool, mark Mark) error { + for i := range parser.tag_directives { + if bytes.Equal(value.handle, parser.tag_directives[i].handle) { + if allow_duplicates { + return nil + } + return formatParserError("found duplicate %TAG directive", mark) + } + } + + // [Go] I suspect the copy is unnecessary. This was likely done + // because there was no way to track ownership of the data. + value_copy := TagDirective{ + handle: make([]byte, len(value.handle)), + prefix: make([]byte, len(value.prefix)), + } + copy(value_copy.handle, value.handle) + copy(value_copy.prefix, value.prefix) + parser.tag_directives = append(parser.tag_directives, value_copy) + return nil +} + +// Parse the productions: +// +// block_node_or_indentless_sequence ::= +// ALIAS +// ***** +// | properties (block_content | indentless_block_sequence)? +// ********** * +// | block_content | indentless_block_sequence +// * +// block_node ::= ALIAS +// ***** +// | properties block_content? +// ********** * +// | block_content +// * +// flow_node ::= ALIAS +// ***** +// | properties flow_content? +// ********** * +// | flow_content +// * +// properties ::= TAG ANCHOR? | ANCHOR TAG? +// ************************* +// block_content ::= block_collection | flow_collection | SCALAR +// ****** +// flow_content ::= flow_collection | SCALAR +// ****** +func (parser *Parser) parseNode(event *Event, block, indentless_sequence bool) error { + // defer trace("yaml_parser_parse_node", "block:", block, "indentless_sequence:", indentless_sequence)() + + var token *Token + if err := parser.peekToken(&token); err != nil { + return err + } + + if token.Type == ALIAS_TOKEN { + parser.state = parser.states[len(parser.states)-1] + parser.states = parser.states[:len(parser.states)-1] + *event = Event{ + Type: ALIAS_EVENT, + StartMark: token.StartMark, + EndMark: token.EndMark, + Anchor: token.Value, + } + parser.setEventComments(event) + parser.skipToken() + return nil + } + + start_mark := token.StartMark + end_mark := token.StartMark + + var tag_token bool + var tag_handle, tag_suffix, anchor []byte + var tag_mark Mark + switch token.Type { + case ANCHOR_TOKEN: + anchor = token.Value + start_mark = token.StartMark + end_mark = token.EndMark + parser.skipToken() + if err := parser.peekToken(&token); err != nil { + return err + } + if token.Type == TAG_TOKEN { + tag_token = true + tag_handle = token.Value + tag_suffix = token.suffix + tag_mark = token.StartMark + end_mark = token.EndMark + parser.skipToken() + if err := parser.peekToken(&token); err != nil { + return err + } + } + case TAG_TOKEN: + tag_token = true + tag_handle = token.Value + tag_suffix = token.suffix + start_mark = token.StartMark + tag_mark = token.StartMark + end_mark = token.EndMark + parser.skipToken() + if err := parser.peekToken(&token); err != nil { + return err + } + if token.Type == ANCHOR_TOKEN { + anchor = token.Value + end_mark = token.EndMark + parser.skipToken() + if err := parser.peekToken(&token); err != nil { + return err + } + } + } + + var tag []byte + if tag_token { + if len(tag_handle) == 0 { + tag = tag_suffix + } else { + for i := range parser.tag_directives { + if bytes.Equal(parser.tag_directives[i].handle, tag_handle) { + tag = append([]byte(nil), parser.tag_directives[i].prefix...) + tag = append(tag, tag_suffix...) + break + } + } + if len(tag) == 0 { + return formatParserErrorContext( + "while parsing a node", start_mark, + "found undefined tag handle", tag_mark) + } + } + } + + implicit := len(tag) == 0 + if indentless_sequence && token.Type == BLOCK_ENTRY_TOKEN { + end_mark = token.EndMark + parser.state = PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE + *event = Event{ + Type: SEQUENCE_START_EVENT, + StartMark: start_mark, + EndMark: end_mark, + Anchor: anchor, + Tag: tag, + Implicit: implicit, + Style: Style(BLOCK_SEQUENCE_STYLE), + } + return nil + } + if token.Type == SCALAR_TOKEN { + var plain_implicit, quoted_implicit bool + end_mark = token.EndMark + if (len(tag) == 0 && token.Style == PLAIN_SCALAR_STYLE) || (len(tag) == 1 && tag[0] == '!') { + plain_implicit = true + } else if len(tag) == 0 { + quoted_implicit = true + } + parser.state = parser.states[len(parser.states)-1] + parser.states = parser.states[:len(parser.states)-1] + + *event = Event{ + Type: SCALAR_EVENT, + StartMark: start_mark, + EndMark: end_mark, + Anchor: anchor, + Tag: tag, + Value: token.Value, + Implicit: plain_implicit, + quoted_implicit: quoted_implicit, + Style: Style(token.Style), + } + parser.setEventComments(event) + parser.skipToken() + return nil + } + if token.Type == FLOW_SEQUENCE_START_TOKEN { + // [Go] Some of the events below can be merged as they differ only on style. + end_mark = token.EndMark + parser.state = PARSE_FLOW_SEQUENCE_FIRST_ENTRY_STATE + *event = Event{ + Type: SEQUENCE_START_EVENT, + StartMark: start_mark, + EndMark: end_mark, + Anchor: anchor, + Tag: tag, + Implicit: implicit, + Style: Style(FLOW_SEQUENCE_STYLE), + } + parser.setEventComments(event) + return nil + } + if token.Type == FLOW_MAPPING_START_TOKEN { + end_mark = token.EndMark + parser.state = PARSE_FLOW_MAPPING_FIRST_KEY_STATE + *event = Event{ + Type: MAPPING_START_EVENT, + StartMark: start_mark, + EndMark: end_mark, + Anchor: anchor, + Tag: tag, + Implicit: implicit, + Style: Style(FLOW_MAPPING_STYLE), + } + parser.setEventComments(event) + return nil + } + if block && token.Type == BLOCK_SEQUENCE_START_TOKEN { + end_mark = token.EndMark + parser.state = PARSE_BLOCK_SEQUENCE_FIRST_ENTRY_STATE + *event = Event{ + Type: SEQUENCE_START_EVENT, + StartMark: start_mark, + EndMark: end_mark, + Anchor: anchor, + Tag: tag, + Implicit: implicit, + Style: Style(BLOCK_SEQUENCE_STYLE), + } + if parser.stem_comment != nil { + event.HeadComment = parser.stem_comment + parser.stem_comment = nil + } + return nil + } + if block && token.Type == BLOCK_MAPPING_START_TOKEN { + end_mark = token.EndMark + parser.state = PARSE_BLOCK_MAPPING_FIRST_KEY_STATE + *event = Event{ + Type: MAPPING_START_EVENT, + StartMark: start_mark, + EndMark: end_mark, + Anchor: anchor, + Tag: tag, + Implicit: implicit, + Style: Style(BLOCK_MAPPING_STYLE), + } + if parser.stem_comment != nil { + event.HeadComment = parser.stem_comment + parser.stem_comment = nil + } + return nil + } + if len(anchor) > 0 || len(tag) > 0 { + parser.state = parser.states[len(parser.states)-1] + parser.states = parser.states[:len(parser.states)-1] + + *event = Event{ + Type: SCALAR_EVENT, + StartMark: start_mark, + EndMark: end_mark, + Anchor: anchor, + Tag: tag, + Implicit: implicit, + quoted_implicit: false, + Style: Style(PLAIN_SCALAR_STYLE), + } + return nil + } + + context := "while parsing a flow node" + if block { + context = "while parsing a block node" + } + return formatParserErrorContext(context, start_mark, + "did not find expected node content", token.StartMark) +} + +// Parse the productions: +// +// block_sequence ::= BLOCK-SEQUENCE-START (BLOCK-ENTRY block_node?)* BLOCK-END +// ******************** *********** * ********* +func (parser *Parser) parseBlockSequenceEntry(event *Event, first bool) error { + if first { + var token *Token + if err := parser.peekToken(&token); err != nil { + return err + } + parser.marks = append(parser.marks, token.StartMark) + parser.skipToken() + } + + var token *Token + if err := parser.peekToken(&token); err != nil { + return err + } + + if token.Type == BLOCK_ENTRY_TOKEN { + mark := token.EndMark + prior_head_len := len(parser.HeadComment) + parser.skipToken() + if err := parser.splitStemComment(prior_head_len); err != nil { + return err + } + if err := parser.peekToken(&token); err != nil { + return err + } + if token.Type != BLOCK_ENTRY_TOKEN && token.Type != BLOCK_END_TOKEN { + parser.states = append(parser.states, PARSE_BLOCK_SEQUENCE_ENTRY_STATE) + return parser.parseNode(event, true, false) + } else { + parser.state = PARSE_BLOCK_SEQUENCE_ENTRY_STATE + return parser.processEmptyScalar(event, mark) + } + } + if token.Type == BLOCK_END_TOKEN { + parser.state = parser.states[len(parser.states)-1] + parser.states = parser.states[:len(parser.states)-1] + parser.marks = parser.marks[:len(parser.marks)-1] + + *event = Event{ + Type: SEQUENCE_END_EVENT, + StartMark: token.StartMark, + EndMark: token.EndMark, + } + + parser.skipToken() + return nil + } + + context_mark := parser.marks[len(parser.marks)-1] + parser.marks = parser.marks[:len(parser.marks)-1] + return formatParserErrorContext( + "while parsing a block collection", context_mark, + "did not find expected '-' indicator", token.StartMark) +} + +// Parse the productions: +// +// indentless_sequence ::= (BLOCK-ENTRY block_node?)+ +// *********** * +func (parser *Parser) parseIndentlessSequenceEntry(event *Event) error { + var token *Token + if err := parser.peekToken(&token); err != nil { + return err + } + + if token.Type == BLOCK_ENTRY_TOKEN { + mark := token.EndMark + prior_head_len := len(parser.HeadComment) + parser.skipToken() + if err := parser.splitStemComment(prior_head_len); err != nil { + return err + } + if err := parser.peekToken(&token); err != nil { + return err + } + if token.Type != BLOCK_ENTRY_TOKEN && + token.Type != KEY_TOKEN && + token.Type != VALUE_TOKEN && + token.Type != BLOCK_END_TOKEN { + parser.states = append(parser.states, PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE) + return parser.parseNode(event, true, false) + } + parser.state = PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE + return parser.processEmptyScalar(event, mark) + } + parser.state = parser.states[len(parser.states)-1] + parser.states = parser.states[:len(parser.states)-1] + + *event = Event{ + Type: SEQUENCE_END_EVENT, + StartMark: token.StartMark, + EndMark: token.StartMark, // [Go] Shouldn't this be token.end_mark? + } + return nil +} + +// Split stem comment from head comment. +// +// When a sequence or map is found under a sequence entry, the former head +// comment is assigned to the underlying sequence or map as a whole, not the +// individual sequence or map entry as would be expected otherwise. +// To handle this case the previous head comment is moved aside as the stem +// comment. +func (parser *Parser) splitStemComment(stem_len int) error { + if stem_len == 0 { + return nil + } + + var token *Token + if err := parser.peekToken(&token); err != nil { + return err + } + if token.Type != BLOCK_SEQUENCE_START_TOKEN && token.Type != BLOCK_MAPPING_START_TOKEN { + return nil + } + + parser.stem_comment = parser.HeadComment[:stem_len] + if len(parser.HeadComment) == stem_len { + parser.HeadComment = nil + } else { + // Copy suffix to prevent very strange bugs if someone ever appends + // further bytes to the prefix in the stem_comment slice above. + parser.HeadComment = append([]byte(nil), parser.HeadComment[stem_len+1:]...) + } + return nil +} + +// Parse the productions: +// +// block_mapping ::= BLOCK-MAPPING_START +// ******************* +// ((KEY block_node_or_indentless_sequence?)? +// *** * +// (VALUE block_node_or_indentless_sequence?)?)* +// +// BLOCK-END +// ********* +func (parser *Parser) parseBlockMappingKey(event *Event, first bool) error { + if first { + var token *Token + if err := parser.peekToken(&token); err != nil { + return err + } + parser.marks = append(parser.marks, token.StartMark) + parser.skipToken() + } + + var token *Token + if err := parser.peekToken(&token); err != nil { + return err + } + + // [Go] A tail comment was left from the prior mapping value processed. + // Emit an event as it needs to be processed with that value and not + // the following key. + if len(parser.tail_comment) > 0 { + *event = Event{ + Type: TAIL_COMMENT_EVENT, + StartMark: token.StartMark, + EndMark: token.EndMark, + FootComment: parser.tail_comment, + } + parser.tail_comment = nil + return nil + } + + switch token.Type { + case KEY_TOKEN: + mark := token.EndMark + parser.skipToken() + if err := parser.peekToken(&token); err != nil { + return err + } + if token.Type != KEY_TOKEN && + token.Type != VALUE_TOKEN && + token.Type != BLOCK_END_TOKEN { + parser.states = append(parser.states, PARSE_BLOCK_MAPPING_VALUE_STATE) + return parser.parseNode(event, true, true) + } else { + parser.state = PARSE_BLOCK_MAPPING_VALUE_STATE + return parser.processEmptyScalar(event, mark) + } + case BLOCK_END_TOKEN: + parser.state = parser.states[len(parser.states)-1] + parser.states = parser.states[:len(parser.states)-1] + parser.marks = parser.marks[:len(parser.marks)-1] + *event = Event{ + Type: MAPPING_END_EVENT, + StartMark: token.StartMark, + EndMark: token.EndMark, + } + parser.setEventComments(event) + parser.skipToken() + return nil + } + + context_mark := parser.marks[len(parser.marks)-1] + parser.marks = parser.marks[:len(parser.marks)-1] + return formatParserErrorContext( + "while parsing a block mapping", context_mark, + "did not find expected key", token.StartMark) +} + +// Parse the productions: +// +// block_mapping ::= BLOCK-MAPPING_START +// +// ((KEY block_node_or_indentless_sequence?)? +// +// (VALUE block_node_or_indentless_sequence?)?)* +// ***** * +// BLOCK-END +func (parser *Parser) parseBlockMappingValue(event *Event) error { + var token *Token + if err := parser.peekToken(&token); err != nil { + return err + } + if token.Type == VALUE_TOKEN { + mark := token.EndMark + parser.skipToken() + if err := parser.peekToken(&token); err != nil { + return err + } + if token.Type != KEY_TOKEN && + token.Type != VALUE_TOKEN && + token.Type != BLOCK_END_TOKEN { + parser.states = append(parser.states, PARSE_BLOCK_MAPPING_KEY_STATE) + return parser.parseNode(event, true, true) + } + parser.state = PARSE_BLOCK_MAPPING_KEY_STATE + return parser.processEmptyScalar(event, mark) + } + parser.state = PARSE_BLOCK_MAPPING_KEY_STATE + return parser.processEmptyScalar(event, token.StartMark) +} + +// Parse the productions: +// +// flow_sequence ::= FLOW-SEQUENCE-START +// ******************* +// (flow_sequence_entry FLOW-ENTRY)* +// * ********** +// flow_sequence_entry? +// * +// FLOW-SEQUENCE-END +// ***************** +// flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? +// * +func (parser *Parser) parseFlowSequenceEntry(event *Event, first bool) error { + if first { + var token *Token + if err := parser.peekToken(&token); err != nil { + return err + } + parser.marks = append(parser.marks, token.StartMark) + parser.skipToken() + } + var token *Token + if err := parser.peekToken(&token); err != nil { + return err + } + if token.Type != FLOW_SEQUENCE_END_TOKEN { + if !first { + if token.Type == FLOW_ENTRY_TOKEN { + parser.skipToken() + if err := parser.peekToken(&token); err != nil { + return err + } + } else { + context_mark := parser.marks[len(parser.marks)-1] + parser.marks = parser.marks[:len(parser.marks)-1] + return formatParserErrorContext( + "while parsing a flow sequence", context_mark, + "did not find expected ',' or ']'", token.StartMark) + } + } + + if token.Type == KEY_TOKEN { + parser.state = PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_KEY_STATE + *event = Event{ + Type: MAPPING_START_EVENT, + StartMark: token.StartMark, + EndMark: token.EndMark, + Implicit: true, + Style: Style(FLOW_MAPPING_STYLE), + } + parser.skipToken() + return nil + } else if token.Type != FLOW_SEQUENCE_END_TOKEN { + parser.states = append(parser.states, PARSE_FLOW_SEQUENCE_ENTRY_STATE) + return parser.parseNode(event, false, false) + } + } + + parser.state = parser.states[len(parser.states)-1] + parser.states = parser.states[:len(parser.states)-1] + parser.marks = parser.marks[:len(parser.marks)-1] + + *event = Event{ + Type: SEQUENCE_END_EVENT, + StartMark: token.StartMark, + EndMark: token.EndMark, + } + parser.setEventComments(event) + + parser.skipToken() + return nil +} + +// Parse the productions: +// +// flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? +// *** * +func (parser *Parser) parseFlowSequenceEntryMappingKey(event *Event) error { + var token *Token + if err := parser.peekToken(&token); err != nil { + return err + } + if token.Type != VALUE_TOKEN && + token.Type != FLOW_ENTRY_TOKEN && + token.Type != FLOW_SEQUENCE_END_TOKEN { + parser.states = append(parser.states, PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_VALUE_STATE) + return parser.parseNode(event, false, false) + } + mark := token.EndMark + parser.skipToken() + parser.state = PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_VALUE_STATE + return parser.processEmptyScalar(event, mark) +} + +// Parse the productions: +// +// flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? +// ***** * +func (parser *Parser) parseFlowSequenceEntryMappingValue(event *Event) error { + var token *Token + if err := parser.peekToken(&token); err != nil { + return err + } + if token.Type == VALUE_TOKEN { + parser.skipToken() + var token *Token + if err := parser.peekToken(&token); err != nil { + return err + } + if token.Type != FLOW_ENTRY_TOKEN && token.Type != FLOW_SEQUENCE_END_TOKEN { + parser.states = append(parser.states, PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_END_STATE) + return parser.parseNode(event, false, false) + } + } + parser.state = PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_END_STATE + return parser.processEmptyScalar(event, token.StartMark) +} + +// Parse the productions: +// +// flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? +// * +func (parser *Parser) parseFlowSequenceEntryMappingEnd(event *Event) error { + var token *Token + if err := parser.peekToken(&token); err != nil { + return err + } + parser.state = PARSE_FLOW_SEQUENCE_ENTRY_STATE + *event = Event{ + Type: MAPPING_END_EVENT, + StartMark: token.StartMark, + EndMark: token.StartMark, // [Go] Shouldn't this be end_mark? + } + return nil +} + +// Parse the productions: +// +// flow_mapping ::= FLOW-MAPPING-START +// ****************** +// (flow_mapping_entry FLOW-ENTRY)* +// * ********** +// flow_mapping_entry? +// ****************** +// FLOW-MAPPING-END +// **************** +// flow_mapping_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? +// * *** * +func (parser *Parser) parseFlowMappingKey(event *Event, first bool) error { + if first { + var token *Token + if err := parser.peekToken(&token); err != nil { + return err + } + parser.marks = append(parser.marks, token.StartMark) + parser.skipToken() + } + + var token *Token + if err := parser.peekToken(&token); err != nil { + return err + } + + if token.Type != FLOW_MAPPING_END_TOKEN { + if !first { + if token.Type == FLOW_ENTRY_TOKEN { + parser.skipToken() + if err := parser.peekToken(&token); err != nil { + return err + } + } else { + context_mark := parser.marks[len(parser.marks)-1] + parser.marks = parser.marks[:len(parser.marks)-1] + return formatParserErrorContext( + "while parsing a flow mapping", context_mark, + "did not find expected ',' or '}'", token.StartMark) + } + } + + if token.Type == KEY_TOKEN { + parser.skipToken() + if err := parser.peekToken(&token); err != nil { + return err + } + if token.Type != VALUE_TOKEN && + token.Type != FLOW_ENTRY_TOKEN && + token.Type != FLOW_MAPPING_END_TOKEN { + parser.states = append(parser.states, PARSE_FLOW_MAPPING_VALUE_STATE) + return parser.parseNode(event, false, false) + } else { + parser.state = PARSE_FLOW_MAPPING_VALUE_STATE + return parser.processEmptyScalar(event, token.StartMark) + } + } else if token.Type != FLOW_MAPPING_END_TOKEN { + parser.states = append(parser.states, PARSE_FLOW_MAPPING_EMPTY_VALUE_STATE) + return parser.parseNode(event, false, false) + } + } + + parser.state = parser.states[len(parser.states)-1] + parser.states = parser.states[:len(parser.states)-1] + parser.marks = parser.marks[:len(parser.marks)-1] + *event = Event{ + Type: MAPPING_END_EVENT, + StartMark: token.StartMark, + EndMark: token.EndMark, + } + parser.setEventComments(event) + parser.skipToken() + return nil +} + +// Parse the productions: +// +// flow_mapping_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? +// * ***** * +func (parser *Parser) parseFlowMappingValue(event *Event, empty bool) error { + var token *Token + if err := parser.peekToken(&token); err != nil { + return err + } + if empty { + parser.state = PARSE_FLOW_MAPPING_KEY_STATE + return parser.processEmptyScalar(event, token.StartMark) + } + if token.Type == VALUE_TOKEN { + parser.skipToken() + if err := parser.peekToken(&token); err != nil { + return err + } + if token.Type != FLOW_ENTRY_TOKEN && token.Type != FLOW_MAPPING_END_TOKEN { + parser.states = append(parser.states, PARSE_FLOW_MAPPING_KEY_STATE) + return parser.parseNode(event, false, false) + } + } + parser.state = PARSE_FLOW_MAPPING_KEY_STATE + return parser.processEmptyScalar(event, token.StartMark) +} + +// Peek the next token in the token queue. +func (parser *Parser) peekToken(out **Token) error { + if !parser.token_available { + if err := parser.fetchMoreTokens(); err != nil { + return err + } + } + + token := &parser.tokens[parser.tokens_head] + parser.UnfoldComments(token) + *out = token + return nil +} + +// UnfoldComments walks through the comments queue and joins all +// comments behind the position of the provided token into the respective +// top-level comment slices in the parser. +func (parser *Parser) UnfoldComments(token *Token) { + for parser.comments_head < len(parser.comments) && token.StartMark.Index >= parser.comments[parser.comments_head].TokenMark.Index { + comment := &parser.comments[parser.comments_head] + if len(comment.Head) > 0 { + if token.Type == BLOCK_END_TOKEN { + // No heads on ends, so keep comment.Head for a follow up token. + break + } + if len(parser.HeadComment) > 0 { + parser.HeadComment = append(parser.HeadComment, '\n') + } + parser.HeadComment = append(parser.HeadComment, comment.Head...) + } + if len(comment.Foot) > 0 { + if len(parser.FootComment) > 0 { + parser.FootComment = append(parser.FootComment, '\n') + } + parser.FootComment = append(parser.FootComment, comment.Foot...) + } + if len(comment.Line) > 0 { + if len(parser.LineComment) > 0 { + parser.LineComment = append(parser.LineComment, '\n') + } + parser.LineComment = append(parser.LineComment, comment.Line...) + } + *comment = Comment{} + parser.comments_head++ + } +} + +// Remove the next token from the queue (must be called after peek_token). +func (parser *Parser) skipToken() { + parser.token_available = false + parser.tokens_parsed++ + parser.stream_end_produced = parser.tokens[parser.tokens_head].Type == STREAM_END_TOKEN + parser.tokens_head++ +} + +// formatParserError creates a LoadError with the given problem message +// and mark position. +func formatParserError(problem string, problemMark Mark) *LoadError { + return &LoadError{ + Stage: ParserStage, + Mark: problemMark, + Message: problem, + } +} + +// formatParserErrorContext creates a LoadError with both context and +// problem information, each with their own mark positions. +func formatParserErrorContext(context string, contextMark Mark, problem string, problemMark Mark) *LoadError { + return &LoadError{ + Stage: ParserStage, + ContextMark: contextMark, + ContextMsg: context, + Mark: problemMark, + Message: problem, + } +} + +// setEventComments transfers accumulated comments from the parser to the +// event and clears the parser's comment state. +func (parser *Parser) setEventComments(event *Event) { + event.HeadComment = parser.HeadComment + event.LineComment = parser.LineComment + event.FootComment = parser.FootComment + parser.HeadComment = nil + parser.LineComment = nil + parser.FootComment = nil + parser.tail_comment = nil + parser.stem_comment = nil +} + +// Generate an empty scalar event. +func (parser *Parser) processEmptyScalar(event *Event, mark Mark) error { + *event = Event{ + Type: SCALAR_EVENT, + StartMark: mark, + EndMark: mark, + Value: nil, // Empty + Implicit: true, + Style: Style(PLAIN_SCALAR_STYLE), + } + return nil +} + +// ParserGetEvents parses the YAML input and returns the generated event stream. +func ParserGetEvents(in []byte) (string, error) { + p := NewComposer(in, nil) + defer p.Destroy() + var events strings.Builder + var event Event + for { + if err := p.Parser.Parse(&event); err != nil { + return "", err + } + formatted := formatEvent(&event) + events.WriteString(formatted) + if event.Type == STREAM_END_EVENT { + event.Delete() + break + } + event.Delete() + events.WriteByte('\n') + } + return events.String(), nil +} + +// formatEvent formats an event as a human-readable string for debugging +// and testing purposes. +func formatEvent(e *Event) string { + var b strings.Builder + switch e.Type { + case STREAM_START_EVENT: + b.WriteString("+STR") + case STREAM_END_EVENT: + b.WriteString("-STR") + case DOCUMENT_START_EVENT: + b.WriteString("+DOC") + if !e.Implicit { + b.WriteString(" ---") + } + case DOCUMENT_END_EVENT: + b.WriteString("-DOC") + if !e.Implicit { + b.WriteString(" ...") + } + case ALIAS_EVENT: + b.WriteString("=ALI *") + b.Write(e.Anchor) + case SCALAR_EVENT: + b.WriteString("=VAL") + if len(e.Anchor) > 0 { + b.WriteString(" &") + b.Write(e.Anchor) + } + if len(e.Tag) > 0 { + b.WriteString(" <") + b.Write(e.Tag) + b.WriteString(">") + } + switch e.ScalarStyle() { + case PLAIN_SCALAR_STYLE: + b.WriteString(" :") + case LITERAL_SCALAR_STYLE: + b.WriteString(" |") + case FOLDED_SCALAR_STYLE: + b.WriteString(" >") + case SINGLE_QUOTED_SCALAR_STYLE: + b.WriteString(" '") + case DOUBLE_QUOTED_SCALAR_STYLE: + b.WriteString(` "`) + } + // Escape special characters for consistent event output. + val := strings.NewReplacer( + `\`, `\\`, + "\n", `\n`, + "\t", `\t`, + ).Replace(string(e.Value)) + b.WriteString(val) + + case SEQUENCE_START_EVENT: + b.WriteString("+SEQ") + if len(e.Anchor) > 0 { + b.WriteString(" &") + b.Write(e.Anchor) + } + if len(e.Tag) > 0 { + b.WriteString(" <") + b.Write(e.Tag) + b.WriteString(">") + } + if e.SequenceStyle() == FLOW_SEQUENCE_STYLE { + b.WriteString(" []") + } + case SEQUENCE_END_EVENT: + b.WriteString("-SEQ") + case MAPPING_START_EVENT: + b.WriteString("+MAP") + if len(e.Anchor) > 0 { + b.WriteString(" &") + b.Write(e.Anchor) + } + if len(e.Tag) > 0 { + b.WriteString(" <") + b.Write(e.Tag) + b.WriteString(">") + } + if e.MappingStyle() == FLOW_MAPPING_STYLE { + b.WriteString(" {}") + } + case MAPPING_END_EVENT: + b.WriteString("-MAP") + } + return b.String() +} diff --git a/vendor/go.yaml.in/yaml/v4/internal/libyaml/reader.go b/vendor/go.yaml.in/yaml/v4/internal/libyaml/reader.go new file mode 100644 index 000000000..b2c1a53bd --- /dev/null +++ b/vendor/go.yaml.in/yaml/v4/internal/libyaml/reader.go @@ -0,0 +1,446 @@ +// Copyright 2006-2010 Kirill Simonov +// Copyright 2011-2019 Canonical Ltd +// Copyright 2025 The go-yaml Project Contributors +// SPDX-License-Identifier: Apache-2.0 AND MIT + +// Input reader with encoding detection and buffering. +// Handles BOM detection, UTF-8/UTF-16 conversion, and provides buffered input +// for the scanner. + +package libyaml + +import ( + "errors" + "fmt" + "io" +) + +// 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 (parser *Parser) updateBuffer(length int) error { + 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. + // + //nolint:staticcheck // there is no problem with this empty branch as it's documentation. + 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 nil + } + + // Determine the input encoding if it is not known yet. + if parser.encoding == ANY_ENCODING { + if err := parser.determineEncoding(); err != nil { + return err + } + } + + // 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 err := parser.updateRawBuffer(); err != nil { + parser.buffer = parser.buffer[:buffer_len] + return err + } + } + 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 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 formatReaderError( + fmt.Sprintf("invalid leading UTF-8 octet (value: %d)", octet), + Mark{Index: parser.offset}) + } + + // Check if the raw buffer contains an incomplete character. + if width > raw_unread { + if parser.eof { + return formatReaderError( + "incomplete UTF-8 octet sequence", + Mark{Index: parser.offset}) + } + 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 formatReaderError( + fmt.Sprintf("invalid trailing UTF-8 octet (value: %d)", octet), + Mark{Index: parser.offset + k}) + } + + // 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 formatReaderError( + "invalid length of a UTF-8 sequence", + Mark{Index: parser.offset}) + } + + // Check the range of the value. + if value >= 0xD800 && value <= 0xDFFF || value > 0x10FFFF { + return formatReaderError( + fmt.Sprintf("invalid Unicode character (value: %d)", value), + Mark{Index: parser.offset}) + } + + case UTF16LE_ENCODING, UTF16BE_ENCODING: + var low, high int + if parser.encoding == 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 formatReaderError( + "incomplete UTF-16 character", + Mark{Index: parser.offset}) + } + 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 formatReaderError( + fmt.Sprintf("unexpected low surrogate area (value: %d)", value), + Mark{Index: parser.offset}) + } + + // 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 formatReaderError( + "incomplete UTF-16 surrogate pair", + Mark{Index: parser.offset}) + } + 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 formatReaderError( + fmt.Sprintf("expected low surrogate area (value: %d)", value2), + Mark{Index: parser.offset + 2}) + } + + // Generate the value of the surrogate pair. + value = 0x10000 + ((value & 0x3FF) << 10) + (value2 & 0x3FF) + } else { + width = 2 + } + + default: + panic("impossible") + } + + // YAML 1.2 compatible character sets + // Check if the character is in the allowed range: + // For JSON compatibility in quoted scalars, we must allow all + // non-C0 characters. This includes ASCII DEL (0x7F) and the + // C1 control block [#x80-#x9F]. + // ref: https://yaml.org/spec/1.2.2/#51-character-set + switch { + // 8 bit set + // Tab (\t) + case value == 0x09: + // Line feed (LF \n) + case value == 0x0A: + // Carriage Return (CR \r) + case value == 0x0D: + // 16 bit set + // Printable ASCII + case value >= 0x20 && value <= 0x7E: + // DEL, C1 control + // incompatible with YAML versions <= 1.1 + case value >= 0x7F && value <= 0x9F: + // and Basic Multilingual Plane (BMP), + case value >= 0xA0 && value <= 0xD7FF: + // Additional Unicode Areas + case value >= 0xE000 && value <= 0xFFFD: + // 32 bit set + case value >= 0x10000 && value <= 0x10FFFF: + default: + return formatReaderError( + fmt.Sprintf("control characters are not allowed (value: %d)", value), + Mark{Index: parser.offset}) + } + + // 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 nil +} + +// Byte order marks for UTF-8, UTF-16LE, and UTF-16BE encodings. +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 (parser *Parser) determineEncoding() error { + // Ensure that we had enough bytes in the raw buffer. + for !parser.eof && len(parser.raw_buffer)-parser.raw_buffer_pos < 3 { + if err := parser.updateRawBuffer(); err != nil { + return err + } + } + + // 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 = 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 = 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 = UTF8_ENCODING + parser.raw_buffer_pos += 3 + parser.offset += 3 + } else { + parser.encoding = UTF8_ENCODING + } + return nil +} + +// Update the raw buffer. +func (parser *Parser) updateRawBuffer() error { + 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 nil + } + + // Return on EOF. + if parser.eof { + return nil + } + + // 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 &LoadError{ + Stage: ReaderStage, + Message: fmt.Sprintf("input error: %v", err), + Mark: Mark{Index: parser.offset}, + err: err, + } + } + return nil +} + +// formatReaderError creates a LoadError for reader-stage errors. +func formatReaderError(message string, mark Mark) *LoadError { + return &LoadError{ + Stage: ReaderStage, + Message: message, + Mark: mark, + err: errors.New(message), + } +} diff --git a/vendor/go.yaml.in/yaml/v4/internal/libyaml/representer.go b/vendor/go.yaml.in/yaml/v4/internal/libyaml/representer.go new file mode 100644 index 000000000..c9f1b21f7 --- /dev/null +++ b/vendor/go.yaml.in/yaml/v4/internal/libyaml/representer.go @@ -0,0 +1,582 @@ +// Copyright 2011-2019 Canonical Ltd +// Copyright 2025 The go-yaml Project Contributors +// SPDX-License-Identifier: Apache-2.0 + +// Representer stage: Converts Go values to YAML nodes. +// Handles representing from Go types to the intermediate node representation. + +package libyaml + +import ( + "encoding" + "reflect" + "regexp" + "sort" + "strconv" + "strings" + "time" + "unicode" + "unicode/utf8" +) + +// keyList is a sortable slice of reflect.Values used for sorting map keys +// in a natural order (numeric, then lexicographic). +type keyList []reflect.Value + +// Representer converts Go values to YAML node trees with configurable +// formatting options. +type Representer struct { + flow bool + Indent int + lineWidth int + explicitStart bool + explicitEnd bool + flowSimpleCollections bool + quotePreference QuoteStyle +} + +// NewRepresenter creates a new YAML representer with the given options. +func NewRepresenter(opts *Options) *Representer { + return &Representer{ + Indent: opts.Indent, + lineWidth: opts.LineWidth, + explicitStart: opts.ExplicitStart, + explicitEnd: opts.ExplicitEnd, + flowSimpleCollections: opts.FlowSimpleCollections, + quotePreference: opts.QuotePreference, + } +} + +// Represent converts a Go value to a YAML node tree. +// This is the primary method for the Representer stage in the dump pipeline. +func (r *Representer) Represent(tag string, in reflect.Value) *Node { + var node *Node + if in.IsValid() { + node, _ = in.Interface().(*Node) + } + if node != nil && node.Kind == DocumentNode { + // Already a document node, return as-is + return node + } else { + // Wrap the represented value in a document node + contentNode := r.represent(tag, in) + return &Node{ + Kind: DocumentNode, + Content: []*Node{contentNode}, + } + } +} + +// 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_]*)?$`) + +// represent is the core conversion method that handles the actual +// type-specific conversion from Go values to YAML nodes. +func (r *Representer) represent(tag string, in reflect.Value) *Node { + tag = shortTag(tag) + if !in.IsValid() || in.Kind() == reflect.Pointer && in.IsNil() { + return r.nilv() + } + iface := in.Interface() + switch value := iface.(type) { + case *Node: + return r.nodev(in) + case Node: + if !in.CanAddr() { + n := reflect.New(in.Type()).Elem() + n.Set(in) + in = n + } + return r.nodev(in.Addr()) + case time.Time: + return r.timev(tag, in) + case *time.Time: + return r.timev(tag, in.Elem()) + case time.Duration: + return r.stringv(tag, reflect.ValueOf(value.String())) + case Marshaler: + v, err := value.MarshalYAML() + if err != nil { + failDump(RepresenterStage, err) + } + if v == nil { + return r.nilv() + } + return r.represent(tag, reflect.ValueOf(v)) + case encoding.TextMarshaler: + text, err := value.MarshalText() + if err != nil { + failDump(RepresenterStage, err) + } + in = reflect.ValueOf(string(text)) + case nil: + return r.nilv() + } + switch in.Kind() { + case reflect.Interface: + return r.represent(tag, in.Elem()) + case reflect.Map: + return r.mapv(tag, in) + case reflect.Pointer: + return r.represent(tag, in.Elem()) + case reflect.Struct: + return r.structv(tag, in) + case reflect.Slice, reflect.Array: + return r.slicev(tag, in) + case reflect.String: + return r.stringv(tag, in) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return r.intv(tag, in) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return r.uintv(tag, in) + case reflect.Float32, reflect.Float64: + return r.floatv(tag, in) + case reflect.Bool: + return r.boolv(tag, in) + default: + failDumpf(RepresenterStage, "cannot represent type: %s", in.Type().String()) + return nil // unreachable; failDumpf always panics + } +} + +// mapv converts a Go map to a YAML mapping node with sorted keys. +func (r *Representer) mapv(tag string, in reflect.Value) *Node { + if tag == "" { + tag = mapTag + } + var style Style + if r.flow { + r.flow = false + style = FlowStyle + } + + keys := keyList(in.MapKeys()) + sort.Sort(keys) + content := make([]*Node, 0, len(keys)*2) + for _, k := range keys { + content = append(content, r.represent("", k)) + content = append(content, r.represent("", in.MapIndex(k))) + } + + return &Node{ + Kind: MappingNode, + Tag: tag, + Content: content, + Style: style, + } +} + +// structv converts a Go struct to a YAML mapping node, handling field tags, +// omitempty, inline fields, and inline maps. +func (r *Representer) structv(tag string, in reflect.Value) *Node { + sinfo, err := getStructInfo(in.Type()) + if err != nil { + failDump(RepresenterStage, err) + } + + if tag == "" { + tag = mapTag + } + var style Style + if r.flow { + r.flow = false + style = FlowStyle + } + + content := make([]*Node, 0) + for _, info := range sinfo.FieldsList { + var value reflect.Value + if info.Inline == nil { + value = in.Field(info.Num) + } else { + value = r.fieldByIndex(in, info.Inline) + if !value.IsValid() { + continue + } + } + if info.OmitEmpty && isZero(value) { + continue + } + content = append(content, r.represent("", reflect.ValueOf(info.Key))) + r.flow = info.Flow + content = append(content, r.represent("", value)) + } + if sinfo.InlineMap >= 0 { + m := in.Field(sinfo.InlineMap) + if m.Len() > 0 { + r.flow = false + keys := keyList(m.MapKeys()) + sort.Sort(keys) + for _, k := range keys { + if _, found := sinfo.FieldsMap[k.String()]; found { + failDumpf(RepresenterStage, "cannot have key %q in inlined map: conflicts with struct field", k.String()) + } + content = append(content, r.represent("", k)) + r.flow = false + content = append(content, r.represent("", m.MapIndex(k))) + } + } + } + + return &Node{ + Kind: MappingNode, + Tag: tag, + Content: content, + Style: style, + } +} + +// slicev converts a Go slice or array to a YAML sequence node. +func (r *Representer) slicev(tag string, in reflect.Value) *Node { + if tag == "" { + tag = seqTag + } + var style Style + if r.flow { + r.flow = false + style = FlowStyle + } + + n := in.Len() + content := make([]*Node, n) + for i := 0; i < n; i++ { + content[i] = r.represent("", in.Index(i)) + } + + return &Node{ + Kind: SequenceNode, + Tag: tag, + Content: content, + Style: style, + } +} + +// stringv converts a Go string to a YAML scalar node, handling quoting, +// binary data (base64 encoding), and special string values. +func (r *Representer) stringv(tag string, in reflect.Value) *Node { + var style Style + s := in.String() + needsQuoting := false + + switch { + case !utf8.ValidString(s): + if tag == binaryTag { + failDumpf(RepresenterStage, "explicitly tagged !!binary data must be base64-encoded") + } + if tag != "" { + failDumpf(RepresenterStage, "cannot represent invalid UTF-8 data as %s", shortTag(tag)) + } + // It can't be represented directly as YAML so use a binary tag + // and represent it as base64. + tag = binaryTag + s = encodeBase64(s) + case tag == "": + tag = strTag + // Check if this string needs quoting for compatibility + // even though it would resolve as !!str + needsQuoting = isBase60Float(s) || isOldBool(s) || looksLikeMerge(s) + } + + // Set the style based on content + switch { + case strings.Contains(s, "\n"): + if r.flow || !shouldUseLiteralStyle(s) { + style = DoubleQuotedStyle + } else { + style = LiteralStyle + } + case needsQuoting: + // Force quoting for YAML 1.1 compatibility values + style = SingleQuotedStyle + default: + // Plain style by default - Desolver will add quotes if type mismatch + style = 0 + } + + return &Node{ + Kind: ScalarNode, + Tag: tag, + Value: s, + Style: style, + } +} + +// boolv converts a Go bool to a YAML scalar node. +func (r *Representer) boolv(tag string, in reflect.Value) *Node { + var s string + if in.Bool() { + s = "true" + } else { + s = "false" + } + if tag == "" { + tag = boolTag + } + return &Node{ + Kind: ScalarNode, + Tag: tag, + Value: s, + } +} + +// intv converts a Go signed integer to a YAML scalar node. +func (r *Representer) intv(tag string, in reflect.Value) *Node { + s := strconv.FormatInt(in.Int(), 10) + if tag == "" { + tag = intTag + } + return &Node{ + Kind: ScalarNode, + Tag: tag, + Value: s, + } +} + +// uintv converts a Go unsigned integer to a YAML scalar node. +func (r *Representer) uintv(tag string, in reflect.Value) *Node { + s := strconv.FormatUint(in.Uint(), 10) + if tag == "" { + tag = intTag + } + return &Node{ + Kind: ScalarNode, + Tag: tag, + Value: s, + } +} + +// timev converts a Go [time.Time] to a YAML scalar node in RFC3339Nano format. +func (r *Representer) timev(tag string, in reflect.Value) *Node { + t := in.Interface().(time.Time) + s := t.Format(time.RFC3339Nano) + if tag == "" { + tag = timestampTag + } + return &Node{ + Kind: ScalarNode, + Tag: tag, + Value: s, + } +} + +// floatv converts a Go float to a YAML scalar node, handling special values +// like infinity and NaN. +func (r *Representer) floatv(tag string, in reflect.Value) *Node { + // 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" + } + if tag == "" { + tag = floatTag + } + return &Node{ + Kind: ScalarNode, + Tag: tag, + Value: s, + } +} + +// nilv creates a YAML null node. +func (r *Representer) nilv() *Node { + return &Node{ + Kind: ScalarNode, + Tag: nullTag, + Value: "null", + } +} + +// nodev returns a node value as-is without conversion. +func (r *Representer) nodev(in reflect.Value) *Node { + // Return the node as-is - no conversion needed + return in.Interface().(*Node) +} + +// Len returns the number of keys in the list. +func (l keyList) Len() int { return len(l) } + +// Swap exchanges the positions of two keys in the list. +func (l keyList) Swap(i, j int) { l[i], l[j] = l[j], l[i] } + +// Less implements a natural sort order for map keys: numeric values sort +// numerically, strings sort with natural number ordering, and mixed types +// sort by kind. +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.Pointer) && !a.IsNil() { + a = a.Elem() + ak = a.Kind() + } + for (bk == reflect.Interface || bk == reflect.Pointer) && !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") +} + +// fieldByIndex navigates through struct fields using the given index path, +// dereferencing pointers as needed. +func (r *Representer) fieldByIndex(v reflect.Value, index []int) (field reflect.Value) { + for _, num := range index { + for { + if v.Kind() == reflect.Pointer { + if v.IsNil() { + return reflect.Value{} + } + v = v.Elem() + continue + } + break + } + v = v.Field(num) + } + return v +} + +// 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 represented 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) +} + +// 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 represented 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 + } +} + +// looksLikeMerge returns true if the given string is the merge indicator "<<". +// +// When encoding a scalar with this exact value, it must be quoted to prevent it +// from being interpreted as a merge indicator during decoding. +func looksLikeMerge(s string) (result bool) { + return s == "<<" +} diff --git a/vendor/go.yaml.in/yaml/v4/internal/libyaml/resolver.go b/vendor/go.yaml.in/yaml/v4/internal/libyaml/resolver.go new file mode 100644 index 000000000..5256b77fc --- /dev/null +++ b/vendor/go.yaml.in/yaml/v4/internal/libyaml/resolver.go @@ -0,0 +1,332 @@ +// Copyright 2011-2019 Canonical Ltd +// Copyright 2025 The go-yaml Project Contributors +// SPDX-License-Identifier: Apache-2.0 + +// Tag resolution for YAML scalars. +// Determines implicit types (int, float, bool, null, timestamp) from untagged +// scalar values. + +package libyaml + +import ( + "encoding/base64" + "fmt" + "math" + "regexp" + "strconv" + "strings" + "time" +) + +// resolveMapItem holds a resolved value and its YAML tag for exact string +// matches in the resolution table. +type resolveMapItem struct { + value any + tag string +} + +// Resolver handles tag resolution for YAML nodes. +type Resolver struct { + opts *Options +} + +// NewResolver creates a new Resolver with the given options. +func NewResolver(opts *Options) *Resolver { + return &Resolver{opts: opts} +} + +// Resolve walks the node tree and resolves tags for untagged nodes. +// This is called after composition to: +// - Default quoted scalars to !!str +// - Default sequences to !!seq +// - Default mappings to !!map +// - Resolve plain scalars to implicit types (int, float, bool, null, timestamp) +func (r *Resolver) Resolve(n *Node) { + if n == nil { + return + } + + switch n.Kind { + case ScalarNode: + if n.Tag == "" { + if n.Style&(SingleQuotedStyle|DoubleQuotedStyle|LiteralStyle|FoldedStyle) != 0 { + // Quoted scalars default to !!str without value resolution + n.Tag = strTag + } else { + // Plain scalars: resolve type from value + n.Tag, _ = resolve("", n.Value) + } + } + + case SequenceNode: + if n.Tag == "" { + n.Tag = seqTag + } + for _, child := range n.Content { + r.Resolve(child) + } + + case MappingNode: + if n.Tag == "" { + n.Tag = mapTag + } + for _, child := range n.Content { + r.Resolve(child) + } + + case DocumentNode: + for _, child := range n.Content { + r.Resolve(child) + } + + case AliasNode: + // Alias nodes point to already-resolved nodes + } +} + +// resolve determines the YAML tag and Go value for a scalar string. +// It takes a tag hint and the scalar string value, and returns the resolved +// tag and the corresponding Go value (int, float, bool, [time.Time], etc.). +// If the tag is already specified and non-resolvable, it returns the input +// unchanged. +func resolve(tag string, in string) (rtag string, out any) { + 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 + } + } + } + Fail(formatResolverError( + fmt.Sprintf("cannot construct %s `%s` as a %s", shortTag(rtag), in, shortTag(tag)), + Mark{}, + )) + }() + + // 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.ReplaceAll(in, "_", "") + 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 + } + } + default: + panic("internal error: missing handler for resolver table: " + string(rune(hint)) + " (with " + in + ")") + } + } + return strTag, in +} + +// resolveTable provides a fast lookup table for initial character-based +// classification during tag resolution. +// resolveMap maps specific scalar strings to their resolved values and tags. +var ( + resolveTable = make([]byte, 256) + resolveMap = make(map[string]resolveMapItem) +) + +// negativeZero represents -0.0 for YAML encoding/decoding +// this is needed because Go constants cannot express -0.0 +// https://staticcheck.dev/docs/checks/#SA4026 +var negativeZero = math.Copysign(0.0, -1.0) + +// yamlStyleFloat matches floating-point numbers in YAML style (including +// scientific notation and numbers starting with a dot). +var yamlStyleFloat = regexp.MustCompile(`^[-+]?(?:\.[0-9]+|[0-9]+(?:\.[0-9]*)?)(?:[eE][-+]?[0-9]+)?$`) + +// allowedTimestampFormats lists the timestamp formats supported by the +// resolver. +// 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. +} + +// init initializes the resolveTable with character class mappings for tag resolution. +func init() { + t := resolveTable + t[int('+')] = 'S' // Sign + t[int('-')] = 'S' + for _, c := range "0123456789" { + t[int(c)] = 'D' // Digit + } + for _, c := range "yYnNtTfFoO~<" { // < for merge key << + t[int(c)] = 'M' // In map + } + t[int('.')] = '.' // Float (potentially in map) + + resolveMapList := []struct { + v any + 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"}}, + {negativeZero, floatTag, []string{"-0", "-0.0"}}, + {"<<", mergeTag, []string{"<<"}}, + } + + m := resolveMap + for _, item := range resolveMapList { + for _, s := range item.l { + m[s] = resolveMapItem{item.v, item.tag} + } + } +} + +// resolvableTag checks if a tag can be automatically resolved from a scalar +// value. +func resolvableTag(tag string) bool { + switch tag { + case "", strTag, boolTag, intTag, floatTag, nullTag, timestampTag: + return true + } + return false +} + +// 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]) +} + +// 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 +} + +// formatResolverError creates a LoadError for resolver-stage errors. +func formatResolverError(message string, mark Mark) *LoadError { + return &LoadError{ + Stage: ResolverStage, + Mark: mark, + Message: message, + } +} + +// formatResolverErrorContext creates a LoadError with both context and +// problem information for resolver-stage errors. +func formatResolverErrorContext(context string, contextMark Mark, message string, mark Mark) *LoadError { + return &LoadError{ + Stage: ResolverStage, + ContextMark: contextMark, + ContextMsg: context, + Mark: mark, + Message: message, + } +} diff --git a/vendor/go.yaml.in/yaml/v4/internal/libyaml/scanner.go b/vendor/go.yaml.in/yaml/v4/internal/libyaml/scanner.go new file mode 100644 index 000000000..1cbcc32f5 --- /dev/null +++ b/vendor/go.yaml.in/yaml/v4/internal/libyaml/scanner.go @@ -0,0 +1,3430 @@ +// Copyright 2006-2010 Kirill Simonov +// Copyright 2011-2019 Canonical Ltd +// Copyright 2025 The go-yaml Project Contributors +// SPDX-License-Identifier: Apache-2.0 AND MIT + +// Scanner stage: Transforms input stream into token sequence. +// The Scanner is the most complex stage, handling indentation, simple keys, +// and block collection detection. + +package libyaml + +import ( + "bytes" + "fmt" + "io" +) + +// Introduction +// ************ +// +// The following notes assume that you are familiar with the YAML specification +// (http://yaml.org/spec/1.2/spec.html). We mostly follow it, although in +// some cases we are less restrictive that it requires. +// +// The process of transforming a YAML stream into a sequence of events is +// divided on two steps: Scanning and Parsing. +// +// The Scanner transforms the input stream into a sequence of tokens, while the +// parser transform the sequence of tokens produced by the Scanner into a +// sequence of parsing events. +// +// The Scanner is rather clever and complicated. The Parser, on the contrary, +// is a straightforward implementation of a recursive-descendant parser (or, +// LL(1) parser, as it is usually called). +// +// Actually there are two issues of Scanning that might be called "clever", the +// rest is quite straightforward. The issues are "block collection start" and +// "simple keys". Both issues are explained below in details. +// +// Here the Scanning step is explained and implemented. We start with the list +// of all the tokens produced by the Scanner together with short descriptions. +// +// Now, tokens: +// +// STREAM-START(encoding) # The stream start. +// STREAM-END # The stream end. +// VERSION-DIRECTIVE(major,minor) # The '%YAML' directive. +// TAG-DIRECTIVE(handle,prefix) # The '%TAG' directive. +// DOCUMENT-START # '---' +// DOCUMENT-END # '...' +// BLOCK-SEQUENCE-START # Indentation increase denoting a block +// BLOCK-MAPPING-START # sequence or a block mapping. +// BLOCK-END # Indentation decrease. +// FLOW-SEQUENCE-START # '[' +// FLOW-SEQUENCE-END # ']' +// BLOCK-SEQUENCE-START # '{' +// BLOCK-SEQUENCE-END # '}' +// BLOCK-ENTRY # '-' +// FLOW-ENTRY # ',' +// KEY # '?' or nothing (simple keys). +// VALUE # ':' +// ALIAS(anchor) # '*anchor' +// ANCHOR(anchor) # '&anchor' +// TAG(handle,suffix) # '!handle!suffix' +// SCALAR(value,style) # A scalar. +// +// The following two tokens are "virtual" tokens denoting the beginning and the +// end of the stream: +// +// STREAM-START(encoding) +// STREAM-END +// +// We pass the information about the input stream encoding with the +// STREAM-START token. +// +// The next two tokens are responsible for tags: +// +// VERSION-DIRECTIVE(major,minor) +// TAG-DIRECTIVE(handle,prefix) +// +// Example: +// +// %YAML 1.1 +// %TAG ! !foo +// %TAG !yaml! tag:yaml.org,2002: +// --- +// +// The corresponding sequence of tokens: +// +// STREAM-START(utf-8) +// VERSION-DIRECTIVE(1,1) +// TAG-DIRECTIVE("!","!foo") +// TAG-DIRECTIVE("!yaml","tag:yaml.org,2002:") +// DOCUMENT-START +// STREAM-END +// +// Note that the VERSION-DIRECTIVE and TAG-DIRECTIVE tokens occupy a whole +// line. +// +// The document start and end indicators are represented by: +// +// DOCUMENT-START +// DOCUMENT-END +// +// Note that if a YAML stream contains an implicit document (without '---' +// and '...' indicators), no DOCUMENT-START and DOCUMENT-END tokens will be +// produced. +// +// In the following examples, we present whole documents together with the +// produced tokens. +// +// 1. An implicit document: +// +// 'a scalar' +// +// Tokens: +// +// STREAM-START(utf-8) +// SCALAR("a scalar",single-quoted) +// STREAM-END +// +// 2. An explicit document: +// +// --- +// 'a scalar' +// ... +// +// Tokens: +// +// STREAM-START(utf-8) +// DOCUMENT-START +// SCALAR("a scalar",single-quoted) +// DOCUMENT-END +// STREAM-END +// +// 3. Several documents in a stream: +// +// 'a scalar' +// --- +// 'another scalar' +// --- +// 'yet another scalar' +// +// Tokens: +// +// STREAM-START(utf-8) +// SCALAR("a scalar",single-quoted) +// DOCUMENT-START +// SCALAR("another scalar",single-quoted) +// DOCUMENT-START +// SCALAR("yet another scalar",single-quoted) +// STREAM-END +// +// We have already introduced the SCALAR token above. The following tokens are +// used to describe aliases, anchors, tag, and scalars: +// +// ALIAS(anchor) +// ANCHOR(anchor) +// TAG(handle,suffix) +// SCALAR(value,style) +// +// The following series of examples illustrate the usage of these tokens: +// +// 1. A recursive sequence: +// +// &A [ *A ] +// +// Tokens: +// +// STREAM-START(utf-8) +// ANCHOR("A") +// FLOW-SEQUENCE-START +// ALIAS("A") +// FLOW-SEQUENCE-END +// STREAM-END +// +// 2. A tagged scalar: +// +// !!float "3.14" # A good approximation. +// +// Tokens: +// +// STREAM-START(utf-8) +// TAG("!!","float") +// SCALAR("3.14",double-quoted) +// STREAM-END +// +// 3. Various scalar styles: +// +// --- # Implicit empty plain scalars do not produce tokens. +// --- a plain scalar +// --- 'a single-quoted scalar' +// --- "a double-quoted scalar" +// --- |- +// a literal scalar +// --- >- +// a folded +// scalar +// +// Tokens: +// +// STREAM-START(utf-8) +// DOCUMENT-START +// DOCUMENT-START +// SCALAR("a plain scalar",plain) +// DOCUMENT-START +// SCALAR("a single-quoted scalar",single-quoted) +// DOCUMENT-START +// SCALAR("a double-quoted scalar",double-quoted) +// DOCUMENT-START +// SCALAR("a literal scalar",literal) +// DOCUMENT-START +// SCALAR("a folded scalar",folded) +// STREAM-END +// +// Now it's time to review collection-related tokens. We will start with +// flow collections: +// +// FLOW-SEQUENCE-START +// FLOW-SEQUENCE-END +// FLOW-MAPPING-START +// FLOW-MAPPING-END +// FLOW-ENTRY +// KEY +// VALUE +// +// The tokens FLOW-SEQUENCE-START, FLOW-SEQUENCE-END, FLOW-MAPPING-START, and +// FLOW-MAPPING-END represent the indicators '[', ']', '{', and '}' +// correspondingly. FLOW-ENTRY represent the ',' indicator. Finally the +// indicators '?' and ':', which are used for denoting mapping keys and values, +// are represented by the KEY and VALUE tokens. +// +// The following examples show flow collections: +// +// 1. A flow sequence: +// +// [item 1, item 2, item 3] +// +// Tokens: +// +// STREAM-START(utf-8) +// FLOW-SEQUENCE-START +// SCALAR("item 1",plain) +// FLOW-ENTRY +// SCALAR("item 2",plain) +// FLOW-ENTRY +// SCALAR("item 3",plain) +// FLOW-SEQUENCE-END +// STREAM-END +// +// 2. A flow mapping: +// +// { +// a simple key: a value, # Note that the KEY token is produced. +// ? a complex key: another value, +// } +// +// Tokens: +// +// STREAM-START(utf-8) +// FLOW-MAPPING-START +// KEY +// SCALAR("a simple key",plain) +// VALUE +// SCALAR("a value",plain) +// FLOW-ENTRY +// KEY +// SCALAR("a complex key",plain) +// VALUE +// SCALAR("another value",plain) +// FLOW-ENTRY +// FLOW-MAPPING-END +// STREAM-END +// +// A simple key is a key which is not denoted by the '?' indicator. Note that +// the Scanner still produce the KEY token whenever it encounters a simple key. +// +// For scanning block collections, the following tokens are used (note that we +// repeat KEY and VALUE here): +// +// BLOCK-SEQUENCE-START +// BLOCK-MAPPING-START +// BLOCK-END +// BLOCK-ENTRY +// KEY +// VALUE +// +// The tokens BLOCK-SEQUENCE-START and BLOCK-MAPPING-START denote indentation +// increase that precedes a block collection (cf. the INDENT token in Python). +// The token BLOCK-END denote indentation decrease that ends a block collection +// (cf. the DEDENT token in Python). However YAML has some syntax peculiarities +// that makes detections of these tokens more complex. +// +// The tokens BLOCK-ENTRY, KEY, and VALUE are used to represent the indicators +// '-', '?', and ':' correspondingly. +// +// The following examples show how the tokens BLOCK-SEQUENCE-START, +// BLOCK-MAPPING-START, and BLOCK-END are emitted by the Scanner: +// +// 1. Block sequences: +// +// - item 1 +// - item 2 +// - +// - item 3.1 +// - item 3.2 +// - +// key 1: value 1 +// key 2: value 2 +// +// Tokens: +// +// STREAM-START(utf-8) +// BLOCK-SEQUENCE-START +// BLOCK-ENTRY +// SCALAR("item 1",plain) +// BLOCK-ENTRY +// SCALAR("item 2",plain) +// BLOCK-ENTRY +// BLOCK-SEQUENCE-START +// BLOCK-ENTRY +// SCALAR("item 3.1",plain) +// BLOCK-ENTRY +// SCALAR("item 3.2",plain) +// BLOCK-END +// BLOCK-ENTRY +// BLOCK-MAPPING-START +// KEY +// SCALAR("key 1",plain) +// VALUE +// SCALAR("value 1",plain) +// KEY +// SCALAR("key 2",plain) +// VALUE +// SCALAR("value 2",plain) +// BLOCK-END +// BLOCK-END +// STREAM-END +// +// 2. Block mappings: +// +// a simple key: a value # The KEY token is produced here. +// ? a complex key +// : another value +// a mapping: +// key 1: value 1 +// key 2: value 2 +// a sequence: +// - item 1 +// - item 2 +// +// Tokens: +// +// STREAM-START(utf-8) +// BLOCK-MAPPING-START +// KEY +// SCALAR("a simple key",plain) +// VALUE +// SCALAR("a value",plain) +// KEY +// SCALAR("a complex key",plain) +// VALUE +// SCALAR("another value",plain) +// KEY +// SCALAR("a mapping",plain) +// BLOCK-MAPPING-START +// KEY +// SCALAR("key 1",plain) +// VALUE +// SCALAR("value 1",plain) +// KEY +// SCALAR("key 2",plain) +// VALUE +// SCALAR("value 2",plain) +// BLOCK-END +// KEY +// SCALAR("a sequence",plain) +// VALUE +// BLOCK-SEQUENCE-START +// BLOCK-ENTRY +// SCALAR("item 1",plain) +// BLOCK-ENTRY +// SCALAR("item 2",plain) +// BLOCK-END +// BLOCK-END +// STREAM-END +// +// YAML does not always require to start a new block collection from a new +// line. If the current line contains only '-', '?', and ':' indicators, a new +// block collection may start at the current line. The following examples +// illustrate this case: +// +// 1. Collections in a sequence: +// +// - - item 1 +// - item 2 +// - key 1: value 1 +// key 2: value 2 +// - ? complex key +// : complex value +// +// Tokens: +// +// STREAM-START(utf-8) +// BLOCK-SEQUENCE-START +// BLOCK-ENTRY +// BLOCK-SEQUENCE-START +// BLOCK-ENTRY +// SCALAR("item 1",plain) +// BLOCK-ENTRY +// SCALAR("item 2",plain) +// BLOCK-END +// BLOCK-ENTRY +// BLOCK-MAPPING-START +// KEY +// SCALAR("key 1",plain) +// VALUE +// SCALAR("value 1",plain) +// KEY +// SCALAR("key 2",plain) +// VALUE +// SCALAR("value 2",plain) +// BLOCK-END +// BLOCK-ENTRY +// BLOCK-MAPPING-START +// KEY +// SCALAR("complex key") +// VALUE +// SCALAR("complex value") +// BLOCK-END +// BLOCK-END +// STREAM-END +// +// 2. Collections in a mapping: +// +// ? a sequence +// : - item 1 +// - item 2 +// ? a mapping +// : key 1: value 1 +// key 2: value 2 +// +// Tokens: +// +// STREAM-START(utf-8) +// BLOCK-MAPPING-START +// KEY +// SCALAR("a sequence",plain) +// VALUE +// BLOCK-SEQUENCE-START +// BLOCK-ENTRY +// SCALAR("item 1",plain) +// BLOCK-ENTRY +// SCALAR("item 2",plain) +// BLOCK-END +// KEY +// SCALAR("a mapping",plain) +// VALUE +// BLOCK-MAPPING-START +// KEY +// SCALAR("key 1",plain) +// VALUE +// SCALAR("value 1",plain) +// KEY +// SCALAR("key 2",plain) +// VALUE +// SCALAR("value 2",plain) +// BLOCK-END +// BLOCK-END +// STREAM-END +// +// YAML also permits non-indented sequences if they are included into a block +// mapping. In this case, the token BLOCK-SEQUENCE-START is not produced: +// +// key: +// - item 1 # BLOCK-SEQUENCE-START is NOT produced here. +// - item 2 +// +// Tokens: +// +// STREAM-START(utf-8) +// BLOCK-MAPPING-START +// KEY +// SCALAR("key",plain) +// VALUE +// BLOCK-ENTRY +// SCALAR("item 1",plain) +// BLOCK-ENTRY +// SCALAR("item 2",plain) +// BLOCK-END +// + +// Buffer sizes and internal constants +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 other stacks and queues. + initial_stack_size = 16 + initial_queue_size = 16 + initial_string_size = 16 +) + +// Character classification functions + +// Check if the character at the specified position is an alphabetical +// character, a digit, '_', or '-'. +func isAlpha(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 flow indicator as +// defined by spec production [23] c-flow-indicator ::= +// c-collect-entry | c-sequence-start | c-sequence-end | +// c-mapping-start | c-mapping-end +func isFlowIndicator(b []byte, i int) bool { + return b[i] == '[' || b[i] == ']' || + b[i] == '{' || b[i] == '}' || b[i] == ',' +} + +// Check if the character at the specified position is valid for anchor names +// as defined by spec production [102] ns-anchor-char ::= ns-char - +// c-flow-indicator. +// This includes all printable characters except: CR, LF, BOM, space, tab, '[', +// ']', '{', '}', ','. +// We further limit it to ascii chars only, which is a subset of the spec +// production but is usually what most people expect. +func isAnchorChar(b []byte, i int) bool { + if isColon(b, i) { + // [Go] we exclude colons from anchor/alias names. + // + // A colon is a valid anchor character according to the YAML 1.2 specification, + // but it can lead to ambiguity. + // https://github.com/yaml/go-yaml/issues/109 + // + // Also, it would have been a breaking change to support it, as go.yaml.in/yaml/v3 ignores it. + // Supporting it could lead to unexpected behavior. + return false + } + + return isPrintable(b, i) && + !isLineBreak(b, i) && + !isBlank(b, i) && + !isBOM(b, i) && + !isFlowIndicator(b, i) && + isASCII(b, i) +} + +// isColon checks whether the character at the specified position is a colon. +func isColon(b []byte, i int) bool { + return b[i] == ':' +} + +// Check if the character at the specified position is valid in a tag URI. +// +// The set of valid characters is: +// +// '0'-'9', 'A'-'Z', 'a'-'z', '_', '-', ';', '/', '?', ':', '@', '&', +// '=', '+', '$', '.', '!', '~', '*', '\'', '(', ')', '%'. +// +// If verbatim is true, flow indicators (',', '[', ']', '{', '}') are also +// allowed. +func isTagURIChar(b []byte, i int, verbatim bool) bool { + c := b[i] + // isAlpha covers: 0-9, A-Z, a-z, _, - + if isAlpha(b, i) { + return true + } + // Check special URI characters + switch c { + case ';', '/', '?', ':', '@', '&', '=', '+', '$', '.', '!', '~', '*', '\'', '(', ')', '%': + return true + case ',', '[', ']', '{', '}': + return verbatim + } + return false +} + +// Check if the character at the specified position is a digit. +func isDigit(b []byte, i int) bool { + return b[i] >= '0' && b[i] <= '9' +} + +// Get the value of a digit. +func asDigit(b []byte, i int) int { + return int(b[i]) - '0' +} + +// Check if the character at the specified position is a hex-digit. +func isHex(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 asHex(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 isASCII(b []byte, i int) bool { + return b[i] <= 0x7F +} + +// Check if the character at the start of the buffer can be printed unescaped. +func isPrintable(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 isZeroChar(b []byte, i int) bool { + return b[i] == 0x00 +} + +// Check if the beginning of the buffer is a BOM. +func isBOM(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 isSpace(b []byte, i int) bool { + return b[i] == ' ' +} + +// Check if the character at the specified position is tab. +func isTab(b []byte, i int) bool { + return b[i] == '\t' +} + +// Check if the character at the specified position is blank (space or tab). +func isBlank(b []byte, i int) bool { + // return isSpace(b, i) || isTab(b, i) + return b[i] == ' ' || b[i] == '\t' +} + +// Check if the character at the specified position is a line break. +func isLineBreak(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) +} + +// isCRLF checks if the position contains a CR LF sequence. +func isCRLF(b []byte, i int) bool { + return b[i] == '\r' && b[i+1] == '\n' +} + +// Check if the character is a line break or NUL. +func isBreakOrZero(b []byte, i int) bool { + // return isLineBreak(b, i) || isZeroChar(b, i) + return ( + // isBreak: + 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) + // isZeroChar: + b[i] == 0) +} + +// Check if the character is a line break, space, or NUL. +func isSpaceOrZero(b []byte, i int) bool { + // return isSpace(b, i) || isBreakOrZero(b, i) + return ( + // isSpace: + b[i] == ' ' || + // isBreakOrZero: + 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 isBlankOrZero(b []byte, i int) bool { + // return isBlank(b, i) || isBreakOrZero(b, i) + return ( + // isBlank: + b[i] == ' ' || b[i] == '\t' || + // isBreakOrZero: + 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) +} + +func isEndOfScalarInFlowContentChar(b []byte, i int) bool { + switch b[i] { + // ",", "[", "]", "{" and "}" + case ',', '[', ']', '{', '}': + return true + // "? " + case '?': + return isBlankOrZero(b, i+1) + // ": ", ":,", ":]" and ":}" + case ':': + return b[i+1] == ' ' || b[i+1] == ',' || + b[i+1] == ']' || b[i+1] == '}' + default: + return false + } +} + +// 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 +} + +// Scan advances the buffer pointer and reads the next token. +func (parser *Parser) Scan(token *Token) error { + // Erase the token object. + *token = Token{} // [Go] Is this necessary? + + if parser.lastError != nil { + return parser.lastError + } + + // No tokens after STREAM-END or error. + if parser.stream_end_produced { + return io.EOF + } + + // Ensure that the tokens queue contains enough tokens. + if !parser.token_available { + if err := parser.fetchMoreTokens(); err != nil { + parser.lastError = err + return err + } + } + + // Fetch the next token from the queue. + *token = parser.tokens[parser.tokens_head] + parser.tokens_head++ + parser.tokens_parsed++ + parser.token_available = false + + if token.Type == STREAM_END_TOKEN { + parser.stream_end_produced = true + } + return nil +} + +// formatScannerError creates a ScannerError with the given problem message +// and mark position. + +// max_number_length is the maximum length of a number suffix in a scalar tag. +const max_number_length = 2 + +// fetchMoreTokens ensures the token queue has at least one token for lookahead. +func (parser *Parser) fetchMoreTokens() error { + // While we need more tokens to fetch, do it. + for { + // [Go] The comment parsing logic requires a lookahead of two tokens + // so that foot comments may be parsed in time of associating them + // with the tokens that are parsed before them, and also for line + // comments to be transformed into head comments in some edge cases. + if parser.tokens_head < len(parser.tokens)-2 { + // If a potential simple key is at the head position, we need to fetch + // the next token to disambiguate it. + + var first_key int + found_potential_key := false + + if len(parser.simple_key_stack) > 0 { + // Found a simple key on the stack + first_key = parser.simple_key_stack[0].token_number + found_potential_key = true + } else if parser.simple_key_possible { + // Found a 'current' simple key (which was not pushed to the stack yet) + first_key = parser.simple_key.token_number + found_potential_key = true + } + + if !found_potential_key { + // We don't have any potential simple keys + break + } else if parser.tokens_parsed != first_key { + // We have not reached the potential simple key yet. + break + } + } + // Fetch the next token. + if err := parser.fetchNextToken(); err != nil { + return err + } + } + + parser.token_available = true + return nil +} + +// The dispatcher for token fetchers. +func (parser *Parser) fetchNextToken() (err error) { + // Ensure that the buffer is initialized. + if parser.unread < 1 { + if err := parser.updateBuffer(1); err != nil { + return err + } + } + + // Check if we just started scanning. Fetch STREAM-START then. + if !parser.stream_start_produced { + return parser.fetchStreamStart() + } + + scan_mark := parser.mark + + // Eat whitespaces and comments until we reach the next token. + if err := parser.scanToNextToken(); err != nil { + return err + } + + // [Go] While unrolling indents, transform the head comments of prior + // indentation levels observed after scan_start into foot comments at + // the respective indexes. + + // Check the indentation level against the current column. + if err := parser.unrollIndent(parser.mark.Column, scan_mark); err != nil { + return err + } + + // Ensure that the buffer contains at least 4 characters. 4 is the length + // of the longest indicators ('--- ' and '... '). + if parser.unread < 4 { + if err := parser.updateBuffer(4); err != nil { + return err + } + } + + // Is it the end of the stream? + if isZeroChar(parser.buffer, parser.buffer_pos) { + return parser.fetchStreamEnd() + } + + // Is it a directive? + if parser.mark.Column == 1 && parser.buffer[parser.buffer_pos] == '%' { + return parser.fetchDirective() + } + + buf := parser.buffer + pos := parser.buffer_pos + + // Is it the document start indicator? + if parser.mark.Column == 1 && buf[pos] == '-' && buf[pos+1] == '-' && buf[pos+2] == '-' && isBlankOrZero(buf, pos+3) { + return parser.fetchDocumentIndicator(DOCUMENT_START_TOKEN) + } + + // Is it the document end indicator? + if parser.mark.Column == 1 && buf[pos] == '.' && buf[pos+1] == '.' && buf[pos+2] == '.' && isBlankOrZero(buf, pos+3) { + return parser.fetchDocumentIndicator(DOCUMENT_END_TOKEN) + } + + comment_mark := parser.mark + if len(parser.tokens) > 0 && (parser.flow_level == 0 && buf[pos] == ':' || parser.flow_level > 0 && buf[pos] == ',') { + // Associate any following comments with the prior token. + comment_mark = parser.tokens[len(parser.tokens)-1].StartMark + } + defer func() { + if err != nil { + return + } + if parser.skip_comments { + return + } + if len(parser.tokens) > 0 && parser.tokens[len(parser.tokens)-1].Type == BLOCK_ENTRY_TOKEN { + // Sequence indicators alone have no line comments. It becomes + // a head comment for whatever follows. + return + } + err = parser.scanLineComment(comment_mark) + }() + + // Is it the flow sequence start indicator? + if buf[pos] == '[' { + return parser.fetchFlowCollectionStart(FLOW_SEQUENCE_START_TOKEN) + } + + // Is it the flow mapping start indicator? + if parser.buffer[parser.buffer_pos] == '{' { + return parser.fetchFlowCollectionStart(FLOW_MAPPING_START_TOKEN) + } + + // Is it the flow sequence end indicator? + if parser.buffer[parser.buffer_pos] == ']' { + return parser.fetchFlowCollectionEnd( + FLOW_SEQUENCE_END_TOKEN) + } + + // Is it the flow mapping end indicator? + if parser.buffer[parser.buffer_pos] == '}' { + return parser.fetchFlowCollectionEnd( + FLOW_MAPPING_END_TOKEN) + } + + // Is it the flow entry indicator? + if parser.buffer[parser.buffer_pos] == ',' { + return parser.fetchFlowEntry() + } + + // Is it the block entry indicator? + if parser.buffer[parser.buffer_pos] == '-' && isBlankOrZero(parser.buffer, parser.buffer_pos+1) { + return parser.fetchBlockEntry() + } + + // Is it the key indicator? + if parser.buffer[parser.buffer_pos] == '?' && isBlankOrZero(parser.buffer, parser.buffer_pos+1) { + return parser.fetchKey() + } + + // Is it the value indicator? + if parser.buffer[parser.buffer_pos] == ':' && (parser.flow_level > 0 && + !parser.isFlowSequence() || isBlankOrZero(parser.buffer, parser.buffer_pos+1)) { + return parser.fetchValue() + } + + // Is it an alias? + if parser.buffer[parser.buffer_pos] == '*' { + return parser.fetchAnchor(ALIAS_TOKEN) + } + + // Is it an anchor? + if parser.buffer[parser.buffer_pos] == '&' { + return parser.fetchAnchor(ANCHOR_TOKEN) + } + + // Is it a tag? + if parser.buffer[parser.buffer_pos] == '!' { + return parser.fetchTag() + } + + // Is it a literal scalar? + if parser.buffer[parser.buffer_pos] == '|' && parser.flow_level == 0 { + return parser.fetchBlockScalar(true) + } + + // Is it a folded scalar? + if parser.buffer[parser.buffer_pos] == '>' && parser.flow_level == 0 { + return parser.fetchBlockScalar(false) + } + + // Is it a single-quoted scalar? + if parser.buffer[parser.buffer_pos] == '\'' { + return parser.fetchFlowScalar(true) + } + + // Is it a double-quoted scalar? + if parser.buffer[parser.buffer_pos] == '"' { + return parser.fetchFlowScalar(false) + } + + // Is it a plain scalar? + // + // A plain scalar may start with any non-blank characters except + // + // '-', '?', ':', ',', '[', ']', '{', '}', + // '#', '&', '*', '!', '|', '>', '\'', '\"', + // '%', '@', '`'. + // + // In the block context (and, for the '-' indicator, in the flow context + // too), it may also start with the characters + // + // '-', '?', ':' + // + // if it is followed by a non-space character. + // + // The last rule is more restrictive than the specification requires. + // [Go] TODO Make this logic more reasonable. + //switch parser.buffer[parser.buffer_pos] { + //case '-', '?', ':', ',', '?', '-', ',', ':', ']', '[', '}', '{', '&', '#', '!', '*', '>', '|', '"', '\'', '@', '%', '-', '`': + //} + if !(isBlankOrZero(parser.buffer, parser.buffer_pos) || parser.buffer[parser.buffer_pos] == '-' || + parser.buffer[parser.buffer_pos] == '?' || parser.buffer[parser.buffer_pos] == ':' || + parser.buffer[parser.buffer_pos] == ',' || parser.buffer[parser.buffer_pos] == '[' || + parser.buffer[parser.buffer_pos] == ']' || parser.buffer[parser.buffer_pos] == '{' || + parser.buffer[parser.buffer_pos] == '}' || parser.buffer[parser.buffer_pos] == '#' || + parser.buffer[parser.buffer_pos] == '&' || parser.buffer[parser.buffer_pos] == '*' || + parser.buffer[parser.buffer_pos] == '!' || parser.buffer[parser.buffer_pos] == '|' || + parser.buffer[parser.buffer_pos] == '>' || parser.buffer[parser.buffer_pos] == '\'' || + parser.buffer[parser.buffer_pos] == '"' || parser.buffer[parser.buffer_pos] == '%' || + parser.buffer[parser.buffer_pos] == '@' || parser.buffer[parser.buffer_pos] == '`') || + (parser.buffer[parser.buffer_pos] == '-' && !isBlank(parser.buffer, parser.buffer_pos+1)) || + ((parser.buffer[parser.buffer_pos] == '?' || parser.buffer[parser.buffer_pos] == ':') && + !isBlankOrZero(parser.buffer, parser.buffer_pos+1)) { + return parser.fetchPlainScalar() + } + + // If we don't determine the token type so far, it is an error. + return formatScannerErrorContext( + "while scanning for the next token", parser.mark, + "found character that cannot start any token", parser.mark) +} + +// isFlowSequence checks if the previous token indicates we're in a flow +// sequence context. +func (parser *Parser) fetchAnchor(typ TokenType) error { + // An anchor or an alias could be a simple key. + if err := parser.saveSimpleKey(); err != nil { + return err + } + + // A simple key cannot follow an anchor or an alias. + parser.simple_key_allowed = false + + // Create the ALIAS or ANCHOR token and append it to the queue. + var token Token + if err := parser.scanAnchor(&token, typ); err != nil { + return err + } + parser.insertToken(-1, &token) + return nil +} + +// Produce the TAG token. +func (parser *Parser) fetchBlockEntry() error { + // Check if the scanner is in the block context. + if parser.flow_level == 0 { + // Check if we are allowed to start a new entry. + if !parser.simple_key_allowed { + return formatScannerError("block sequence entries are not allowed in this context", parser.mark) + } + // Add the BLOCK-SEQUENCE-START token if needed. + if err := parser.rollIndent(parser.mark.Column, -1, BLOCK_SEQUENCE_START_TOKEN, parser.mark); err != nil { + return err + } + } else { //nolint:staticcheck // there is no problem with this empty branch as it's documentation. + + // It is an error for the '-' indicator to occur in the flow context, + // but we let the Parser detect and report about it because the Parser + // is able to point to the context. + } + + // Reset any potential simple keys on the current flow level. + if err := parser.removeSimpleKey(); err != nil { + return err + } + + // Simple keys are allowed after '-'. + parser.simple_key_allowed = true + + // Consume the token. + start_mark := parser.mark + parser.skip() + end_mark := parser.mark + + // Create the BLOCK-ENTRY token and append it to the queue. + token := Token{ + Type: BLOCK_ENTRY_TOKEN, + StartMark: start_mark, + EndMark: end_mark, + } + parser.insertToken(-1, &token) + return nil +} + +// Produce the KEY token. +func (parser *Parser) fetchBlockScalar(literal bool) error { + // Remove any potential simple keys. + if err := parser.removeSimpleKey(); err != nil { + return err + } + + // A simple key may follow a block scalar. + parser.simple_key_allowed = true + + // Create the SCALAR token and append it to the queue. + var token Token + if err := parser.scanBlockScalar(&token, literal); err != nil { + return err + } + parser.insertToken(-1, &token) + return nil +} + +// Produce the SCALAR(...,single-quoted) or SCALAR(...,double-quoted) tokens. +func (parser *Parser) fetchDirective() error { + // Reset the indentation level. + if err := parser.unrollIndent(-1, parser.mark); err != nil { + return err + } + + // Reset simple keys. + if err := parser.removeSimpleKey(); err != nil { + return err + } + + parser.simple_key_allowed = false + + // Create the YAML-DIRECTIVE or TAG-DIRECTIVE token. + token := Token{} + if err := parser.scanDirective(&token); err != nil { + return err + } + // Append the token to the queue. + parser.insertToken(-1, &token) + return nil +} + +// Produce the DOCUMENT-START or DOCUMENT-END token. +func (parser *Parser) fetchDocumentIndicator(typ TokenType) error { + // Reset the indentation level. + if err := parser.unrollIndent(-1, parser.mark); err != nil { + return err + } + + // Reset simple keys. + if err := parser.removeSimpleKey(); err != nil { + return err + } + + parser.simple_key_allowed = false + + // Consume the token. + start_mark := parser.mark + + parser.skip() + parser.skip() + parser.skip() + + end_mark := parser.mark + + // Create the DOCUMENT-START or DOCUMENT-END token. + token := Token{ + Type: typ, + StartMark: start_mark, + EndMark: end_mark, + } + // Append the token to the queue. + parser.insertToken(-1, &token) + return nil +} + +// Produce the FLOW-SEQUENCE-START or FLOW-MAPPING-START token. +func (parser *Parser) fetchFlowCollectionEnd(typ TokenType) error { + // Reset any potential simple key on the current flow level. + if err := parser.removeSimpleKey(); err != nil { + return err + } + + // Decrease the flow level. + if err := parser.decreaseFlowLevel(); err != nil { + return err + } + + // No simple keys after the indicators ']' and '}'. + parser.simple_key_allowed = false + + // Consume the token. + + start_mark := parser.mark + parser.skip() + end_mark := parser.mark + + // Create the FLOW-SEQUENCE-END of FLOW-MAPPING-END token. + token := Token{ + Type: typ, + StartMark: start_mark, + EndMark: end_mark, + } + // Append the token to the queue. + parser.insertToken(-1, &token) + return nil +} + +// Produce the FLOW-ENTRY token. +func (parser *Parser) fetchFlowCollectionStart(typ TokenType) error { + // The indicators '[' and '{' may start a simple key. + if err := parser.saveSimpleKey(); err != nil { + return err + } + + // Increase the flow level. + if err := parser.increaseFlowLevel(); err != nil { + return err + } + + // A simple key may follow the indicators '[' and '{'. + parser.simple_key_allowed = true + + // Consume the token. + start_mark := parser.mark + parser.skip() + end_mark := parser.mark + + // Create the FLOW-SEQUENCE-START of FLOW-MAPPING-START token. + token := Token{ + Type: typ, + StartMark: start_mark, + EndMark: end_mark, + } + // Append the token to the queue. + parser.insertToken(-1, &token) + return nil +} + +// Produce the FLOW-SEQUENCE-END or FLOW-MAPPING-END token. +func (parser *Parser) fetchFlowEntry() error { + // Reset any potential simple keys on the current flow level. + if err := parser.removeSimpleKey(); err != nil { + return err + } + + // Simple keys are allowed after ','. + parser.simple_key_allowed = true + + // Consume the token. + start_mark := parser.mark + parser.skip() + end_mark := parser.mark + + // Create the FLOW-ENTRY token and append it to the queue. + token := Token{ + Type: FLOW_ENTRY_TOKEN, + StartMark: start_mark, + EndMark: end_mark, + } + parser.insertToken(-1, &token) + return nil +} + +// Produce the BLOCK-ENTRY token. +func (parser *Parser) fetchFlowScalar(single bool) error { + // A plain scalar could be a simple key. + if err := parser.saveSimpleKey(); err != nil { + return err + } + + // A simple key cannot follow a flow scalar. + parser.simple_key_allowed = false + + // Create the SCALAR token and append it to the queue. + var token Token + if err := parser.scanFlowScalar(&token, single); err != nil { + return err + } + parser.insertToken(-1, &token) + return nil +} + +// Produce the SCALAR(...,plain) token. +func (parser *Parser) fetchKey() error { + // In the block context, additional checks are required. + if parser.flow_level == 0 { + // Check if we are allowed to start a new key (not necessary simple). + if !parser.simple_key_allowed { + return formatScannerError("mapping keys are not allowed in this context", parser.mark) + } + // Add the BLOCK-MAPPING-START token if needed. + if err := parser.rollIndent(parser.mark.Column, -1, BLOCK_MAPPING_START_TOKEN, parser.mark); err != nil { + return err + } + } + + // Reset any potential simple keys on the current flow level. + if err := parser.removeSimpleKey(); err != nil { + return err + } + + // Simple keys are allowed after '?' in the block context. + parser.simple_key_allowed = parser.flow_level == 0 + + // Consume the token. + start_mark := parser.mark + parser.skip() + end_mark := parser.mark + + // Create the KEY token and append it to the queue. + token := Token{ + Type: KEY_TOKEN, + StartMark: start_mark, + EndMark: end_mark, + } + parser.insertToken(-1, &token) + return nil +} + +// Produce the VALUE token. +func (parser *Parser) fetchPlainScalar() error { + // A plain scalar could be a simple key. + if err := parser.saveSimpleKey(); err != nil { + return err + } + + // A simple key cannot follow a flow scalar. + parser.simple_key_allowed = false + + // Create the SCALAR token and append it to the queue. + var token Token + if err := parser.scanPlainScalar(&token); err != nil { + return err + } + parser.insertToken(-1, &token) + return nil +} + +// Eat whitespaces and comments until the next token is found. +func (parser *Parser) fetchStreamEnd() error { + // Force new line. + if parser.mark.Column != 1 { + parser.mark.Column = 1 + parser.mark.Line++ + } + + // Reset the indentation level. + if err := parser.unrollIndent(-1, parser.mark); err != nil { + return err + } + + // Reset simple keys. + if err := parser.removeSimpleKey(); err != nil { + return err + } + parser.simple_key = SimpleKey{} + parser.simple_key_stack = []SimpleKey{} + parser.simple_key_allowed = false + + // Create the STREAM-END token and append it to the queue. + token := Token{ + Type: STREAM_END_TOKEN, + StartMark: parser.mark, + EndMark: parser.mark, + } + parser.insertToken(-1, &token) + return nil +} + +// Produce a VERSION-DIRECTIVE or TAG-DIRECTIVE token. +func (parser *Parser) fetchStreamStart() error { + // Set the initial indentation. + parser.indent = -1 + + // Initialize the simple key stack. + parser.simple_key = SimpleKey{} + parser.simple_key_stack = []SimpleKey{} + + // A simple key is allowed at the beginning of the stream. + parser.simple_key_allowed = true + + // We have started. + parser.stream_start_produced = true + + // Create the STREAM-START token and append it to the queue. + token := Token{ + Type: STREAM_START_TOKEN, + StartMark: parser.mark, + EndMark: parser.mark, + encoding: parser.encoding, + } + parser.insertToken(-1, &token) + return nil +} + +// Produce the STREAM-END token and shut down the scanner. +func (parser *Parser) fetchTag() error { + // A tag could be a simple key. + if err := parser.saveSimpleKey(); err != nil { + return err + } + + // A simple key cannot follow a tag. + parser.simple_key_allowed = false + + // Create the TAG token and append it to the queue. + var token Token + if err := parser.scanTag(&token); err != nil { + return err + } + parser.insertToken(-1, &token) + return nil +} + +// Produce the SCALAR(...,literal) or SCALAR(...,folded) tokens. +func (parser *Parser) fetchValue() error { + simple_key := &parser.simple_key + + // Have we found a simple key? + if parser.simple_key_possible && simple_key.mark.Line == parser.mark.Line { + // Create the KEY token and insert it into the queue. + token := Token{ + Type: KEY_TOKEN, + StartMark: simple_key.mark, + EndMark: simple_key.mark, + } + parser.insertToken(simple_key.token_number-parser.tokens_parsed, &token) + + // In the block context, we may need to add the BLOCK-MAPPING-START token. + if err := parser.rollIndent(simple_key.mark.Column, + simple_key.token_number, + BLOCK_MAPPING_START_TOKEN, simple_key.mark); err != nil { + return err + } + + // Remove the simple key. + parser.simple_key_possible = false + simple_key.required = false + + // A simple key cannot follow another simple key. + parser.simple_key_allowed = false + + } else { + // The ':' indicator follows a complex key. + + // In the block context, extra checks are required. + if parser.flow_level == 0 { + + // Check if we are allowed to start a complex value. + if !parser.simple_key_allowed { + return formatScannerError("mapping values are not allowed in this context", parser.mark) + } + + // Add the BLOCK-MAPPING-START token if needed. + if err := parser.rollIndent(parser.mark.Column, -1, BLOCK_MAPPING_START_TOKEN, parser.mark); err != nil { + return err + } + } + + // Simple keys after ':' are allowed in the block context. + parser.simple_key_allowed = parser.flow_level == 0 + } + + // Consume the token. + start_mark := parser.mark + parser.skip() + end_mark := parser.mark + + // Create the VALUE token and append it to the queue. + token := Token{ + Type: VALUE_TOKEN, + StartMark: start_mark, + EndMark: end_mark, + } + parser.insertToken(-1, &token) + return nil +} + +// Produce the ALIAS or ANCHOR token. +func (parser *Parser) scanAnchor(token *Token, typ TokenType) error { + var s []byte + + // Eat the indicator character. + start_mark := parser.mark + parser.skip() + + // Consume the value. + if parser.unread < 1 { + if err := parser.updateBuffer(1); err != nil { + return err + } + } + + for isAnchorChar(parser.buffer, parser.buffer_pos) { + s = parser.read(s) + if parser.unread < 1 { + if err := parser.updateBuffer(1); err != nil { + return err + } + } + } + + end_mark := parser.mark + + /* + * Check if length of the anchor is greater than 0 and it is followed by + * a whitespace character or one of the indicators: + * + * '?', ':', ',', ']', '}', '%', '@', '`'. + */ + + if len(s) == 0 || + !(isBlankOrZero(parser.buffer, parser.buffer_pos) || parser.buffer[parser.buffer_pos] == '?' || + parser.buffer[parser.buffer_pos] == ':' || parser.buffer[parser.buffer_pos] == ',' || + parser.buffer[parser.buffer_pos] == ']' || parser.buffer[parser.buffer_pos] == '}' || + parser.buffer[parser.buffer_pos] == '%' || parser.buffer[parser.buffer_pos] == '@' || + parser.buffer[parser.buffer_pos] == '`') { + context := "while scanning an alias" + if typ == ANCHOR_TOKEN { + context = "while scanning an anchor" + } + return formatScannerErrorContext(context, start_mark, + "did not find expected alphabetic or numeric character", parser.mark) + } + + // Create a token. + *token = Token{ + Type: typ, + StartMark: start_mark, + EndMark: end_mark, + Value: s, + } + + return nil +} + +// scanTag scans a TAG token. +func (parser *Parser) scanBlockScalar(token *Token, literal bool) error { + // Eat the indicator '|' or '>'. + start_mark := parser.mark + parser.skip() + + // Scan the additional block scalar indicators. + if parser.unread < 1 { + if err := parser.updateBuffer(1); err != nil { + return err + } + } + + // Check for a chomping indicator. + var chomping, increment int + if parser.buffer[parser.buffer_pos] == '+' || parser.buffer[parser.buffer_pos] == '-' { + // Set the chomping method and eat the indicator. + if parser.buffer[parser.buffer_pos] == '+' { + chomping = +1 + } else { + chomping = -1 + } + parser.skip() + + // Check for an indentation indicator. + if parser.unread < 1 { + if err := parser.updateBuffer(1); err != nil { + return err + } + } + if isDigit(parser.buffer, parser.buffer_pos) { + // Check that the indentation is greater than 0. + if parser.buffer[parser.buffer_pos] == '0' { + return formatScannerErrorContext("while scanning a block scalar", start_mark, + "found an indentation indicator equal to 0", parser.mark) + } + + // Get the indentation level and eat the indicator. + increment = asDigit(parser.buffer, parser.buffer_pos) + parser.skip() + } + + } else if isDigit(parser.buffer, parser.buffer_pos) { + // Do the same as above, but in the opposite order. + + if parser.buffer[parser.buffer_pos] == '0' { + return formatScannerErrorContext("while scanning a block scalar", start_mark, + "found an indentation indicator equal to 0", parser.mark) + } + increment = asDigit(parser.buffer, parser.buffer_pos) + parser.skip() + + if parser.unread < 1 { + if err := parser.updateBuffer(1); err != nil { + return err + } + } + if parser.buffer[parser.buffer_pos] == '+' || parser.buffer[parser.buffer_pos] == '-' { + if parser.buffer[parser.buffer_pos] == '+' { + chomping = +1 + } else { + chomping = -1 + } + parser.skip() + } + } + + // Eat whitespaces and comments to the end of the line. + if parser.unread < 1 { + if err := parser.updateBuffer(1); err != nil { + return err + } + } + for isBlank(parser.buffer, parser.buffer_pos) { + parser.skip() + if parser.unread < 1 { + if err := parser.updateBuffer(1); err != nil { + return err + } + } + } + if parser.buffer[parser.buffer_pos] == '#' { + if err := parser.scanLineComment(start_mark); err != nil { + return err + } + for !isBreakOrZero(parser.buffer, parser.buffer_pos) { + parser.skip() + if parser.unread < 1 { + if err := parser.updateBuffer(1); err != nil { + return err + } + } + } + } + + // Check if we are at the end of the line. + if !isBreakOrZero(parser.buffer, parser.buffer_pos) { + return formatScannerErrorContext("while scanning a block scalar", start_mark, + "did not find expected comment or line break", parser.mark) + } + + // Eat a line break. + if isLineBreak(parser.buffer, parser.buffer_pos) { + if parser.unread < 2 { + if err := parser.updateBuffer(2); err != nil { + return err + } + } + parser.skipLine() + } + + end_mark := parser.mark + + // Set the indentation level if it was specified. + var indent int + if increment > 0 { + if parser.indent >= 0 { + indent = parser.indent + increment + } else { + // With 1-based columns, the line start is column 1, so an + // explicit indent indicator of n means column n+1. + indent = increment + 1 + } + } + + // Scan the leading line breaks and determine the indentation level if + // needed. + var s, leading_break, trailing_breaks []byte + if err := parser.scanBlockScalarBreaks(&indent, &trailing_breaks, start_mark, &end_mark); err != nil { + return err + } + + // Scan the block scalar content. + if parser.unread < 1 { + if err := parser.updateBuffer(1); err != nil { + return err + } + } + var leading_blank, trailing_blank bool + for parser.mark.Column == indent && !isZeroChar(parser.buffer, parser.buffer_pos) { + // We are at the beginning of a non-empty line. + + // Is it a trailing whitespace? + trailing_blank = isBlank(parser.buffer, parser.buffer_pos) + + // Check if we need to fold the leading line break. + if !literal && !leading_blank && !trailing_blank && len(leading_break) > 0 && leading_break[0] == '\n' { + // Do we need to join the lines by space? + if len(trailing_breaks) == 0 { + s = append(s, ' ') + } + } else { + s = append(s, leading_break...) + } + leading_break = leading_break[:0] + + // Append the remaining line breaks. + s = append(s, trailing_breaks...) + trailing_breaks = trailing_breaks[:0] + + // Is it a leading whitespace? + leading_blank = isBlank(parser.buffer, parser.buffer_pos) + + // Consume the current line. + for !isBreakOrZero(parser.buffer, parser.buffer_pos) { + s = parser.read(s) + if parser.unread < 1 { + if err := parser.updateBuffer(1); err != nil { + return err + } + } + } + + // Consume the line break. + if parser.unread < 2 { + if err := parser.updateBuffer(2); err != nil { + return err + } + } + + leading_break = parser.readLine(leading_break) + + // Eat the following indentation spaces and line breaks. + if err := parser.scanBlockScalarBreaks(&indent, &trailing_breaks, start_mark, &end_mark); err != nil { + return err + } + } + + // Chomp the tail. + if chomping != -1 { + s = append(s, leading_break...) + } + if chomping == 1 { + s = append(s, trailing_breaks...) + } + + // Create a token. + *token = Token{ + Type: SCALAR_TOKEN, + StartMark: start_mark, + EndMark: end_mark, + Value: s, + Style: LITERAL_SCALAR_STYLE, + } + if !literal { + token.Style = FOLDED_SCALAR_STYLE + } + return nil +} + +// Scan indentation spaces and line breaks for a block scalar. +// Determine the indentation level if needed. +func (parser *Parser) scanBlockScalarBreaks(indent *int, breaks *[]byte, start_mark Mark, end_mark *Mark) error { + *end_mark = parser.mark + + // Eat the indentation spaces and line breaks. + max_indent := 0 + for { + // Eat the indentation spaces. + if parser.unread < 1 { + if err := parser.updateBuffer(1); err != nil { + return err + } + } + for (*indent == 0 || parser.mark.Column < *indent) && isSpace(parser.buffer, parser.buffer_pos) { + parser.skip() + if parser.unread < 1 { + if err := parser.updateBuffer(1); err != nil { + return err + } + } + } + if parser.mark.Column > max_indent { + max_indent = parser.mark.Column + } + + // Check for a tab character messing the indentation. + if (*indent == 0 || parser.mark.Column < *indent) && isTab(parser.buffer, parser.buffer_pos) { + return formatScannerErrorContext("while scanning a block scalar", start_mark, + "found a tab character where an indentation space is expected", parser.mark) + } + + // Have we found a non-empty line? + if !isLineBreak(parser.buffer, parser.buffer_pos) { + break + } + + // Consume the line break. + if parser.unread < 2 { + if err := parser.updateBuffer(2); err != nil { + return err + } + } + // [Go] Should really be returning breaks instead. + *breaks = parser.readLine(*breaks) + *end_mark = parser.mark + } + + // Determine the indentation level if needed. + if *indent == 0 { + *indent = max_indent + if *indent < parser.indent+1 { + *indent = parser.indent + 1 + } + if *indent < 1 { + *indent = 1 + } + } + return nil +} + +// Scan a quoted scalar. +func (parser *Parser) scanComments(scan_mark Mark) error { + token := parser.tokens[len(parser.tokens)-1] + + if token.Type == FLOW_ENTRY_TOKEN && len(parser.tokens) > 1 { + token = parser.tokens[len(parser.tokens)-2] + } + + token_mark := token.StartMark + var start_mark Mark + next_indent := parser.indent + if next_indent < 0 { + next_indent = 0 + } + + recent_empty := false + first_empty := parser.newlines <= 1 + + line := parser.mark.Line + column := parser.mark.Column + + var text []byte + + // The foot line is the place where a comment must start to + // still be considered as a foot of the prior content. + // If there's some content in the currently parsed line, then + // the foot is the line below it. + foot_line := -1 + if scan_mark.Line > 0 { + foot_line = parser.mark.Line - parser.newlines + 1 + if parser.newlines == 0 && parser.mark.Column > 1 { + foot_line++ + } + } + + peek := 0 + for ; peek < 512; peek++ { + if parser.unread < peek+1 { + if parser.updateBuffer(peek+1) != nil { + break + } + } + column++ + if isBlank(parser.buffer, parser.buffer_pos+peek) { + continue + } + c := parser.buffer[parser.buffer_pos+peek] + close_flow := parser.flow_level > 0 && (c == ']' || c == '}') + if close_flow || isBreakOrZero(parser.buffer, parser.buffer_pos+peek) { + // Got line break or terminator. + if close_flow || !recent_empty { + if close_flow || first_empty && (start_mark.Line == foot_line && token.Type != VALUE_TOKEN || start_mark.Column-1 < next_indent) { + // This is the first empty line and there were no empty lines before, + // so this initial part of the comment is a foot of the prior token + // instead of being a head for the following one. Split it up. + // Alternatively, this might also be the last comment inside a flow + // scope, so it must be a footer. + if len(text) > 0 { + if start_mark.Column-1 < next_indent { + // If dedented it's unrelated to the prior token. + token_mark = start_mark + } + parser.comments = append(parser.comments, Comment{ + ScanMark: scan_mark, + TokenMark: token_mark, + StartMark: start_mark, + EndMark: Mark{parser.mark.Index + peek, line, column}, + Foot: text, + }) + scan_mark = Mark{parser.mark.Index + peek, line, column} + token_mark = scan_mark + text = nil + } + } else { + if len(text) > 0 && parser.buffer[parser.buffer_pos+peek] != 0 { + text = append(text, '\n') + } + } + } + if !isLineBreak(parser.buffer, parser.buffer_pos+peek) { + break + } + first_empty = false + recent_empty = true + column = 1 + line++ + continue + } + + if len(text) > 0 && (close_flow || column-1 < next_indent && column != start_mark.Column) { + // The comment at the different indentation is a foot of the + // preceding data rather than a head of the upcoming one. + parser.comments = append(parser.comments, Comment{ + ScanMark: scan_mark, + TokenMark: token_mark, + StartMark: start_mark, + EndMark: Mark{parser.mark.Index + peek, line, column}, + Foot: text, + }) + scan_mark = Mark{parser.mark.Index + peek, line, column} + token_mark = scan_mark + text = nil + } + + if parser.buffer[parser.buffer_pos+peek] != '#' { + break + } + + if len(text) == 0 { + start_mark = Mark{parser.mark.Index + peek, line, column} + } else { + text = append(text, '\n') + } + + recent_empty = false + + // Consume until after the consumed comment line. + seen := parser.mark.Index + peek + for { + if parser.unread < 1 { + if err := parser.updateBuffer(1); err != nil { + return err + } + } + if isBreakOrZero(parser.buffer, parser.buffer_pos) { + if parser.mark.Index >= seen { + break + } + if parser.unread < 2 { + if err := parser.updateBuffer(2); err != nil { + return err + } + } + parser.skipLine() + } else if parser.mark.Index >= seen { + text = parser.read(text) + } else { + parser.skip() + } + } + + peek = 0 + column = 1 + line = parser.mark.Line + next_indent = parser.indent + if next_indent < 0 { + next_indent = 0 + } + } + + if len(text) > 0 { + parser.comments = append(parser.comments, Comment{ + ScanMark: scan_mark, + TokenMark: start_mark, + StartMark: start_mark, + EndMark: Mark{parser.mark.Index + peek - 1, line, column}, + Head: text, + }) + } + return nil +} + +// scanDirective scans a directive (%YAML or %TAG). +func (parser *Parser) scanDirective(token *Token) error { + // Eat '%'. + start_mark := parser.mark + parser.skip() + + // Scan the directive name. + var name []byte + if err := parser.scanDirectiveName(start_mark, &name); err != nil { + return err + } + + // Is it a YAML directive? + if bytes.Equal(name, []byte("YAML")) { + // Scan the VERSION directive value. + var major, minor int8 + if err := parser.scanVersionDirectiveValue(start_mark, &major, &minor); err != nil { + return err + } + end_mark := parser.mark + + // Create a VERSION-DIRECTIVE token. + *token = Token{ + Type: VERSION_DIRECTIVE_TOKEN, + StartMark: start_mark, + EndMark: end_mark, + major: major, + minor: minor, + } + + // Is it a TAG directive? + } else if bytes.Equal(name, []byte("TAG")) { + // Scan the TAG directive value. + var handle, prefix []byte + if err := parser.scanTagDirectiveValue(start_mark, &handle, &prefix); err != nil { + return err + } + end_mark := parser.mark + + // Create a TAG-DIRECTIVE token. + *token = Token{ + Type: TAG_DIRECTIVE_TOKEN, + StartMark: start_mark, + EndMark: end_mark, + Value: handle, + prefix: prefix, + } + + // Unknown directive. + } else { + return formatScannerErrorContext("while scanning a directive", start_mark, + "found unknown directive name", parser.mark) + } + + // Eat the rest of the line including any comments. + if parser.unread < 1 { + if err := parser.updateBuffer(1); err != nil { + return err + } + } + + for isBlank(parser.buffer, parser.buffer_pos) { + parser.skip() + if parser.unread < 1 { + if err := parser.updateBuffer(1); err != nil { + return err + } + } + } + + if parser.buffer[parser.buffer_pos] == '#' { + // [Go] Discard this inline comment for the time being. + //if !parser.ScanLineComment(start_mark) { + // return false + //} + for !isBreakOrZero(parser.buffer, parser.buffer_pos) { + parser.skip() + if parser.unread < 1 { + if err := parser.updateBuffer(1); err != nil { + return err + } + } + } + } + + // Check if we are at the end of the line. + if !isBreakOrZero(parser.buffer, parser.buffer_pos) { + return formatScannerErrorContext("while scanning a directive", start_mark, + "did not find expected comment or line break", parser.mark) + } + + // Eat a line break. + if isLineBreak(parser.buffer, parser.buffer_pos) { + if parser.unread < 2 { + if err := parser.updateBuffer(2); err != nil { + return err + } + } + parser.skipLine() + } + + return nil +} + +// Scan the directive name. +// +// Scope: +// +// %YAML 1.1 # a comment \n +// ^^^^ +// %TAG !yaml! tag:yaml.org,2002: \n +// ^^^ +func (parser *Parser) scanDirectiveName(start_mark Mark, name *[]byte) error { + // Consume the directive name. + if parser.unread < 1 { + if err := parser.updateBuffer(1); err != nil { + return err + } + } + + var s []byte + for isAlpha(parser.buffer, parser.buffer_pos) { + s = parser.read(s) + if parser.unread < 1 { + if err := parser.updateBuffer(1); err != nil { + return err + } + } + } + + // Check if the name is empty. + if len(s) == 0 { + return formatScannerErrorContext("while scanning a directive", start_mark, + "could not find expected directive name", parser.mark) + } + + // Check for an blank character after the name. + if !isBlankOrZero(parser.buffer, parser.buffer_pos) { + return formatScannerErrorContext("while scanning a directive", start_mark, + "found unexpected non-alphabetical character", parser.mark) + } + *name = s + return nil +} + +// Scan the value of VERSION-DIRECTIVE. +// +// Scope: +// +// %YAML 1.1 # a comment \n +// ^^^^^^ +func (parser *Parser) scanFlowScalar(token *Token, single bool) error { + // Eat the left quote. + start_mark := parser.mark + parser.skip() + + // Consume the content of the quoted scalar. + var s, leading_break, trailing_breaks, whitespaces []byte + for { + // Check that there are no document indicators at the beginning + // of the line. + if parser.unread < 4 { + if err := parser.updateBuffer(4); err != nil { + return err + } + } + + if parser.mark.Column == 1 && + ((parser.buffer[parser.buffer_pos+0] == '-' && + parser.buffer[parser.buffer_pos+1] == '-' && + parser.buffer[parser.buffer_pos+2] == '-') || + (parser.buffer[parser.buffer_pos+0] == '.' && + parser.buffer[parser.buffer_pos+1] == '.' && + parser.buffer[parser.buffer_pos+2] == '.')) && + isBlankOrZero(parser.buffer, parser.buffer_pos+3) { + return formatScannerErrorContext("while scanning a quoted scalar", start_mark, + "found unexpected document indicator", parser.mark) + } + + // Check for EOF. + if isZeroChar(parser.buffer, parser.buffer_pos) { + return formatScannerErrorContext("while scanning a quoted scalar", start_mark, + "found unexpected end of stream", parser.mark) + } + + // Consume non-blank characters. + leading_blanks := false + for !isBlankOrZero(parser.buffer, parser.buffer_pos) { + if single && parser.buffer[parser.buffer_pos] == '\'' && parser.buffer[parser.buffer_pos+1] == '\'' { + // Is is an escaped single quote. + s = append(s, '\'') + parser.skip() + parser.skip() + + } else if single && parser.buffer[parser.buffer_pos] == '\'' { + // It is a right single quote. + break + } else if !single && parser.buffer[parser.buffer_pos] == '"' { + // It is a right double quote. + break + } else if !single && parser.buffer[parser.buffer_pos] == '\\' && isLineBreak(parser.buffer, parser.buffer_pos+1) { + // It is an escaped line break. + if parser.unread < 3 { + if err := parser.updateBuffer(3); err != nil { + return err + } + } + parser.skip() + parser.skipLine() + leading_blanks = true + break + + } else if !single && parser.buffer[parser.buffer_pos] == '\\' { + // It is an escape sequence. + code_length := 0 + + // Check the escape character. + switch parser.buffer[parser.buffer_pos+1] { + case '0': + s = append(s, 0) + case 'a': + s = append(s, '\x07') + case 'b': + s = append(s, '\x08') + case 't', '\t': + s = append(s, '\x09') + case 'n': + s = append(s, '\x0A') + case 'v': + s = append(s, '\x0B') + case 'f': + s = append(s, '\x0C') + case 'r': + s = append(s, '\x0D') + case 'e': + s = append(s, '\x1B') + case ' ': + s = append(s, '\x20') + case '"': + s = append(s, '"') + case '\'': + s = append(s, '\'') + case '\\': + s = append(s, '\\') + case 'N': // NEL (#x85) + s = append(s, '\xC2') + s = append(s, '\x85') + case '_': // #xA0 + s = append(s, '\xC2') + s = append(s, '\xA0') + case 'L': // LS (#x2028) + s = append(s, '\xE2') + s = append(s, '\x80') + s = append(s, '\xA8') + case 'P': // PS (#x2029) + s = append(s, '\xE2') + s = append(s, '\x80') + s = append(s, '\xA9') + case 'x': + code_length = 2 + case 'u': + code_length = 4 + case 'U': + code_length = 8 + default: + return formatScannerErrorContext("while scanning a quoted scalar", start_mark, + "found unknown escape character", parser.mark) + } + + parser.skip() + parser.skip() + + // Consume an arbitrary escape code. + if code_length > 0 { + var value int + + // Scan the character value. + if parser.unread < code_length { + if err := parser.updateBuffer(code_length); err != nil { + return err + } + } + for k := 0; k < code_length; k++ { + if !isHex(parser.buffer, parser.buffer_pos+k) { + return formatScannerErrorContext("while scanning a quoted scalar", start_mark, + "did not find expected hexadecimal number", parser.mark) + } + value = (value << 4) + asHex(parser.buffer, parser.buffer_pos+k) + } + + // Check the value and write the character. + if (value >= 0xD800 && value <= 0xDFFF) || value > 0x10FFFF { + return formatScannerErrorContext("while scanning a quoted scalar", start_mark, + "found invalid Unicode character escape code", parser.mark) + } + if value <= 0x7F { + s = append(s, byte(value)) + } else if value <= 0x7FF { + s = append(s, byte(0xC0+(value>>6))) + s = append(s, byte(0x80+(value&0x3F))) + } else if value <= 0xFFFF { + s = append(s, byte(0xE0+(value>>12))) + s = append(s, byte(0x80+((value>>6)&0x3F))) + s = append(s, byte(0x80+(value&0x3F))) + } else { + s = append(s, byte(0xF0+(value>>18))) + s = append(s, byte(0x80+((value>>12)&0x3F))) + s = append(s, byte(0x80+((value>>6)&0x3F))) + s = append(s, byte(0x80+(value&0x3F))) + } + + // Advance the pointer. + for k := 0; k < code_length; k++ { + parser.skip() + } + } + } else { + // It is a non-escaped non-blank character. + s = parser.read(s) + } + if parser.unread < 2 { + if err := parser.updateBuffer(2); err != nil { + return err + } + } + } + + if parser.unread < 1 { + if err := parser.updateBuffer(1); err != nil { + return err + } + } + + // Check if we are at the end of the scalar. + if single { + if parser.buffer[parser.buffer_pos] == '\'' { + break + } + } else { + if parser.buffer[parser.buffer_pos] == '"' { + break + } + } + + // Consume blank characters. + for isBlank(parser.buffer, parser.buffer_pos) || isLineBreak(parser.buffer, parser.buffer_pos) { + if isBlank(parser.buffer, parser.buffer_pos) { + // Consume a space or a tab character. + if !leading_blanks { + whitespaces = parser.read(whitespaces) + } else { + parser.skip() + } + } else { + if parser.unread < 2 { + if err := parser.updateBuffer(2); err != nil { + return err + } + } + + // Check if it is a first line break. + if !leading_blanks { + whitespaces = whitespaces[:0] + leading_break = parser.readLine(leading_break) + leading_blanks = true + } else { + trailing_breaks = parser.readLine(trailing_breaks) + } + } + if parser.unread < 1 { + if err := parser.updateBuffer(1); err != nil { + return err + } + } + } + + // Join the whitespaces or fold line breaks. + if leading_blanks { + // Do we need to fold line breaks? + if len(leading_break) > 0 && leading_break[0] == '\n' { + if len(trailing_breaks) == 0 { + s = append(s, ' ') + } else { + s = append(s, trailing_breaks...) + } + } else { + s = append(s, leading_break...) + s = append(s, trailing_breaks...) + } + trailing_breaks = trailing_breaks[:0] + leading_break = leading_break[:0] + } else { + s = append(s, whitespaces...) + whitespaces = whitespaces[:0] + } + } + + // Eat the right quote. + parser.skip() + end_mark := parser.mark + + // Create a token. + *token = Token{ + Type: SCALAR_TOKEN, + StartMark: start_mark, + EndMark: end_mark, + Value: s, + Style: SINGLE_QUOTED_SCALAR_STYLE, + } + if !single { + token.Style = DOUBLE_QUOTED_SCALAR_STYLE + } + return nil +} + +// Scan a plain scalar. +func (parser *Parser) scanLineComment(token_mark Mark) error { + if parser.newlines > 0 { + return nil + } + + var start_mark Mark + var text []byte + + for peek := 0; peek < 512; peek++ { + if parser.unread < peek+1 { + if parser.updateBuffer(peek+1) != nil { + break + } + } + if isBlank(parser.buffer, parser.buffer_pos+peek) { + continue + } + if parser.buffer[parser.buffer_pos+peek] == '#' { + seen := parser.mark.Index + peek + for { + if parser.unread < 1 { + if err := parser.updateBuffer(1); err != nil { + return err + } + } + if isBreakOrZero(parser.buffer, parser.buffer_pos) { + if parser.mark.Index >= seen { + break + } + if parser.unread < 2 { + if err := parser.updateBuffer(2); err != nil { + return err + } + } + parser.skipLine() + } else if parser.mark.Index >= seen { + if len(text) == 0 { + start_mark = parser.mark + } + text = parser.read(text) + } else { + parser.skip() + } + } + } + break + } + if len(text) > 0 { + parser.comments = append(parser.comments, Comment{ + ScanMark: token_mark, + TokenMark: token_mark, + StartMark: start_mark, + EndMark: parser.mark, + Line: text, + }) + } + return nil +} + +// scanComments scans and associates comments with tokens, handling head, line, +// and foot comments based on their position relative to tokens. +func (parser *Parser) scanPlainScalar(token *Token) error { + var s, leading_break, trailing_breaks, whitespaces []byte + var leading_blanks bool + indent := parser.indent + 1 + + start_mark := parser.mark + end_mark := parser.mark + + // Consume the content of the plain scalar. + for { + // Check for a document indicator. + if parser.unread < 4 { + if err := parser.updateBuffer(4); err != nil { + return err + } + } + if parser.mark.Column == 1 && + ((parser.buffer[parser.buffer_pos+0] == '-' && + parser.buffer[parser.buffer_pos+1] == '-' && + parser.buffer[parser.buffer_pos+2] == '-') || + (parser.buffer[parser.buffer_pos+0] == '.' && + parser.buffer[parser.buffer_pos+1] == '.' && + parser.buffer[parser.buffer_pos+2] == '.')) && + isBlankOrZero(parser.buffer, parser.buffer_pos+3) { + break + } + + // Check for a comment. + if parser.buffer[parser.buffer_pos] == '#' { + break + } + + // Consume non-blank characters. + for !isBlankOrZero(parser.buffer, parser.buffer_pos) { + + // Check for indicators that may end a plain scalar. + // Check for ": " or isEndOfScalarInFlowContentChar in flow context. + if (parser.buffer[parser.buffer_pos] == ':' && isBlankOrZero(parser.buffer, parser.buffer_pos+1)) || + (parser.flow_level > 0 && isEndOfScalarInFlowContentChar(parser.buffer, parser.buffer_pos)) { + break + } + + // Check if we need to join whitespaces and breaks. + if leading_blanks || len(whitespaces) > 0 { + if leading_blanks { + // Do we need to fold line breaks? + if leading_break[0] == '\n' { + if len(trailing_breaks) == 0 { + s = append(s, ' ') + } else { + s = append(s, trailing_breaks...) + } + } else { + s = append(s, leading_break...) + s = append(s, trailing_breaks...) + } + trailing_breaks = trailing_breaks[:0] + leading_break = leading_break[:0] + leading_blanks = false + } else { + s = append(s, whitespaces...) + whitespaces = whitespaces[:0] + } + } + + // Copy the character. + s = parser.read(s) + + end_mark = parser.mark + if parser.unread < 2 { + if err := parser.updateBuffer(2); err != nil { + return err + } + } + } + + // Is it the end? + if !(isBlank(parser.buffer, parser.buffer_pos) || isLineBreak(parser.buffer, parser.buffer_pos)) { + break + } + + // Consume blank characters. + if parser.unread < 1 { + if err := parser.updateBuffer(1); err != nil { + return err + } + } + + for isBlank(parser.buffer, parser.buffer_pos) || isLineBreak(parser.buffer, parser.buffer_pos) { + if isBlank(parser.buffer, parser.buffer_pos) { + + // Check for tab characters that abuse indentation. + if leading_blanks && parser.mark.Column < indent && isTab(parser.buffer, parser.buffer_pos) { + return formatScannerErrorContext("while scanning a plain scalar", start_mark, + "found a tab character that violates indentation", parser.mark) + } + + // Consume a space or a tab character. + if !leading_blanks { + whitespaces = parser.read(whitespaces) + } else { + parser.skip() + } + } else { + if parser.unread < 2 { + if err := parser.updateBuffer(2); err != nil { + return err + } + } + + // Check if it is a first line break. + if !leading_blanks { + whitespaces = whitespaces[:0] + leading_break = parser.readLine(leading_break) + leading_blanks = true + } else { + trailing_breaks = parser.readLine(trailing_breaks) + } + } + if parser.unread < 1 { + if err := parser.updateBuffer(1); err != nil { + return err + } + } + } + + // Check indentation level. + if parser.flow_level == 0 && parser.mark.Column < indent { + break + } + } + + // Create a token. + *token = Token{ + Type: SCALAR_TOKEN, + StartMark: start_mark, + EndMark: end_mark, + Value: s, + Style: PLAIN_SCALAR_STYLE, + } + + // Note that we change the 'simple_key_allowed' flag. + if leading_blanks { + parser.simple_key_allowed = true + } + return nil +} + +// scanLineComment scans a comment on the same line as a token. +func (parser *Parser) scanTag(token *Token) error { + var handle, suffix []byte + + start_mark := parser.mark + + // Check if the tag is in the canonical form. + if parser.unread < 2 { + if err := parser.updateBuffer(2); err != nil { + return err + } + } + + if parser.buffer[parser.buffer_pos+1] == '<' { + // Keep the handle as '' + + // Eat '!<' + parser.skip() + parser.skip() + + // Consume the tag value (verbatim tag - flow indicators allowed). + if err := parser.scanTagURI(false, true, nil, start_mark, &suffix); err != nil { + return err + } + + // Check for '>' and eat it. + if parser.buffer[parser.buffer_pos] != '>' { + return formatScannerErrorContext("while scanning a tag", start_mark, + "did not find the expected '>'", parser.mark) + } + + parser.skip() + } else { + // The tag has either the '!suffix' or the '!handle!suffix' form. + + // First, try to scan a handle. + if err := parser.scanTagHandle(false, start_mark, &handle); err != nil { + return err + } + + // Check if it is, indeed, handle. + if handle[0] == '!' && len(handle) > 1 && handle[len(handle)-1] == '!' { + // Scan the suffix now (short form - flow indicators + // not allowed). + if err := parser.scanTagURI(false, false, nil, start_mark, &suffix); err != nil { + return err + } + } else { + // It wasn't a handle after all. Scan the rest of the + // tag (short form). + if err := parser.scanTagURI(false, false, handle, start_mark, &suffix); err != nil { + return err + } + + // Set the handle to '!'. + handle = []byte{'!'} + + // A special case: the '!' tag. Set the handle to '' + // and the suffix to '!'. + if len(suffix) == 0 { + handle, suffix = suffix, handle + } + } + } + + // Check the character which ends the tag. + if parser.unread < 1 { + if err := parser.updateBuffer(1); err != nil { + return err + } + } + if !isBlankOrZero(parser.buffer, parser.buffer_pos) { + return formatScannerErrorContext("while scanning a tag", start_mark, + "did not find expected whitespace or line break", parser.mark) + } + + end_mark := parser.mark + + // Create a token. + *token = Token{ + Type: TAG_TOKEN, + StartMark: start_mark, + EndMark: end_mark, + Value: handle, + suffix: suffix, + } + return nil +} + +// Scan a tag handle. +func (parser *Parser) scanTagDirectiveValue(start_mark Mark, handle, prefix *[]byte) error { + var handle_value, prefix_value []byte + + // Eat whitespaces. + if parser.unread < 1 { + if err := parser.updateBuffer(1); err != nil { + return err + } + } + + for isBlank(parser.buffer, parser.buffer_pos) { + parser.skip() + if parser.unread < 1 { + if err := parser.updateBuffer(1); err != nil { + return err + } + } + } + + // Scan a handle. + if err := parser.scanTagHandle(true, start_mark, &handle_value); err != nil { + return err + } + + // Expect a whitespace. + if parser.unread < 1 { + if err := parser.updateBuffer(1); err != nil { + return err + } + } + if !isBlank(parser.buffer, parser.buffer_pos) { + return formatScannerErrorContext("while scanning a %TAG directive", start_mark, + "did not find expected whitespace", parser.mark) + } + + // Eat whitespaces. + for isBlank(parser.buffer, parser.buffer_pos) { + parser.skip() + if parser.unread < 1 { + if err := parser.updateBuffer(1); err != nil { + return err + } + } + } + + // Scan a prefix (TAG directive URI - flow indicators allowed). + if err := parser.scanTagURI(true, true, nil, start_mark, &prefix_value); err != nil { + return err + } + + // Expect a whitespace or line break. + if parser.unread < 1 { + if err := parser.updateBuffer(1); err != nil { + return err + } + } + if !isBlankOrZero(parser.buffer, parser.buffer_pos) { + return formatScannerErrorContext("while scanning a %TAG directive", start_mark, + "did not find expected whitespace or line break", parser.mark) + } + + *handle = handle_value + *prefix = prefix_value + return nil +} + +// scanAnchor scans an ANCHOR or ALIAS token. +func (parser *Parser) scanTagHandle(directive bool, start_mark Mark, handle *[]byte) error { + // Check the initial '!' character. + if parser.unread < 1 { + if err := parser.updateBuffer(1); err != nil { + return err + } + } + if parser.buffer[parser.buffer_pos] != '!' { + return parser.setScannerTagError(directive, + start_mark, "did not find expected '!'") + } + + var s []byte + + // Copy the '!' character. + s = parser.read(s) + + // Copy all subsequent alphabetical and numerical characters. + if parser.unread < 1 { + if err := parser.updateBuffer(1); err != nil { + return err + } + } + for isAlpha(parser.buffer, parser.buffer_pos) { + s = parser.read(s) + if parser.unread < 1 { + if err := parser.updateBuffer(1); err != nil { + return err + } + } + } + + // Check if the trailing character is '!' and copy it. + if parser.buffer[parser.buffer_pos] == '!' { + s = parser.read(s) + } else { + // It's either the '!' tag or not really a tag handle. + // If it's a %TAG directive, it's an error. + // If it's a tag token, it must be a part of URI. + if directive && string(s) != "!" { + return parser.setScannerTagError(directive, + start_mark, "did not find expected '!'") + } + } + + *handle = s + return nil +} + +// Scan a tag URI. +// directive: true if scanning a %TAG directive URI +// verbatim: true if scanning a verbatim tag !<...> or TAG directive (flow indicators allowed) +func (parser *Parser) scanTagURI(directive bool, verbatim bool, head []byte, start_mark Mark, uri *[]byte) error { + // size_t length = head ? strlen((char *)head) : 0 + var s []byte + hasTag := len(head) > 0 + + // Copy the head if needed. + // + // Note that we don't copy the leading '!' character. + if len(head) > 1 { + s = append(s, head[1:]...) + } + + // Scan the tag. + if parser.unread < 1 { + if err := parser.updateBuffer(1); err != nil { + return err + } + } + + // The set of characters that may appear in URI is as follows: + // + // '0'-'9', 'A'-'Z', 'a'-'z', '_', '-', ';', '/', '?', ':', '@', + // '&', '=', '+', '$', '.', '!', '~', '*', '\'', '(', ')', '%'. + // + // Note: Flow indicators (',', '[', ']', '{', '}') are only allowed in + // verbatim tags. + for isTagURIChar(parser.buffer, parser.buffer_pos, verbatim) { + // Check if it is a URI-escape sequence. + if parser.buffer[parser.buffer_pos] == '%' { + if err := parser.scanURIEscapes(directive, start_mark, &s); err != nil { + return err + } + } else { + s = parser.read(s) + } + if parser.unread < 1 { + if err := parser.updateBuffer(1); err != nil { + return err + } + } + hasTag = true + } + + // Check for characters which are not allowed in tags. + // For non-verbatim tags, if we stopped at a printable character that + // isn't whitespace, it's an invalid tag character - give a specific + // error. + // For verbatim tags, the caller will check for the expected '>' + // delimiter. + if !verbatim { + c := parser.buffer[parser.buffer_pos] + if !isBlankOrZero(parser.buffer, parser.buffer_pos) && + c >= 0x20 && c <= 0x7E { + return parser.setScannerTagError(directive, start_mark, + fmt.Sprintf("found character '%c' that is not allowed in a YAML tag", c)) + } + } + + if !hasTag { + return parser.setScannerTagError(directive, + start_mark, "did not find expected tag URI") + } + *uri = s + return nil +} + +// Decode an URI-escape sequence corresponding to a single UTF-8 character. +func (parser *Parser) scanToNextToken() error { + scan_mark := parser.mark + + // Until the next token is not found. + for { + // Allow the BOM mark to start a line. + if parser.unread < 1 { + if err := parser.updateBuffer(1); err != nil { + return err + } + } + if parser.mark.Column == 1 && isBOM(parser.buffer, parser.buffer_pos) { + parser.skip() + } + + // Eat whitespaces. + // Tabs are allowed: + // - in the flow context + // - in the block context, but not at the beginning of the line or + // after '-', '?', or ':' (complex value). + if parser.unread < 1 { + if err := parser.updateBuffer(1); err != nil { + return err + } + } + + for parser.buffer[parser.buffer_pos] == ' ' || ((parser.flow_level > 0 || !parser.simple_key_allowed) && parser.buffer[parser.buffer_pos] == '\t') { + parser.skip() + if parser.unread < 1 { + if err := parser.updateBuffer(1); err != nil { + return err + } + } + } + + // Check if we just had a line comment under a sequence entry that + // looks more like a header to the following content. Similar to this: + // + // - # The comment + // - Some data + // + // If so, transform the line comment to a head comment and reposition. + if len(parser.comments) > 0 && len(parser.tokens) > 1 { + tokenA := parser.tokens[len(parser.tokens)-2] + tokenB := parser.tokens[len(parser.tokens)-1] + comment := &parser.comments[len(parser.comments)-1] + if tokenA.Type == BLOCK_SEQUENCE_START_TOKEN && tokenB.Type == BLOCK_ENTRY_TOKEN && len(comment.Line) > 0 && !isLineBreak(parser.buffer, parser.buffer_pos) { + // If it was in the prior line, reposition so it becomes a + // header of the follow up token. Otherwise, keep it in place + // so it becomes a header of the former. + comment.Head = comment.Line + comment.Line = nil + if comment.StartMark.Line == parser.mark.Line-1 { + comment.TokenMark = parser.mark + } + } + } + + // Eat a comment until a line break. + if parser.buffer[parser.buffer_pos] == '#' { + if parser.skip_comments { + // Skip comment without storing it + for !isLineBreak(parser.buffer, parser.buffer_pos) && !isZeroChar(parser.buffer, parser.buffer_pos) { + parser.skip() + if parser.unread < 1 { + if err := parser.updateBuffer(1); err != nil { + return err + } + } + } + } else { + if err := parser.scanComments(scan_mark); err != nil { + return err + } + } + } + + // If it is a line break, eat it. + if isLineBreak(parser.buffer, parser.buffer_pos) { + if parser.unread < 2 { + if err := parser.updateBuffer(2); err != nil { + return err + } + } + parser.skipLine() + + // In the block context, a new line may start a simple key. + if parser.flow_level == 0 { + parser.simple_key_allowed = true + } + } else { + break // We have found a token. + } + } + + return nil +} + +// Scan a YAML-DIRECTIVE or TAG-DIRECTIVE token. +// +// Scope: +// +// %YAML 1.1 # a comment \n +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +// %TAG !yaml! tag:yaml.org,2002: \n +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +func (parser *Parser) scanURIEscapes(directive bool, start_mark Mark, s *[]byte) error { + // Decode the required number of characters. + w := 1024 + for w > 0 { + // Check for a URI-escaped octet. + if parser.unread < 3 { + if err := parser.updateBuffer(3); err != nil { + return err + } + } + + if !(parser.buffer[parser.buffer_pos] == '%' && + isHex(parser.buffer, parser.buffer_pos+1) && + isHex(parser.buffer, parser.buffer_pos+2)) { + return parser.setScannerTagError(directive, + start_mark, "did not find URI escaped octet") + } + + // Get the octet. + octet := byte((asHex(parser.buffer, parser.buffer_pos+1) << 4) + asHex(parser.buffer, parser.buffer_pos+2)) + + // If it is the leading octet, determine the length of the + // UTF-8 sequence. + if w == 1024 { + w = width(octet) + if w == 0 { + return parser.setScannerTagError(directive, + start_mark, "found an incorrect leading UTF-8 octet") + } + } else { + // Check if the trailing octet is correct. + if octet&0xC0 != 0x80 { + return parser.setScannerTagError(directive, + start_mark, "found an incorrect trailing UTF-8 octet") + } + } + + // Copy the octet and move the pointers. + *s = append(*s, octet) + parser.skip() + parser.skip() + parser.skip() + w-- + } + return nil +} + +// Scan a block scalar. +func (parser *Parser) scanVersionDirectiveNumber(start_mark Mark, number *int8) error { + // Repeat while the next character is digit. + if parser.unread < 1 { + if err := parser.updateBuffer(1); err != nil { + return err + } + } + var value, length int8 + for isDigit(parser.buffer, parser.buffer_pos) { + // Check if the number is too long. + length++ + if length > max_number_length { + return formatScannerErrorContext("while scanning a %YAML directive", start_mark, + "found extremely long version number", parser.mark) + } + value = value*10 + int8(asDigit(parser.buffer, parser.buffer_pos)) + parser.skip() + if parser.unread < 1 { + if err := parser.updateBuffer(1); err != nil { + return err + } + } + } + + // Check if the number was present. + if length == 0 { + return formatScannerErrorContext("while scanning a %YAML directive", start_mark, + "did not find expected version number", parser.mark) + } + *number = value + return nil +} + +// Scan the value of a TAG-DIRECTIVE token. +// +// Scope: +// +// %TAG !yaml! tag:yaml.org,2002: \n +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +func (parser *Parser) scanVersionDirectiveValue(start_mark Mark, major, minor *int8) error { + // Eat whitespaces. + if parser.unread < 1 { + if err := parser.updateBuffer(1); err != nil { + return err + } + } + for isBlank(parser.buffer, parser.buffer_pos) { + parser.skip() + if parser.unread < 1 { + if err := parser.updateBuffer(1); err != nil { + return err + } + } + } + + // Consume the major version number. + if err := parser.scanVersionDirectiveNumber(start_mark, major); err != nil { + return err + } + + // Eat '.'. + if parser.buffer[parser.buffer_pos] != '.' { + return formatScannerErrorContext("while scanning a %YAML directive", start_mark, + "did not find expected digit or '.' character", parser.mark) + } + + parser.skip() + + // Consume the minor version number. + if err := parser.scanVersionDirectiveNumber(start_mark, minor); err != nil { + return err + } + return nil +} + +// max_number_length limits the length of version number components in +// %YAML directives. +func (parser *Parser) saveSimpleKey() error { + // A simple key is required at the current position if the scanner is in + // the block context and the current column coincides with the indentation + // level. + + required := parser.flow_level == 0 && parser.indent == parser.mark.Column + + // + // If the current position may start a simple key, save it. + // + if parser.simple_key_allowed { + if err := parser.removeSimpleKey(); err != nil { + return err + } + + parser.simple_key_possible = true + parser.simple_key = SimpleKey{ + required: required, + flow_level: parser.flow_level, + token_number: parser.tokens_parsed + (len(parser.tokens) - parser.tokens_head), + mark: parser.mark, + } + } + return nil +} + +// Remove a potential simple key at the current flow level. +func (parser *Parser) removeSimpleKey() error { + // If the key is required, it is an error. + if parser.simple_key.required { + return formatScannerErrorContext( + "while scanning a simple key", parser.simple_key.mark, + "could not find expected ':'", parser.mark) + } + + parser.simple_key_possible = false // disable the key + return nil +} + +func (parser *Parser) isFlowSequence() bool { + if len(parser.tokens) == 0 { + return false + } + previousToken := parser.tokens[len(parser.tokens)-1] + return previousToken.Type == FLOW_ENTRY_TOKEN || previousToken.Type == FLOW_SEQUENCE_START_TOKEN +} + +// Check if a simple key may start at the current position and add it if +// needed. +func (parser *Parser) increaseFlowLevel() error { + // Increase the flow level. + parser.flow_level++ + if err := parser.depthCheck(parser.flow_level, &DepthContext{Kind: DepthKindFlow}); err != nil { + return formatScannerErrorContext( + "while increasing flow level", parser.simple_key.mark, + err.Error(), parser.mark) + } + + // If a simple key was possible, push it to the stack before resetting the key. + if parser.simple_key_possible { + parser.simple_key_stack = append(parser.simple_key_stack, parser.simple_key) + } + + // Reset the simple key for the new flow level. + parser.simple_key = SimpleKey{} + + return nil +} + +// Decrease the flow level. +func (parser *Parser) decreaseFlowLevel() error { + if parser.flow_level > 0 { + parser.flow_level-- + + if len(parser.simple_key_stack) == 0 { + return nil + } + + last := len(parser.simple_key_stack) - 1 + if parser.simple_key_stack[last].flow_level == parser.flow_level { + parser.simple_key = parser.simple_key_stack[last] // use last item + parser.simple_key_stack = parser.simple_key_stack[:last] // remove last item + parser.simple_key_possible = true // enable the key + } + } + return nil +} + +func (parser *Parser) rollIndent(column, number int, typ TokenType, mark Mark) error { + // In the flow context, do nothing. + if parser.flow_level > 0 { + return nil + } + + if parser.indent < column { + // Push the current indentation level to the stack and set the new + // indentation level. + parser.indents = append(parser.indents, parser.indent) + parser.indent = column + if err := parser.depthCheck(len(parser.indents), &DepthContext{Kind: DepthKindBlock}); err != nil { + return formatScannerErrorContext( + "while increasing indent level", parser.simple_key.mark, + err.Error(), parser.mark) + } + + // Create a token and insert it into the queue. + token := Token{ + Type: typ, + StartMark: mark, + EndMark: mark, + } + if number > -1 { + number -= parser.tokens_parsed + } + parser.insertToken(number, &token) + } + return nil +} + +// Pop indentation levels from the indents stack until the current level +// becomes less or equal to the column. For each indentation level, append +// the BLOCK-END token. +func (parser *Parser) unrollIndent(column int, scan_mark Mark) error { + // In the flow context, do nothing. + if parser.flow_level > 0 { + return nil + } + + block_mark := scan_mark + block_mark.Index-- + + // Loop through the indentation levels in the stack. + for parser.indent > column { + + // [Go] Reposition the end token before potential following + // foot comments of parent blocks. For that, search + // backwards for recent comments that were at the same + // indent as the block that is ending now. + stop_index := block_mark.Index + for i := len(parser.comments) - 1; i >= 0; i-- { + comment := &parser.comments[i] + + if comment.EndMark.Index < stop_index { + // Don't go back beyond the start of the + // comment/whitespace scan, unless column < 0. + // If requested indent column is < 0, then the + // document is over and everything else is a + // foot anyway. + break + } + if comment.StartMark.Column == parser.indent+1 { + // This is a good match. + // But maybe there's a former comment at that + // same indent level, so keep searching. + block_mark = comment.StartMark + } + + // While the end of the former comment matches with + // the start of the following one, we know there's + // nothing in between and scanning is still safe. + stop_index = comment.ScanMark.Index + } + + // Create a token and append it to the queue. + token := Token{ + Type: BLOCK_END_TOKEN, + StartMark: block_mark, + EndMark: block_mark, + } + parser.insertToken(-1, &token) + + // Pop the indentation level. + parser.indent = parser.indents[len(parser.indents)-1] + parser.indents = parser.indents[:len(parser.indents)-1] + } + return nil +} + +// insertToken inserts a token into the queue at the specified position. +func (parser *Parser) insertToken(pos int, token *Token) { + // 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 +} + +// Initialize the scanner and produce the STREAM-START token. +func (parser *Parser) skip() { + if !isBlank(parser.buffer, parser.buffer_pos) { + parser.newlines = 0 + } + parser.mark.Index++ + parser.mark.Column++ + parser.unread-- + parser.buffer_pos += width(parser.buffer[parser.buffer_pos]) +} + +// skipLine advances the parser position past the current line break. +func (parser *Parser) skipLine() { + if isCRLF(parser.buffer, parser.buffer_pos) { + parser.mark.Index += 2 + parser.mark.Column = 1 + parser.mark.Line++ + parser.unread -= 2 + parser.buffer_pos += 2 + parser.newlines++ + } else if isLineBreak(parser.buffer, parser.buffer_pos) { + parser.mark.Index++ + parser.mark.Column = 1 + parser.mark.Line++ + parser.unread-- + parser.buffer_pos += width(parser.buffer[parser.buffer_pos]) + parser.newlines++ + } +} + +// Copy a character to a string buffer and advance pointers. +func (parser *Parser) read(s []byte) []byte { + if !isBlank(parser.buffer, parser.buffer_pos) { + parser.newlines = 0 + } + w := width(parser.buffer[parser.buffer_pos]) + if w == 0 { + panic("invalid character sequence") + } + if len(s) == 0 { + s = make([]byte, 0, 32) + } + if w == 1 && len(s)+w <= cap(s) { + s = s[:len(s)+1] + s[len(s)-1] = parser.buffer[parser.buffer_pos] + parser.buffer_pos++ + } else { + s = append(s, parser.buffer[parser.buffer_pos:parser.buffer_pos+w]...) + parser.buffer_pos += w + } + parser.mark.Index++ + parser.mark.Column++ + parser.unread-- + return s +} + +// Copy a line break character to a string buffer and advance pointers. +func (parser *Parser) readLine(s []byte) []byte { + buf := parser.buffer + pos := parser.buffer_pos + switch { + case buf[pos] == '\r' && buf[pos+1] == '\n': + // CR LF . LF + s = append(s, '\n') + parser.buffer_pos += 2 + parser.mark.Index++ + parser.unread-- + case buf[pos] == '\r' || buf[pos] == '\n': + // CR|LF . LF + s = append(s, '\n') + parser.buffer_pos += 1 + case buf[pos] == '\xC2' && buf[pos+1] == '\x85': + // NEL . LF + s = append(s, '\n') + parser.buffer_pos += 2 + case buf[pos] == '\xE2' && buf[pos+1] == '\x80' && (buf[pos+2] == '\xA8' || buf[pos+2] == '\xA9'): + // LS|PS . LS|PS + s = append(s, buf[parser.buffer_pos:pos+3]...) + parser.buffer_pos += 3 + default: + return s + } + parser.mark.Index++ + parser.mark.Column = 1 + parser.mark.Line++ + parser.unread-- + parser.newlines++ + return s +} + +// formatScannerError creates a LoadError for scanner-stage errors. +func formatScannerError(problem string, problemMark Mark) *LoadError { + return &LoadError{ + Stage: ScannerStage, + Mark: problemMark, + Message: problem, + } +} + +// formatScannerErrorContext creates a LoadError with both context and +// problem information, each with their own mark positions. +func formatScannerErrorContext(context string, contextMark Mark, problem string, problemMark Mark) *LoadError { + return &LoadError{ + Stage: ScannerStage, + ContextMark: contextMark, + ContextMsg: context, + Mark: problemMark, + Message: problem, + } +} + +// setScannerTagError creates a tag-related scanner error with appropriate +// context based on whether it's from a directive or tag parsing. +func (parser *Parser) setScannerTagError(directive bool, contextMark Mark, problem string) error { + context := "while parsing a tag" + if directive { + context = "while parsing a %TAG directive" + } + return formatScannerErrorContext(context, contextMark, problem, parser.mark) +} + +// trace is a debug utility that prints entry/exit messages for function calls. +func trace(args ...any) func() { + pargs := append([]any{"+++"}, args...) + fmt.Println(pargs...) + pargs = append([]any{"---"}, args...) + return func() { fmt.Println(pargs...) } +} + +// Ensure that the tokens queue contains at least one token which can be +// returned to the Parser. diff --git a/vendor/go.yaml.in/yaml/v4/internal/libyaml/serializer.go b/vendor/go.yaml.in/yaml/v4/internal/libyaml/serializer.go new file mode 100644 index 000000000..4914dce08 --- /dev/null +++ b/vendor/go.yaml.in/yaml/v4/internal/libyaml/serializer.go @@ -0,0 +1,422 @@ +// Copyright 2011-2019 Canonical Ltd +// Copyright 2025 The go-yaml Project Contributors +// SPDX-License-Identifier: Apache-2.0 + +// Serializer stage: Converts representation tree (Nodes) to event stream. +// Walks the node tree and produces events for the emitter. + +package libyaml + +import ( + "errors" + "fmt" + "io" + "strings" + "unicode/utf8" +) + +// Serializer handles serialization of YAML nodes to event stream. +type Serializer struct { + Emitter Emitter + out []byte + lineWidth int + explicitStart bool + explicitEnd bool + flowSimpleCollections bool + quotePreference QuoteStyle + doneInit bool +} + +// NewSerializer creates a new Serializer with the given options. +func NewSerializer(w io.Writer, opts *Options) *Serializer { + emitter := NewEmitter() + emitter.CompactSequenceIndent = opts.CompactSeqIndent + emitter.quotePreference = opts.QuotePreference + emitter.SetWidth(opts.LineWidth) + emitter.SetUnicode(opts.Unicode) + emitter.SetCanonical(opts.Canonical) + emitter.SetLineBreak(opts.LineBreak) + + // Set indentation (defaults to 2 if not specified) + indent := opts.Indent + if indent == 0 { + indent = 2 + } + emitter.BestIndent = indent + + if w != nil { + emitter.SetOutputWriter(w) + } + + return &Serializer{ + Emitter: emitter, + lineWidth: opts.LineWidth, + explicitStart: opts.ExplicitStart, + explicitEnd: opts.ExplicitEnd, + flowSimpleCollections: opts.FlowSimpleCollections, + quotePreference: opts.QuotePreference, + } +} + +// Serialize walks a Node tree and emits events to produce YAML output. +// This is the primary method for the Serializer stage. +func (s *Serializer) Serialize(node *Node) { + s.init() + s.node(node, "") +} + +// Sentinel values for event creation. +// These provide clarity at call sites, similar to http.NoBody. +var ( + noVersionDirective *VersionDirective = nil + noTagDirective []TagDirective = nil +) + +// init initializes the serializer by emitting a STREAM_START event. +func (s *Serializer) init() { + if s.doneInit { + return + } + s.emit(NewStreamStartEvent(UTF8_ENCODING)) + s.doneInit = true +} + +// Finish completes serialization by emitting a STREAM_END event. +func (s *Serializer) Finish() { + s.Emitter.OpenEnded = false + s.emit(NewStreamEndEvent()) +} + +// node serializes a Node tree into YAML events. +// This is the core of the serializer stage - it walks the tree and produces +// events. +func (s *Serializer) node(node *Node, tail string) { + // Zero nodes behave as nil. + if node.Kind == 0 && node.IsZero() { + s.emitScalar("null", "", "", PLAIN_SCALAR_STYLE, nil, nil, nil, nil) + return + } + + // Tags have been processed by Desolver: + // - Empty tag = can be inferred or style handles it + // - Non-empty tag = emit explicitly + // Style has also been set by Desolver for quoting needs + tag := node.Tag + var forceQuoting bool + if tag == "" && node.Kind == ScalarNode { + // Empty tag with quoting style means the string type needs to + // be preserved + if node.Style&(SingleQuotedStyle|DoubleQuotedStyle|LiteralStyle|FoldedStyle) != 0 { + forceQuoting = true + } + } + + switch node.Kind { + case DocumentNode: + event := NewDocumentStartEvent(noVersionDirective, noTagDirective, !s.explicitStart) + event.HeadComment = []byte(node.HeadComment) + s.emit(event) + for _, node := range node.Content { + s.node(node, "") + } + event = NewDocumentEndEvent(!s.explicitEnd) + event.FootComment = []byte(node.FootComment) + s.emit(event) + + case SequenceNode: + style := BLOCK_SEQUENCE_STYLE + // Use flow style if explicitly requested or if it's a simple + // collection (scalar-only contents that fit within line width, + // enabled via WithFlowSimpleCollections) + if node.Style&FlowStyle != 0 || s.isSimpleCollection(node) { + style = FLOW_SEQUENCE_STYLE + } + event := NewSequenceStartEvent([]byte(node.Anchor), []byte(longTag(tag)), tag == "", style) + event.HeadComment = []byte(node.HeadComment) + s.emit(event) + for _, node := range node.Content { + s.node(node, "") + } + event = NewSequenceEndEvent() + event.LineComment = []byte(node.LineComment) + event.FootComment = []byte(node.FootComment) + s.emit(event) + + case MappingNode: + style := BLOCK_MAPPING_STYLE + // Use flow style if explicitly requested or if it's a simple + // collection (scalar-only contents that fit within line width, + // enabled via WithFlowSimpleCollections) + if node.Style&FlowStyle != 0 || s.isSimpleCollection(node) { + style = FLOW_MAPPING_STYLE + } + event := NewMappingStartEvent([]byte(node.Anchor), []byte(longTag(tag)), tag == "", style) + event.TailComment = []byte(tail) + event.HeadComment = []byte(node.HeadComment) + s.emit(event) + + // 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 + } + s.node(k, tail) + tail = foot + + v := node.Content[i+1] + s.node(v, "") + } + + event = NewMappingEndEvent() + event.TailComment = []byte(tail) + event.LineComment = []byte(node.LineComment) + event.FootComment = []byte(node.FootComment) + s.emit(event) + + case AliasNode: + event := NewAliasEvent([]byte(node.Value)) + event.HeadComment = []byte(node.HeadComment) + event.LineComment = []byte(node.LineComment) + event.FootComment = []byte(node.FootComment) + s.emit(event) + + case ScalarNode: + value := node.Value + if !utf8.ValidString(value) { + stag := shortTag(tag) + if stag == binaryTag { + failDumpf(SerializerStage, "explicitly tagged !!binary data must be base64-encoded") + } + if stag != "" { + failDumpf(SerializerStage, "cannot marshal invalid UTF-8 data as %s", stag) + } + // It can't be represented directly as YAML so use a binary tag + // and represent it as base64. + tag = binaryTag + value = encodeBase64(value) + } + + style := PLAIN_SCALAR_STYLE + switch { + case node.Style&DoubleQuotedStyle != 0: + style = DOUBLE_QUOTED_SCALAR_STYLE + case node.Style&SingleQuotedStyle != 0: + style = SINGLE_QUOTED_SCALAR_STYLE + case node.Style&LiteralStyle != 0: + style = LITERAL_SCALAR_STYLE + case node.Style&FoldedStyle != 0: + style = FOLDED_SCALAR_STYLE + case strings.Contains(value, "\n"): + style = LITERAL_SCALAR_STYLE + case forceQuoting: + style = s.quotePreference.ScalarStyle() + } + + s.emitScalar(value, node.Anchor, tag, style, []byte(node.HeadComment), []byte(node.LineComment), []byte(node.FootComment), []byte(tail)) + default: + failDumpf(SerializerStage, "cannot represent node with unknown kind %d", node.Kind) + } +} + +// emit sends an event to the underlying emitter. +func (s *Serializer) emit(event Event) { + s.must(s.Emitter.Emit(&event)) +} + +// must panics if the given error is non-nil, routing to the appropriate stage. +func (s *Serializer) must(err error) { + if err == nil { + return + } + var ee EmitterError + if errors.As(err, &ee) { + failDumpf(EmitterStage, "%s", ee.Message) + } + var we WriterError + if errors.As(err, &we) { + // Unwrap to get the original I/O error, stripping the + // "write error: " prefix that WriterError adds internally. + cause := we.Err + if unwrapped := errors.Unwrap(we.Err); unwrapped != nil { + cause = unwrapped + } + failDump(WriterStage, cause) + } + msg := err.Error() + if msg == "" { + msg = fmt.Sprintf("unknown problem generating YAML content with %T", err) + } + failDumpf(SerializerStage, "%s", msg) +} + +// emitScalar emits a scalar event with the given value, anchor, tag, style, +// and associated comments. +func (s *Serializer) emitScalar( + value, anchor, tag string, style ScalarStyle, head, line, foot, tail []byte, +) { + implicit := tag == "" + if !implicit { + tag = longTag(tag) + } + event := NewScalarEvent([]byte(anchor), []byte(tag), []byte(value), implicit, implicit, style) + event.HeadComment = head + event.LineComment = line + event.FootComment = foot + event.TailComment = tail + s.emit(event) +} + +// isSimpleCollection checks if a node contains only scalar values and would +// fit within the line width when rendered in flow style. +func (s *Serializer) isSimpleCollection(node *Node) bool { + if !s.flowSimpleCollections { + return false + } + if node.Kind != SequenceNode && node.Kind != MappingNode { + return false + } + // Check all children are scalars + for _, child := range node.Content { + if child.Kind != ScalarNode { + return false + } + } + // Estimate flow style length + estimatedLen := s.estimateFlowLength(node) + width := s.lineWidth + if width <= 0 { + width = 80 // Default width if not set + } + return estimatedLen > 0 && estimatedLen <= width +} + +// estimateFlowLength estimates the character length of a node in flow style. +func (s *Serializer) estimateFlowLength(node *Node) int { + if node.Kind == SequenceNode { + // [item1, item2, ...] = 2 + sum(len(items)) + 2*(len-1) + length := 2 // [] + for i, child := range node.Content { + if i > 0 { + length += 2 // ", " + } + length += len(child.Value) + } + return length + } + if node.Kind == MappingNode { + // {key1: val1, key2: val2} = 2 + sum(key: val) + 2*(pairs-1) + length := 2 // {} + for i := 0; i < len(node.Content); i += 2 { + if i > 0 { + length += 2 // ", " + } + length += len(node.Content[i].Value) + 2 + len(node.Content[i+1].Value) // "key: val" + } + return length + } + return 0 +} + +// NewStreamStartEvent creates a new STREAM-START event. +func NewStreamStartEvent(encoding Encoding) Event { + return Event{ + Type: STREAM_START_EVENT, + encoding: encoding, + } +} + +// NewStreamEndEvent creates a new STREAM-END event. +func NewStreamEndEvent() Event { + return Event{ + Type: STREAM_END_EVENT, + } +} + +// NewDocumentStartEvent creates a new DOCUMENT-START event. +func NewDocumentStartEvent(version_directive *VersionDirective, tag_directives []TagDirective, implicit bool) Event { + return Event{ + Type: DOCUMENT_START_EVENT, + versionDirective: version_directive, + tagDirectives: tag_directives, + Implicit: implicit, + } +} + +// NewDocumentEndEvent creates a new DOCUMENT-END event. +func NewDocumentEndEvent(implicit bool) Event { + return Event{ + Type: DOCUMENT_END_EVENT, + Implicit: implicit, + } +} + +// NewAliasEvent creates a new ALIAS event. +func NewAliasEvent(anchor []byte) Event { + return Event{ + Type: ALIAS_EVENT, + Anchor: anchor, + } +} + +// NewScalarEvent creates a new SCALAR event. +func NewScalarEvent(anchor, tag, value []byte, plain_implicit, quoted_implicit bool, style ScalarStyle) Event { + return Event{ + Type: SCALAR_EVENT, + Anchor: anchor, + Tag: tag, + Value: value, + Implicit: plain_implicit, + quoted_implicit: quoted_implicit, + Style: Style(style), + } +} + +// NewSequenceStartEvent creates a new SEQUENCE-START event. +func NewSequenceStartEvent(anchor, tag []byte, implicit bool, style SequenceStyle) Event { + return Event{ + Type: SEQUENCE_START_EVENT, + Anchor: anchor, + Tag: tag, + Implicit: implicit, + Style: Style(style), + } +} + +// NewSequenceEndEvent creates a new SEQUENCE-END event. +func NewSequenceEndEvent() Event { + return Event{ + Type: SEQUENCE_END_EVENT, + } +} + +// NewMappingStartEvent creates a new MAPPING-START event. +func NewMappingStartEvent(anchor, tag []byte, implicit bool, style MappingStyle) Event { + return Event{ + Type: MAPPING_START_EVENT, + Anchor: anchor, + Tag: tag, + Implicit: implicit, + Style: Style(style), + } +} + +// NewMappingEndEvent creates a new MAPPING-END event. +func NewMappingEndEvent() Event { + return Event{ + Type: MAPPING_END_EVENT, + } +} + +// Delete an event object. +func (e *Event) Delete() { + *e = Event{} +} diff --git a/vendor/go.yaml.in/yaml/v4/internal/libyaml/structmeta.go b/vendor/go.yaml.in/yaml/v4/internal/libyaml/structmeta.go new file mode 100644 index 000000000..70c663097 --- /dev/null +++ b/vendor/go.yaml.in/yaml/v4/internal/libyaml/structmeta.go @@ -0,0 +1,250 @@ +// Copyright 2011-2019 Canonical Ltd +// Copyright 2025 The go-yaml Project Contributors +// SPDX-License-Identifier: Apache-2.0 + +// Struct metadata extraction for YAML marshaling/unmarshaling. +// +// This file analyzes Go struct types to build mappings between YAML keys and +// struct fields. It parses struct tags like `yaml:"name,omitempty,flow,inline"` +// and caches the results for efficient repeated access. +// +// Used by: +// - Constructor: maps YAML keys to struct fields when unmarshaling +// - Representer: maps struct fields to YAML keys when marshaling +// +// Key types: +// - structInfo: cached metadata about a struct type +// - fieldInfo: metadata about a single struct field +// - getStructInfo(): analyzes a struct type and returns cached metadata + +package libyaml + +import ( + "errors" + "fmt" + "reflect" + "strings" + "sync" +) + +// structInfo holds cached information about a struct's YAML-relevant fields. +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 + + // InlineConstructors holds indexes to inlined fields that + // contain constructor values. + InlineConstructors [][]int +} + +// fieldInfo holds information about a single struct field. +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 +} + +// structMap caches struct reflection information. +// fieldMapMutex protects access to structMap. +// constructorType holds the [reflect.Type] for the constructor interface. +var ( + structMap = make(map[reflect.Type]*structInfo) + fieldMapMutex sync.RWMutex + constructorType reflect.Type +) + +// constructor interface is defined here to detect types that implement +// UnmarshalYAML during struct reflection. +type constructor interface { + UnmarshalYAML(value *Node) error +} + +// init initializes the constructorType variable with the [reflect.Type] of constructor interface. +func init() { + var v constructor + constructorType = reflect.ValueOf(&v).Elem().Type() +} + +// hasConstructYAMLMethod checks if a type has an UnmarshalYAML method +// that takes a *Node from an allowlisted v3 yaml package. This detects +// v3 backward-compatible Unmarshaler implementations whose Node type +// can't be checked via interface assertion from this package. +func hasConstructYAMLMethod(t reflect.Type) bool { + method, found := t.MethodByName("UnmarshalYAML") + if !found { + return false + } + + // Check signature: func(*T) UnmarshalYAML(*Node) error + mtype := method.Type + if mtype.NumIn() != 2 || mtype.NumOut() != 1 { + return false + } + + // First param is receiver (already checked by MethodByName) + // Second param should be a pointer to a Node-like struct + paramType := mtype.In(1) + if paramType.Kind() != reflect.Ptr { + return false + } + + elemType := paramType.Elem() + if elemType.Kind() != reflect.Struct || elemType.Name() != "Node" || !isYAMLNodePkg(elemType.PkgPath()) { + return false + } + + // Return type should be error + retType := mtype.Out(0) + if retType.Kind() != reflect.Interface || retType.Name() != "error" { + return false + } + + return true +} + +func isYAMLNodePkg(pkg string) bool { + switch pkg { + case "gopkg.in/yaml.v3", "go.yaml.in/yaml/v3": + return true + } + return false +} + +// getStructInfo returns cached information about a struct type's fields. +// It parses struct tags and builds a map of field names to field info. +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 + inlineConstructors := [][]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.Contains(string(field.Tag), ":") { + 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, fmt.Errorf("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.Pointer: + ftype := field.Type + for ftype.Kind() == reflect.Pointer { + ftype = ftype.Elem() + } + if ftype.Kind() != reflect.Struct { + return nil, errors.New("option ,inline may only be used on a struct or map field") + } + // Check for both libyaml.constructor and yaml.Unmarshaler (by method name) + if reflect.PointerTo(ftype).Implements(constructorType) || hasConstructYAMLMethod(reflect.PointerTo(ftype)) { + inlineConstructors = append(inlineConstructors, []int{i}) + } else { + sinfo, err := getStructInfo(ftype) + if err != nil { + return nil, err + } + for _, index := range sinfo.InlineConstructors { + inlineConstructors = append(inlineConstructors, 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, + InlineConstructors: inlineConstructors, + } + + fieldMapMutex.Lock() + structMap[st] = sinfo + fieldMapMutex.Unlock() + return sinfo, nil +} diff --git a/vendor/go.yaml.in/yaml/v4/internal/libyaml/writer.go b/vendor/go.yaml.in/yaml/v4/internal/libyaml/writer.go new file mode 100644 index 000000000..dcd51d6ce --- /dev/null +++ b/vendor/go.yaml.in/yaml/v4/internal/libyaml/writer.go @@ -0,0 +1,31 @@ +// Copyright 2006-2010 Kirill Simonov +// Copyright 2011-2019 Canonical Ltd +// Copyright 2025 The go-yaml Project Contributors +// SPDX-License-Identifier: Apache-2.0 AND MIT + +// Output writer with buffering. +// Provides write buffering for the emitter stage. + +package libyaml + +import "fmt" + +// Flush the output buffer. +func (emitter *Emitter) flush() error { + if emitter.write_handler == nil { + panic("write handler not set") + } + + // Check if the buffer is empty. + if emitter.buffer_pos == 0 { + return nil + } + + if err := emitter.write_handler(emitter, emitter.buffer[:emitter.buffer_pos]); err != nil { + return WriterError{ + Err: fmt.Errorf("write error: %w", err), + } + } + emitter.buffer_pos = 0 + return nil +} diff --git a/vendor/go.yaml.in/yaml/v4/internal/libyaml/yaml.go b/vendor/go.yaml.in/yaml/v4/internal/libyaml/yaml.go new file mode 100644 index 000000000..04a401a33 --- /dev/null +++ b/vendor/go.yaml.in/yaml/v4/internal/libyaml/yaml.go @@ -0,0 +1,476 @@ +// Copyright 2006-2010 Kirill Simonov +// Copyright 2011-2019 Canonical Ltd +// Copyright 2025 The go-yaml Project Contributors +// SPDX-License-Identifier: Apache-2.0 AND MIT + +// Core libyaml types and structures. +// Defines Parser, Emitter, Event, Token, and related constants for YAML +// processing. + +package libyaml + +import ( + "fmt" + "strings" +) + +// VersionDirective holds the YAML version directive data. +type VersionDirective struct { + major int8 // The major version number. + minor int8 // The minor version number. +} + +// Major returns the major version number. +func (v *VersionDirective) Major() int { return int(v.major) } + +// Minor returns the minor version number. +func (v *VersionDirective) Minor() int { return int(v.minor) } + +// TagDirective holds the YAML tag directive data. +type TagDirective struct { + handle []byte // The tag handle. + prefix []byte // The tag prefix. +} + +// GetHandle returns the tag handle. +func (t *TagDirective) GetHandle() string { return string(t.handle) } + +// GetPrefix returns the tag prefix. +func (t *TagDirective) GetPrefix() string { return string(t.prefix) } + +// Encoding represents the character encoding of a YAML stream. +type Encoding int + +// The stream encoding. +const ( + // Let the parser choose the encoding. + ANY_ENCODING Encoding = iota + + UTF8_ENCODING // The default UTF-8 encoding. + UTF16LE_ENCODING // The UTF-16-LE encoding with BOM. + UTF16BE_ENCODING // The UTF-16-BE encoding with BOM. +) + +// LineBreak represents the line break style used in YAML output. +type LineBreak int + +// Line break types. +const ( + // Let the parser choose the break type. + ANY_BREAK LineBreak = iota + + CR_BREAK // Use CR for line breaks (Mac style). + LN_BREAK // Use LN for line breaks (Unix style). + CRLN_BREAK // Use CR LN for line breaks (DOS style). +) + +// QuoteStyle represents the preferred quote style for scalar values. +type QuoteStyle int + +// Quote style types for required quoting. +const ( + QuoteSingle QuoteStyle = iota // Prefer single quotes when quoting is required. + QuoteDouble // Prefer double quotes when quoting is required. + QuoteLegacy // Legacy behavior: double in representer, single in emitter. +) + +// ScalarStyle returns the scalar style for this quote preference in the +// representer/serializer context. +// In this context, both QuoteDouble and QuoteLegacy use double quotes. +func (q QuoteStyle) ScalarStyle() ScalarStyle { + if q == QuoteDouble || q == QuoteLegacy { + return DOUBLE_QUOTED_SCALAR_STYLE + } + return SINGLE_QUOTED_SCALAR_STYLE +} + +// ErrorType represents the category of error that occurred during processing. +type ErrorType int + +// Many bad things could happen with the parser and emitter. +const ( + // No error is produced. + NO_ERROR ErrorType = iota + + MEMORY_ERROR // Cannot allocate or reallocate a block of memory. + READER_ERROR // Cannot read or decode the input stream. + SCANNER_ERROR // Cannot scan the input stream. + PARSER_ERROR // Cannot parse the input stream. + COMPOSER_ERROR // Cannot compose a YAML document. + WRITER_ERROR // Cannot write to the output stream. + EMITTER_ERROR // Cannot emit a YAML stream. +) + +// Mark holds the pointer position. +type Mark struct { + Index int // The position index. + Line int // The position line (1-indexed; 0 means unknown). + Column int // The position column (1-indexed; 0 means unknown). +} + +// String returns a human-readable string representation of the position mark. +func (m Mark) String() string { + var builder strings.Builder + if m.Line == 0 { + return "" + } + + fmt.Fprintf(&builder, "line %d", m.Line) + if m.Column > 0 { + fmt.Fprintf(&builder, ", column %d", m.Column) + } + + return builder.String() +} + +// shortString returns a compact position string. +// Returns "" when Line is 0 (position not known). +// When Column is 0 (unknown), it is omitted from output ("L{line}"); +// otherwise it is displayed as "L{line}.C{col}". +func (m Mark) shortString() string { + if m.Line == 0 { + return "" + } + if m.Column > 0 { + return fmt.Sprintf("L%d.C%d", m.Line, m.Column) + } + return fmt.Sprintf("L%d", m.Line) +} + +// rangeString formats a position range from start mark m to end mark. +// Both marks use shortString for their individual display. +// When marks are on the same line: +// - Both Column==0: just "L2" (unknown columns, no range shown) +// - Both Column>0: "L2.C6-C7" (compact column range) +// - Mixed columns: "L1.C4-L1" (full start with line-only end) +// +// When marks are on different lines: "L1.C8-L2.C3" +func (m Mark) rangeString(end Mark) string { + start := m.shortString() + if m.Line == end.Line { + if m.Column == 0 && end.Column == 0 { + // Same line, unknown columns: just "L2" + return start + } + if m.Column > 0 && end.Column > 0 { + if m.Column == end.Column { + // Same position: just "L2.C6" + return start + } + // Same line with columns: "L2.C6-C7" + return fmt.Sprintf("%s-C%d", start, end.Column) + } + } + return fmt.Sprintf("%s-%s", start, end.shortString()) +} + +// Node Styles + +// styleInt is the underlying type for style constants. +type styleInt int8 + +// ScalarStyle represents the formatting style of a scalar value. +type ScalarStyle styleInt + +// Scalar styles. +const ( + // Let the emitter choose the style. + ANY_SCALAR_STYLE ScalarStyle = 0 + + PLAIN_SCALAR_STYLE ScalarStyle = 1 << iota // The plain scalar style. + SINGLE_QUOTED_SCALAR_STYLE // The single-quoted scalar style. + DOUBLE_QUOTED_SCALAR_STYLE // The double-quoted scalar style. + LITERAL_SCALAR_STYLE // The literal scalar style. + FOLDED_SCALAR_STYLE // The folded scalar style. +) + +// String returns a string representation of a [ScalarStyle]. +func (style ScalarStyle) String() string { + switch style { + case PLAIN_SCALAR_STYLE: + return "Plain" + case SINGLE_QUOTED_SCALAR_STYLE: + return "Single" + case DOUBLE_QUOTED_SCALAR_STYLE: + return "Double" + case LITERAL_SCALAR_STYLE: + return "Literal" + case FOLDED_SCALAR_STYLE: + return "Folded" + default: + return "" + } +} + +// SequenceStyle represents the formatting style of a sequence node. +type SequenceStyle styleInt + +// Sequence styles. +const ( + // Let the emitter choose the style. + ANY_SEQUENCE_STYLE SequenceStyle = iota + + BLOCK_SEQUENCE_STYLE // The block sequence style. + FLOW_SEQUENCE_STYLE // The flow sequence style. +) + +// MappingStyle represents the formatting style of a mapping node. +type MappingStyle styleInt + +// Mapping styles. +const ( + // Let the emitter choose the style. + ANY_MAPPING_STYLE MappingStyle = iota + + BLOCK_MAPPING_STYLE // The block mapping style. + FLOW_MAPPING_STYLE // The flow mapping style. +) + +// Tokens + +// TokenType represents the type of a scanned token. +type TokenType int + +// Token types. +const ( + // An empty token. + NO_TOKEN TokenType = iota + + STREAM_START_TOKEN // A STREAM-START token. + STREAM_END_TOKEN // A STREAM-END token. + + VERSION_DIRECTIVE_TOKEN // A VERSION-DIRECTIVE token. + TAG_DIRECTIVE_TOKEN // A TAG-DIRECTIVE token. + DOCUMENT_START_TOKEN // A DOCUMENT-START token. + DOCUMENT_END_TOKEN // A DOCUMENT-END token. + + BLOCK_SEQUENCE_START_TOKEN // A BLOCK-SEQUENCE-START token. + BLOCK_MAPPING_START_TOKEN // A BLOCK-SEQUENCE-END token. + BLOCK_END_TOKEN // A BLOCK-END token. + + FLOW_SEQUENCE_START_TOKEN // A FLOW-SEQUENCE-START token. + FLOW_SEQUENCE_END_TOKEN // A FLOW-SEQUENCE-END token. + FLOW_MAPPING_START_TOKEN // A FLOW-MAPPING-START token. + FLOW_MAPPING_END_TOKEN // A FLOW-MAPPING-END token. + + BLOCK_ENTRY_TOKEN // A BLOCK-ENTRY token. + FLOW_ENTRY_TOKEN // A FLOW-ENTRY token. + KEY_TOKEN // A KEY token. + VALUE_TOKEN // A VALUE token. + + ALIAS_TOKEN // An ALIAS token. + ANCHOR_TOKEN // An ANCHOR token. + TAG_TOKEN // A TAG token. + SCALAR_TOKEN // A SCALAR token. + COMMENT_TOKEN // A COMMENT token. +) + +// String returns a string representation of the token type. +func (tt TokenType) String() string { + switch tt { + case NO_TOKEN: + return "NO_TOKEN" + case STREAM_START_TOKEN: + return "STREAM_START_TOKEN" + case STREAM_END_TOKEN: + return "STREAM_END_TOKEN" + case VERSION_DIRECTIVE_TOKEN: + return "VERSION_DIRECTIVE_TOKEN" + case TAG_DIRECTIVE_TOKEN: + return "TAG_DIRECTIVE_TOKEN" + case DOCUMENT_START_TOKEN: + return "DOCUMENT_START_TOKEN" + case DOCUMENT_END_TOKEN: + return "DOCUMENT_END_TOKEN" + case BLOCK_SEQUENCE_START_TOKEN: + return "BLOCK_SEQUENCE_START_TOKEN" + case BLOCK_MAPPING_START_TOKEN: + return "BLOCK_MAPPING_START_TOKEN" + case BLOCK_END_TOKEN: + return "BLOCK_END_TOKEN" + case FLOW_SEQUENCE_START_TOKEN: + return "FLOW_SEQUENCE_START_TOKEN" + case FLOW_SEQUENCE_END_TOKEN: + return "FLOW_SEQUENCE_END_TOKEN" + case FLOW_MAPPING_START_TOKEN: + return "FLOW_MAPPING_START_TOKEN" + case FLOW_MAPPING_END_TOKEN: + return "FLOW_MAPPING_END_TOKEN" + case BLOCK_ENTRY_TOKEN: + return "BLOCK_ENTRY_TOKEN" + case FLOW_ENTRY_TOKEN: + return "FLOW_ENTRY_TOKEN" + case KEY_TOKEN: + return "KEY_TOKEN" + case VALUE_TOKEN: + return "VALUE_TOKEN" + case ALIAS_TOKEN: + return "ALIAS_TOKEN" + case ANCHOR_TOKEN: + return "ANCHOR_TOKEN" + case TAG_TOKEN: + return "TAG_TOKEN" + case SCALAR_TOKEN: + return "SCALAR_TOKEN" + case COMMENT_TOKEN: + return "COMMENT_TOKEN" + } + return "" +} + +// Token holds information about a scanning token. +type Token struct { + // The token type. + Type TokenType + + // The start/end of the token. + StartMark, EndMark Mark + + // The stream encoding (for STREAM_START_TOKEN). + encoding Encoding + + // The alias/anchor/scalar Value or tag/tag directive handle + // (for ALIAS_TOKEN, ANCHOR_TOKEN, SCALAR_TOKEN, TAG_TOKEN, TAG_DIRECTIVE_TOKEN). + Value []byte + + // The tag suffix (for TAG_TOKEN). + suffix []byte + + // The tag directive prefix (for TAG_DIRECTIVE_TOKEN). + prefix []byte + + // The scalar Style (for SCALAR_TOKEN). + Style ScalarStyle + + // The version directive major/minor (for VERSION_DIRECTIVE_TOKEN). + major, minor int8 +} + +// Events + +// EventType represents the type of a parsing or emitting event. +type EventType int8 + +// Event types. +const ( + // An empty event. + NO_EVENT EventType = iota + + STREAM_START_EVENT // A STREAM-START event. + STREAM_END_EVENT // A STREAM-END event. + DOCUMENT_START_EVENT // A DOCUMENT-START event. + DOCUMENT_END_EVENT // A DOCUMENT-END event. + ALIAS_EVENT // An ALIAS event. + SCALAR_EVENT // A SCALAR event. + SEQUENCE_START_EVENT // A SEQUENCE-START event. + SEQUENCE_END_EVENT // A SEQUENCE-END event. + MAPPING_START_EVENT // A MAPPING-START event. + MAPPING_END_EVENT // A MAPPING-END event. + TAIL_COMMENT_EVENT +) + +// eventStrings maps EventType constants to their string representations. +var eventStrings = []string{ + NO_EVENT: "none", + STREAM_START_EVENT: "stream start", + STREAM_END_EVENT: "stream end", + DOCUMENT_START_EVENT: "document start", + DOCUMENT_END_EVENT: "document end", + ALIAS_EVENT: "alias", + SCALAR_EVENT: "scalar", + SEQUENCE_START_EVENT: "sequence start", + SEQUENCE_END_EVENT: "sequence end", + MAPPING_START_EVENT: "mapping start", + MAPPING_END_EVENT: "mapping end", + TAIL_COMMENT_EVENT: "tail comment", +} + +// String returns a string representation of the event type. +func (e EventType) String() string { + if e < 0 || int(e) >= len(eventStrings) { + return fmt.Sprintf("unknown event %d", e) + } + return eventStrings[e] +} + +// Event holds information about a parsing or emitting event. +type Event struct { + // The event type. + Type EventType + + // The start and end of the event. + StartMark, EndMark Mark + + // The document encoding (for STREAM_START_EVENT). + encoding Encoding + + // The version directive (for DOCUMENT_START_EVENT). + versionDirective *VersionDirective + + // The list of tag directives (for DOCUMENT_START_EVENT). + tagDirectives []TagDirective + + // The comments + HeadComment []byte + LineComment []byte + FootComment []byte + TailComment []byte + + // The Anchor (for SCALAR_EVENT, SEQUENCE_START_EVENT, MAPPING_START_EVENT, ALIAS_EVENT). + Anchor []byte + + // The Tag (for SCALAR_EVENT, SEQUENCE_START_EVENT, MAPPING_START_EVENT). + Tag []byte + + // The scalar Value (for SCALAR_EVENT). + Value []byte + + // Is the document start/end indicator Implicit, or the tag optional? + // (for DOCUMENT_START_EVENT, DOCUMENT_END_EVENT, SEQUENCE_START_EVENT, MAPPING_START_EVENT, SCALAR_EVENT). + Implicit bool + + // Is the tag optional for any non-plain style? (for SCALAR_EVENT). + quoted_implicit bool + + // The Style (for SCALAR_EVENT, SEQUENCE_START_EVENT, MAPPING_START_EVENT). + Style Style +} + +// ScalarStyle returns the style of a scalar event. +func (e *Event) ScalarStyle() ScalarStyle { return ScalarStyle(e.Style) } + +// SequenceStyle returns the style of a sequence event. +func (e *Event) SequenceStyle() SequenceStyle { return SequenceStyle(e.Style) } + +// MappingStyle returns the style of a mapping event. +func (e *Event) MappingStyle() MappingStyle { return MappingStyle(e.Style) } + +// GetEncoding returns the stream encoding (for STREAM_START_EVENT). +func (e *Event) GetEncoding() Encoding { return e.encoding } + +// GetVersionDirective returns the version directive (for DOCUMENT_START_EVENT). +func (e *Event) GetVersionDirective() *VersionDirective { return e.versionDirective } + +// GetTagDirectives returns the tag directives (for DOCUMENT_START_EVENT). +func (e *Event) GetTagDirectives() []TagDirective { return e.tagDirectives } + +// Nodes +const ( + NULL_TAG = "tag:yaml.org,2002:null" // The tag !!null with the only possible value: null. + BOOL_TAG = "tag:yaml.org,2002:bool" // The tag !!bool with the values: true and false. + STR_TAG = "tag:yaml.org,2002:str" // The tag !!str for string values. + INT_TAG = "tag:yaml.org,2002:int" // The tag !!int for integer values. + FLOAT_TAG = "tag:yaml.org,2002:float" // The tag !!float for float values. + TIMESTAMP_TAG = "tag:yaml.org,2002:timestamp" // The tag !!timestamp for date and time values. + + SEQ_TAG = "tag:yaml.org,2002:seq" // The tag !!seq is used to denote sequences. + MAP_TAG = "tag:yaml.org,2002:map" // The tag !!map is used to denote mapping. + + // Not in original libyaml. + BINARY_TAG = "tag:yaml.org,2002:binary" + MERGE_TAG = "tag:yaml.org,2002:merge" + + DEFAULT_SCALAR_TAG = STR_TAG // The default scalar tag is !!str. + DEFAULT_SEQUENCE_TAG = SEQ_TAG // The default sequence tag is !!seq. + DEFAULT_MAPPING_TAG = MAP_TAG // The default mapping tag is !!map. +) diff --git a/vendor/go.yaml.in/yaml/v4/node.go b/vendor/go.yaml.in/yaml/v4/node.go new file mode 100644 index 000000000..b78a50ee1 --- /dev/null +++ b/vendor/go.yaml.in/yaml/v4/node.go @@ -0,0 +1,98 @@ +package yaml + +import "go.yaml.in/yaml/v4/internal/libyaml" + +// ----------------------------------------------------------------------------- +// Node-related type aliases and constants +// ----------------------------------------------------------------------------- +type ( + // 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, columns, and comments, the content when re-encoded will + // not have its original textual representation preserved. + // An effort is made to render the data pleasantly, 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) + Node = libyaml.Node + + // Kind represents the type of YAML node. + Kind = libyaml.Kind + + // Style represents the formatting style of a YAML node. + Style = libyaml.Style + + // Marshaler interface may be implemented by types to customize their + // behavior when being marshaled into a YAML document. + Marshaler = libyaml.Marshaler + + // Unmarshaler is the interface implemented by types that can unmarshal + // a YAML description of themselves. + Unmarshaler = libyaml.Unmarshaler + + // 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]. + IsZeroer = libyaml.IsZeroer +) + +// Kind constants define the different types of YAML nodes. +const ( + // DocumentNode represents the root of a YAML document. + DocumentNode = libyaml.DocumentNode + + // SequenceNode represents a YAML sequence (list). + SequenceNode = libyaml.SequenceNode + + // MappingNode represents a YAML mapping (dictionary). + MappingNode = libyaml.MappingNode + + // ScalarNode represents a YAML scalar value. + ScalarNode = libyaml.ScalarNode + + // AliasNode represents a reference to an anchored node. + AliasNode = libyaml.AliasNode + + // StreamNode represents a container for multiple YAML documents. + StreamNode = libyaml.StreamNode +) + +// Style constants define different formatting styles for YAML nodes. +const ( + // TaggedStyle explicitly shows the tag on the node. + TaggedStyle = libyaml.TaggedStyle + + // DoubleQuotedStyle uses double quotes for scalar values. + DoubleQuotedStyle = libyaml.DoubleQuotedStyle + + // SingleQuotedStyle uses single quotes for scalar values. + SingleQuotedStyle = libyaml.SingleQuotedStyle + + // LiteralStyle uses literal block scalar style (|). + LiteralStyle = libyaml.LiteralStyle + + // FoldedStyle uses folded block scalar style (>). + FoldedStyle = libyaml.FoldedStyle + + // FlowStyle uses flow style (inline) formatting. + FlowStyle = libyaml.FlowStyle +) diff --git a/vendor/go.yaml.in/yaml/v4/plugin.go b/vendor/go.yaml.in/yaml/v4/plugin.go new file mode 100644 index 000000000..4f5b4a3b1 --- /dev/null +++ b/vendor/go.yaml.in/yaml/v4/plugin.go @@ -0,0 +1,25 @@ +// Copyright 2026 The go-yaml Project Contributors +// SPDX-License-Identifier: Apache-2.0 + +package yaml + +// LimitPlugin configures safety limits for YAML parsing. +// +// When registered, CheckDepth is called on each nesting depth increase, +// and CheckAlias is called on each alias expansion to detect excessive +// aliasing. +// +// Example usage: +// +// import "go.yaml.in/yaml/v4/plugin/limit" +// loader := yaml.NewLoader(data, yaml.WithPlugin(limit.New(limit.AliasNone()))) +type LimitPlugin interface { + // CheckDepth is called when the parser increases nesting depth. + // depth is the current nesting level; ctx.Kind is "flow" or "block". + // Return an error to abort parsing. + CheckDepth(depth int, ctx *DepthContext) error + + // CheckAlias is called during alias expansion. + // Return an error to abort construction. + CheckAlias(aliasCount, constructCount int) error +} diff --git a/vendor/go.yaml.in/yaml/v4/plugin/limit/plugin.go b/vendor/go.yaml.in/yaml/v4/plugin/limit/plugin.go new file mode 100644 index 000000000..be9703eaa --- /dev/null +++ b/vendor/go.yaml.in/yaml/v4/plugin/limit/plugin.go @@ -0,0 +1,191 @@ +// Copyright 2026 The go-yaml Project Contributors +// SPDX-License-Identifier: Apache-2.0 + +// Package limit provides a configurable safety limit plugin for go-yaml. +// +// The limit plugin controls the maximum nesting depth and alias expansion +// ratio during YAML parsing. +// By default, go-yaml enforces conservative limits to prevent DoS attacks. +// This plugin lets you relax or tighten those limits for your use case. +// +// # Usage +// +// import ( +// "go.yaml.in/yaml/v4" +// "go.yaml.in/yaml/v4/plugin/limit" +// ) +// +// // Default limits +// loader := yaml.NewLoader(data, yaml.WithPlugin(limit.New())) +// +// // Disable alias checking (e.g. for 11,000 programmatic aliases) +// loader := yaml.NewLoader(data, yaml.WithPlugin(limit.New(limit.AliasNone()))) +// +// // Custom depth limit +// loader := yaml.NewLoader(data, yaml.WithPlugin(limit.New(limit.DepthValue(50)))) +// +// # Third-Party Plugins +// +// You can implement [yaml.LimitPlugin] directly instead of using this package: +// +// type StrictLimit struct{} +// func (s *StrictLimit) CheckDepth(depth int, ctx *yaml.DepthContext) error { ... } +// func (s *StrictLimit) CheckAlias(aliasCount, constructCount int) error { ... } +// yaml.NewLoader(data, yaml.WithPlugin(&StrictLimit{})) +package limit + +import ( + "fmt" + + "go.yaml.in/yaml/v4/internal/libyaml" +) + +// DepthContext is an alias for the type used in depth check callbacks. +// See [yaml.DepthContext] for field documentation. +type DepthContext = libyaml.DepthContext + +// Plugin implements configurable safety limits for YAML parsing. +type Plugin struct { + depthLimit *int + depthDisabled bool + depthFn func(int, *DepthContext) error + aliasLimit *int + aliasDisabled bool + aliasFn func(int, int) error +} + +// Option configures a [Plugin]. +type Option func(*Plugin) + +// New creates a limit plugin with the given options. +// With no options, it uses the same defaults as the bare go-yaml library. +func New(opts ...Option) *Plugin { + p := &Plugin{} + for _, o := range opts { + o(p) + } + return p +} + +// DepthValue sets a maximum nesting depth (both flow and block). +func DepthValue(n int) Option { + return func(p *Plugin) { + p.depthLimit = &n + p.depthDisabled = false + p.depthFn = nil + } +} + +// DepthNone disables depth checking entirely. +func DepthNone() Option { + return func(p *Plugin) { + p.depthDisabled = true + p.depthLimit = nil + p.depthFn = nil + } +} + +// DepthFunc sets a custom depth check function. +func DepthFunc(fn func(depth int, ctx *DepthContext) error) Option { + return func(p *Plugin) { + p.depthFn = fn + p.depthLimit = nil + p.depthDisabled = false + } +} + +// AliasValue sets a simple alias expansion count threshold. +func AliasValue(n int) Option { + return func(p *Plugin) { + p.aliasLimit = &n + p.aliasDisabled = false + p.aliasFn = nil + } +} + +// AliasNone disables alias checking entirely. +func AliasNone() Option { + return func(p *Plugin) { + p.aliasDisabled = true + p.aliasLimit = nil + p.aliasFn = nil + } +} + +// AliasFunc sets a custom alias check function. +func AliasFunc(fn func(aliasCount, constructCount int) error) Option { + return func(p *Plugin) { + p.aliasFn = fn + p.aliasLimit = nil + p.aliasDisabled = false + } +} + +// CheckDepth implements [yaml.LimitPlugin]. +func (p *Plugin) CheckDepth(depth int, ctx *DepthContext) error { + if p.depthFn != nil { + return p.depthFn(depth, ctx) + } + if p.depthDisabled { + return nil + } + if p.depthLimit != nil { + if depth > *p.depthLimit { + return fmt.Errorf("exceeded max depth of %d", *p.depthLimit) + } + return nil + } + return libyaml.DefaultDepthCheck(depth, ctx) +} + +// CheckAlias implements [yaml.LimitPlugin]. +func (p *Plugin) CheckAlias(aliasCount, constructCount int) error { + if p.aliasFn != nil { + return p.aliasFn(aliasCount, constructCount) + } + if p.aliasDisabled { + return nil + } + if p.aliasLimit != nil { + if aliasCount > *p.aliasLimit { + return fmt.Errorf("exceeded max alias count of %d", *p.aliasLimit) + } + return nil + } + return libyaml.DefaultAliasCheck(aliasCount, constructCount) +} + +// NewFromYAML creates a limit plugin from a YAML config map. +// Keys: "depth" (int or null), "alias" (int or null). +// Null values disable the corresponding check. +// Omitted keys use defaults. +func NewFromYAML(cfg map[string]any) (*Plugin, error) { + var opts []Option + for key, val := range cfg { + switch key { + case "depth": + if val == nil { + opts = append(opts, DepthNone()) + } else { + n, ok := val.(int) + if !ok { + return nil, fmt.Errorf("limit: depth must be int or null, got %T", val) + } + opts = append(opts, DepthValue(n)) + } + case "alias": + if val == nil { + opts = append(opts, AliasNone()) + } else { + n, ok := val.(int) + if !ok { + return nil, fmt.Errorf("limit: alias must be int or null, got %T", val) + } + opts = append(opts, AliasValue(n)) + } + default: + return nil, fmt.Errorf("limit: unknown key %q", key) + } + } + return New(opts...), nil +} diff --git a/vendor/go.yaml.in/yaml/v4/yaml.go b/vendor/go.yaml.in/yaml/v4/yaml.go new file mode 100644 index 000000000..b003171db --- /dev/null +++ b/vendor/go.yaml.in/yaml/v4/yaml.go @@ -0,0 +1,751 @@ +// Copyright 2011-2019 Canonical Ltd +// Copyright 2025 The go-yaml Project Contributors +// SPDX-License-Identifier: Apache-2.0 + +// Package yaml implements YAML support for the Go language. +// +// Source code and other details for the project are available at GitHub: +// +// https://github.com/yaml/go-yaml +// +// This file contains: +// - Version preset functions (WithV2Defaults, WithV3Defaults, WithV4Defaults) +// - Options API (WithIndent, WithKnownFields, etc.) +// - Type and constant re-exports from internal/libyaml +// - Helper functions for struct field handling +// - Load/Dump API (Load, Dump, Loader, Dumper) +// - Classic APIs (Decoder, Encoder, Unmarshal, Marshal) + +package yaml + +import ( + "errors" + "fmt" + "io" + + "go.yaml.in/yaml/v4/internal/libyaml" + "go.yaml.in/yaml/v4/plugin/limit" +) + +//----------------------------------------------------------------------------- +// Version presets +//----------------------------------------------------------------------------- + +// Usage: +// yaml.Dump(&data, yaml.WithV3Defaults()) +// yaml.Dump(&data, yaml.WithV3Defaults(), yaml.WithIndent(2), yaml.WithCompactSeqIndent()) + +// WithV2Defaults returns V2-compatible default options. +func WithV2Defaults() Option { + return Options( + WithIndent(2), + WithCompactSeqIndent(false), + WithLineWidth(80), + WithUnicode(true), + WithUniqueKeys(true), + WithQuotePreference(QuoteLegacy), + WithPlugin(limit.New()), + ) +} + +// WithV3Defaults returns V3-compatible default options. +func WithV3Defaults() Option { + return Options( + WithIndent(4), + WithCompactSeqIndent(false), + WithLineWidth(80), + WithUnicode(true), + WithUniqueKeys(true), + WithQuotePreference(QuoteLegacy), + WithPlugin(limit.New()), + ) +} + +// WithV4Defaults returns the current V4 default options. +func WithV4Defaults() Option { + return Options( + WithIndent(2), + WithCompactSeqIndent(true), + WithLineWidth(80), + WithUnicode(true), + WithUniqueKeys(true), + WithQuotePreference(QuoteSingle), + WithPlugin(limit.New()), + ) +} + +//----------------------------------------------------------------------------- +// Options +//----------------------------------------------------------------------------- + +// Option allows configuring YAML loading and dumping operations. +type Option = libyaml.Option + +// Option configuration functions +var ( + // WithIndent sets the number of spaces to use for indentation when + // dumping YAML content. + // + // Valid values are 2-9. Common choices: 2 (compact), 4 (readable). + WithIndent = libyaml.WithIndent + + // WithCompactSeqIndent configures whether the sequence indicator '- ' is + // considered part of the indentation when dumping YAML content. + // + // If compact is true, '- ' is treated as part of the indentation. + // If compact is false, '- ' is not treated as part of the indentation. + // When called without arguments, defaults to true. + WithCompactSeqIndent = libyaml.WithCompactSeqIndent + + // WithKnownFields enables or disables strict field checking during YAML + // loading. + // + // When enabled, loading will return an error if the YAML input contains + // fields that do not correspond to any fields in the target struct. + // When called without arguments, defaults to true. + WithKnownFields = libyaml.WithKnownFields + + // WithSingleDocument configures the Loader to only process the first + // document in a YAML stream. After the first document is loaded, + // subsequent calls to Load will return [io.EOF]. + // + // When called without arguments, defaults to true. + // + // This is useful when you expect exactly one document and want behavior + // similar to Unmarshal. + WithSingleDocument = libyaml.WithSingleDocument + + // WithStreamNodes enables returning stream boundary nodes when loading + // YAML. + // + // When enabled, Loader.Load returns an interleaved sequence of + // StreamNode and DocumentNode values: + // + // [StreamNode, DocNode, StreamNode, DocNode, ..., StreamNode] + // + // StreamNodes contain metadata about the stream including: + // - Encoding (UTF-8, UTF-16LE, UTF-16BE) + // - YAML version directive (%YAML) + // - Tag directives (%TAG) + // - Position information (Line, Column) + // + // An empty YAML stream returns a single StreamNode. + // When called without arguments, defaults to true. + // + // The default is false. + WithStreamNodes = libyaml.WithStreamNodes + + // WithAllDocuments enables multi-document mode for Load and Dump + // operations. + // + // When used with Load, the target must be a pointer to a slice. + // All documents in the YAML stream will be decoded into the slice. + // Zero documents results in an empty slice (no error). + // + // When used with Dump, the input must be a slice. + // Each element will be encoded as a separate YAML document + // with "---" separators. + // + // When called without arguments, defaults to true. + // + // The default is false (single-document mode). + WithAllDocuments = libyaml.WithAllDocuments + + // WithLineWidth sets the preferred line width for YAML output. + // + // When encoding long strings, the encoder will attempt to wrap them at + // this width using literal block style (|). Set to -1 or 0 for unlimited + // width. + // + // The default is 80 characters. + WithLineWidth = libyaml.WithLineWidth + + // WithUnicode controls whether non-ASCII characters are allowed in YAML + // output. + // + // When true, non-ASCII characters appear as-is (e.g., "café"). + // When false, non-ASCII characters are escaped (e.g., "caf\u00e9"). + // When called without arguments, defaults to true. + // + // The default is true. + WithUnicode = libyaml.WithUnicode + + // WithUniqueKeys enables or disables duplicate key detection during YAML + // loading. + // + // When enabled, loading will return an error if the YAML input contains + // duplicate keys in any mapping. This is a security feature that prevents + // key override attacks. + // When called without arguments, defaults to true. + // + // The default is true. + WithUniqueKeys = libyaml.WithUniqueKeys + + // WithCanonical forces canonical YAML output format. + // + // When enabled, the encoder outputs strictly canonical YAML with explicit + // tags for all values. This produces verbose output primarily useful for + // debugging and YAML spec compliance testing. + // When called without arguments, defaults to true. + // + // The default is false. + WithCanonical = libyaml.WithCanonical + + // WithLineBreak sets the line ending style for YAML output. + // + // Available options: + // - LineBreakLN: Unix-style \n (default) + // - LineBreakCR: Old Mac-style \r + // - LineBreakCRLN: Windows-style \r\n + // + // The default is LineBreakLN. + WithLineBreak = libyaml.WithLineBreak + + // WithExplicitStart controls whether document start markers (---) are + // always emitted. + // + // When true, every document begins with an explicit "---" marker. + // When false (default), the marker is omitted for the first document. + // When called without arguments, defaults to true. + WithExplicitStart = libyaml.WithExplicitStart + + // WithExplicitEnd controls whether document end markers (...) are always + // emitted. + // + // When true, every document ends with an explicit "..." marker. + // When false (default), the marker is omitted. + // When called without arguments, defaults to true. + WithExplicitEnd = libyaml.WithExplicitEnd + + // WithFlowSimpleCollections controls whether simple collections use flow + // style. + // + // When true, sequences and mappings containing only scalar values (no + // nested collections) are rendered in flow style if they fit within the + // line width. + // Example: {name: test, count: 42} or [a, b, c] + // When called without arguments, defaults to true. + // + // When false (default), all collections use block style. + WithFlowSimpleCollections = libyaml.WithFlowSimpleCollections + + // WithQuotePreference sets the preferred quote style for strings that + // require quoting. + // + // This option only affects strings that require quoting per the YAML spec. + // Plain strings that don't need quoting remain unquoted regardless of this + // setting. Quoting is required for: + // - Strings that look like other YAML types (true, false, null, 123, etc.) + // - Strings with leading/trailing whitespace + // - Strings containing special YAML syntax characters + // - Empty strings in certain contexts + // + // Quote styles: + // - QuoteSingle: Use single quotes (v4 default) + // - QuoteDouble: Use double quotes + // - QuoteLegacy: Legacy v2/v3 behavior (mixed quoting) + WithQuotePreference = libyaml.WithQuotePreference +) + +// Options combines multiple options into a single Option. +// This is useful for creating option presets or combining version defaults +// with custom options. +// +// Example: +// +// opts := yaml.Options(yaml.WithV4Defaults(), yaml.WithIndent(3)) +// yaml.Dump(&data, opts) +func Options(opts ...Option) Option { + return libyaml.CombineOptions(opts...) +} + +// DepthKind represents the type of nesting (flow or block). +type DepthKind = libyaml.DepthKind + +// DepthKind constants for nesting depth checks. +const ( + DepthKindFlow = libyaml.DepthKindFlow + DepthKindBlock = libyaml.DepthKindBlock +) + +// DepthContext holds context about a nesting depth check. +type DepthContext = libyaml.DepthContext + +// WithPlugin registers one or more plugins for YAML processing. +// +// Plugins extend the YAML library with custom processing logic. +// Each plugin implements one or more plugin interfaces. +// Currently supported plugin types: +// - LimitPlugin: Controls depth and alias expansion limits +// +// Example: +// +// import "go.yaml.in/yaml/v4/plugin/limit" +// loader := yaml.NewLoader(data, yaml.WithPlugin(limit.New(limit.AliasNone()))) +// +// Plugins use public types and can be implemented by external packages. +func WithPlugin(plugins ...any) Option { + return func(o *libyaml.Options) error { + for _, p := range plugins { + registered := false + if lp, ok := p.(LimitPlugin); ok { + o.DepthCheck = lp.CheckDepth + o.AliasCheck = lp.CheckAlias + registered = true + } + // Future plugin types add cases here (non-exclusive if) + if !registered { + return fmt.Errorf("yaml: unsupported plugin type: %T", p) + } + } + return nil + } +} + +// OptsYAML parses a YAML string containing option settings and returns +// an Option that can be combined with other options using Options(). +// +// The YAML string can specify any of these fields: +// - indent (int) +// - compact-seq-indent (bool) +// - line-width (int) +// - unicode (bool) +// - canonical (bool) +// - line-break (string: ln, cr, crln) +// - explicit-start (bool) +// - explicit-end (bool) +// - flow-simple-coll (bool) +// - known-fields (bool) +// - single-document (bool) +// - unique-keys (bool) +// - plugin (map of plugin name to config) +// +// The plugin field configures plugins by name. Each key is a plugin +// name and the value is its configuration map (or null for defaults). +// Currently supported: "limit" with keys "depth" and "alias" (int +// or null to disable). +// +// Only fields specified in the YAML will override other options when +// combined. Unspecified fields won't affect other options. +// +// Example: +// +// opts, err := yaml.OptsYAML(` +// indent: 3 +// known-fields: true +// plugin: +// limit: +// depth: 50 +// `) +// yaml.Dump(&data, yaml.Options(V4, opts)) +func OptsYAML(yamlStr string) (Option, error) { + var cfg struct { + Indent *int `yaml:"indent"` + CompactSeqIndent *bool `yaml:"compact-seq-indent"` + LineWidth *int `yaml:"line-width"` + Unicode *bool `yaml:"unicode"` + Canonical *bool `yaml:"canonical"` + LineBreak *string `yaml:"line-break"` + ExplicitStart *bool `yaml:"explicit-start"` + ExplicitEnd *bool `yaml:"explicit-end"` + FlowSimpleCollections *bool `yaml:"flow-simple-coll"` + KnownFields *bool `yaml:"known-fields"` + SingleDocument *bool `yaml:"single-document"` + UniqueKeys *bool `yaml:"unique-keys"` + Plugin map[string]any `yaml:"plugin"` + } + if err := Load([]byte(yamlStr), &cfg, WithKnownFields()); err != nil { + return nil, err + } + + // Build options only for fields that were set + var optList []Option + if cfg.Indent != nil { + optList = append(optList, WithIndent(*cfg.Indent)) + } + if cfg.CompactSeqIndent != nil { + optList = append(optList, WithCompactSeqIndent(*cfg.CompactSeqIndent)) + } + if cfg.LineWidth != nil { + optList = append(optList, WithLineWidth(*cfg.LineWidth)) + } + if cfg.Unicode != nil { + optList = append(optList, WithUnicode(*cfg.Unicode)) + } + if cfg.ExplicitStart != nil { + optList = append(optList, WithExplicitStart(*cfg.ExplicitStart)) + } + if cfg.ExplicitEnd != nil { + optList = append(optList, WithExplicitEnd(*cfg.ExplicitEnd)) + } + if cfg.FlowSimpleCollections != nil { + optList = append(optList, WithFlowSimpleCollections(*cfg.FlowSimpleCollections)) + } + if cfg.KnownFields != nil { + optList = append(optList, WithKnownFields(*cfg.KnownFields)) + } + if cfg.SingleDocument != nil && *cfg.SingleDocument { + optList = append(optList, WithSingleDocument()) + } + if cfg.UniqueKeys != nil { + optList = append(optList, WithUniqueKeys(*cfg.UniqueKeys)) + } + if cfg.Canonical != nil { + optList = append(optList, WithCanonical(*cfg.Canonical)) + } + if cfg.LineBreak != nil { + switch *cfg.LineBreak { + case "ln": + optList = append(optList, WithLineBreak(LineBreakLN)) + case "cr": + optList = append(optList, WithLineBreak(LineBreakCR)) + case "crln": + optList = append(optList, WithLineBreak(LineBreakCRLN)) + default: + return nil, errors.New("yaml: invalid line-break value (use ln, cr, or crln)") + } + } + + for name, val := range cfg.Plugin { + switch name { + case "limit": + var cfgMap map[string]any + switch v := val.(type) { + case nil: + cfgMap = map[string]any{} + case map[string]any: + cfgMap = v + default: + return nil, fmt.Errorf("yaml: plugin %q value must be a mapping or null", name) + } + p, err := limit.NewFromYAML(cfgMap) + if err != nil { + return nil, err + } + optList = append(optList, WithPlugin(p)) + default: + return nil, fmt.Errorf("yaml: unknown plugin %q", name) + } + } + + return Options(optList...), nil +} + +//----------------------------------------------------------------------------- +// Type and constant re-exports +//----------------------------------------------------------------------------- + +// Re-export stream-related types +type ( + Stream = libyaml.Stream + VersionDirective = libyaml.StreamVersionDirective + + // TagDirective represents a YAML %TAG directive for stream nodes. + TagDirective = libyaml.StreamTagDirective + + // Encoding represents the character encoding of a YAML stream. + Encoding = libyaml.Encoding +) + +// Encoding constants for YAML stream encoding +const ( + // EncodingAny lets the parser choose the encoding. + EncodingAny = libyaml.ANY_ENCODING + + // EncodingUTF8 is the default UTF-8 encoding. + EncodingUTF8 = libyaml.UTF8_ENCODING + + // EncodingUTF16LE is UTF-16-LE encoding with BOM. + EncodingUTF16LE = libyaml.UTF16LE_ENCODING + + // EncodingUTF16BE is UTF-16-BE encoding with BOM. + EncodingUTF16BE = libyaml.UTF16BE_ENCODING +) + +// Stage identifies the processing stage where an error occurred during YAML +// loading or dumping. +type Stage = libyaml.Stage + +// Stage constants for YAML processing pipeline. +const ( + // Load stages + ReaderStage = libyaml.ReaderStage // Input reading and encoding + ScannerStage = libyaml.ScannerStage // Tokenization + ParserStage = libyaml.ParserStage // Event stream parsing + ComposerStage = libyaml.ComposerStage // Node tree construction + ResolverStage = libyaml.ResolverStage // Tag resolution + ConstructorStage = libyaml.ConstructorStage // Go value construction + + // Dump stages + RepresenterStage = libyaml.RepresenterStage // Go value to Node tree + SerializerStage = libyaml.SerializerStage // Node tree to events + EmitterStage = libyaml.EmitterStage // Events to YAML bytes + WriterStage = libyaml.WriterStage // Output writing +) + +// Mark represents a position in the YAML document. +type Mark = libyaml.Mark + +// Error types for YAML loading and dumping +type ( + // LoadError represents an error encountered while decoding a YAML document. + // + // It contains details about the location in the document where the error + // occurred, as well as the processing stage that generated it. + LoadError = libyaml.LoadError + + // LoadErrors is returned when one or more fields cannot be properly decoded. + // + // It contains multiple *[LoadError] instances with details about each error. + LoadErrors = libyaml.LoadErrors + + // DumpError represents an error that occurred while dumping a YAML document. + // + // It identifies the processing stage where the error occurred and provides + // an optional underlying cause via Unwrap. + DumpError = libyaml.DumpError + + // TypeError is a legacy error type retained for compatibility. + // + // Deprecated: Use [LoadErrors] instead. + // + //nolint:staticcheck // we are using deprecated TypeError for compatibility + TypeError = libyaml.TypeError +) + +// NewLoadError creates a LoadError with an underlying cause error. +// The cause is accessible via Unwrap for use with [errors.Is] and [errors.As]. +var NewLoadError = libyaml.NewLoadError + +// NewDumpError creates a DumpError with an underlying cause error. +// The cause is accessible via Unwrap for use with [errors.Is] and [errors.As]. +var NewDumpError = libyaml.NewDumpError + +// LineBreak represents the line ending style for YAML output. +type LineBreak = libyaml.LineBreak + +// Line break constants for different platforms. +const ( + LineBreakLN = libyaml.LN_BREAK // Unix-style \n (default) + LineBreakCR = libyaml.CR_BREAK // Old Mac-style \r + LineBreakCRLN = libyaml.CRLN_BREAK // Windows-style \r\n +) + +// QuoteStyle represents the quote style to use when quoting is required. +type QuoteStyle = libyaml.QuoteStyle + +// Quote style constants for required quoting. +const ( + QuoteSingle = libyaml.QuoteSingle // Prefer single quotes (v4 default) + QuoteDouble = libyaml.QuoteDouble // Prefer double quotes + QuoteLegacy = libyaml.QuoteLegacy // Legacy v2/v3 behavior +) + +//----------------------------------------------------------------------------- +// Load/Dump API +//----------------------------------------------------------------------------- + +// Advanced streaming API types +type ( + // Loader reads and loads YAML values from an input stream with + // configurable options. + Loader = libyaml.Loader + + // Dumper writes YAML values to an output stream with configurable options. + Dumper = libyaml.Dumper +) + +// NewLoader returns a new Loader that reads from r with the given options. +func NewLoader(r io.Reader, opts ...Option) (*Loader, error) { + return libyaml.NewLoader(r, opts...) +} + +// NewDumper returns a new Dumper that writes to w with the given options. +func NewDumper(w io.Writer, opts ...Option) (*Dumper, error) { + return libyaml.NewDumper(w, opts...) +} + +// Load loads YAML document(s) with the given options. +func Load(in []byte, out any, opts ...Option) error { + return libyaml.Load(in, out, opts...) +} + +// Dump encodes a value to YAML with the given options. +func Dump(in any, opts ...Option) (out []byte, err error) { + return libyaml.Dump(in, opts...) +} + +//----------------------------------------------------------------------------- +// Classic APIs +//----------------------------------------------------------------------------- + +// A Decoder reads and decodes YAML values from an input stream. +type Decoder struct { + loader *Loader +} + +// 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 { + // NewLoader won't return error with WithV3Defaults() and withFromLegacy + loader, _ := NewLoader(r, WithV3Defaults(), withFromLegacy()) + return &Decoder{loader: loader} +} + +// 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.loader.SetKnownFields(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 any) error { + return dec.loader.Load(v) +} + +// An Encoder writes YAML values to an output stream. +type Encoder struct { + dumper *Dumper +} + +// 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 { + // NewDumper won't return an error when using WithV3Defaults() + dumper, _ := NewDumper(w, WithV3Defaults()) + return &Encoder{dumper: dumper} +} + +// 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 any) error { + return e.dumper.Dump(v) +} + +// SetIndent changes the used indentation used when encoding. +func (e *Encoder) SetIndent(spaces int) { + e.dumper.SetIndent(spaces) +} + +// CompactSeqIndent makes it so that '- ' is considered part of the indentation. +func (e *Encoder) CompactSeqIndent() { + e.dumper.SetCompactSeqIndent(true) +} + +// DefaultSeqIndent makes it so that '- ' is not considered part of the indentation. +func (e *Encoder) DefaultSeqIndent() { + e.dumper.SetCompactSeqIndent(false) +} + +// Close closes the encoder by writing any remaining data. +// It does not write a stream terminating string "...". +func (e *Encoder) Close() error { + return e.dumper.Close() +} + +// 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.LoadErrors 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 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.Construct([]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 any) (err error) { + return Load(in, out, WithV3Defaults(), withFromLegacy()) +} + +// withFromLegacy is a private option that indicates this call is from +// a legacy API (Unmarshal/Decoder). It enables Unmarshaler interface +// checking and allows trailing content for backward compatibility. +func withFromLegacy() Option { + return func(o *libyaml.Options) error { + o.FromLegacy = true + 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 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:"[][,[,]]" (...)` +// +// 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. +// See doc/inline-tags.md for detailed examples and use cases. +// +// 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 any) (out []byte, err error) { + // Use WithV3Defaults() with unlimited line width to match legacy DefaultOptions + return Dump(in, WithV3Defaults(), WithLineWidth(-1)) +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 8ff75e1c6..0a28082f2 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -25,7 +25,7 @@ github.com/chzyer/readline # github.com/clipperhouse/uax29/v2 v2.7.0 ## explicit; go 1.18 github.com/clipperhouse/uax29/v2/graphemes -# github.com/cloudbase/garm-provider-common v0.1.10-0.20260627162627-e00d7529cc6f +# github.com/cloudbase/garm-provider-common v0.1.10-0.20260707095006-33396540b57b ## explicit; go 1.25.0 github.com/cloudbase/garm-provider-common/cloudconfig github.com/cloudbase/garm-provider-common/defaults @@ -378,6 +378,11 @@ go.opentelemetry.io/otel/trace/noop # go.yaml.in/yaml/v3 v3.0.4 ## explicit; go 1.16 go.yaml.in/yaml/v3 +# go.yaml.in/yaml/v4 v4.0.0-rc.6 +## explicit; go 1.18 +go.yaml.in/yaml/v4 +go.yaml.in/yaml/v4/internal/libyaml +go.yaml.in/yaml/v4/plugin/limit # golang.org/x/crypto v0.53.0 ## explicit; go 1.25.0 golang.org/x/crypto/bcrypt