Files
mstore/vendor/go.yaml.in/yaml/v4/internal/libyaml/options.go
T
2026-02-21 13:16:56 +02:00

391 lines
12 KiB
Go

//
// Copyright (c) 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
}
// Option allows configuring YAML loading and dumping operations.
type Option func(*Options) error
// 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,
}
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,
}