yaml

package module
v0.0.0-...-2167ed0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 17, 2026 License: BSD-3-Clause Imports: 8 Imported by: 0

README

go-ruby-yaml/yaml

yaml — go-ruby-yaml

Docs License Go Coverage

A pure-Go (no cgo) reimplementation of Ruby's Psych YAML emitter and loader — the deterministic, interpreter-independent core of MRI 4.0.5's Psych.dump / Psych.load. It serialises a tree of Ruby values to a Psych-compatible YAML document and parses one back, so Load(Dump(x)) round-trips the structures Ruby programs (and Puppet's state.yaml / last_run_summary.yaml) persist — without any Ruby runtime.

It is the YAML backend for go-embedded-ruby, but is a standalone, reusable module with no dependency on the Ruby runtime — a sibling of go-ruby-regexp (the Onigmo engine) and go-ruby-erb (the ERB compiler).

What it is — and isn't. Emitting and parsing YAML for the Ruby value model (scalar typing, block/flow layout, anchors/aliases, the !ruby/object: tag grammar) is fully deterministic and needs no interpreter, so it lives here as pure Go. Binding the documents to live Ruby objects — instantiating a class, reading an object's instance variables — is the host's job; this library hands back a small, explicit value model (*Object, *Range, Symbol, …) the host maps to and from its own objects.

Features

Faithful port of Psych's emit + load, validated against the ruby binary on every supported platform:

  • Block + flow mappings and sequences, with Psych's default layout (a block sequence under a key aligns its dashes with the key; nested mappings indent two deeper; a dash child continues on the dash line).
  • Every scalar style — plain, single-quoted, double-quoted (with the \n \t \r \0 \xNN \" \\ escapes), and literal (| / |- / |+) and folded (>) block scalars, indent-correct at any depth.
  • Psych implicit typingnull / ~, booleans, integers (decimal, 0x hex, 0o octal, 0b binary, _ separators, and big integers), floats (.inf / -.inf / .nan, exponents), and ISO-8601 timestamps.
  • Symbols — the bare :name form and the !ruby/symbol tag.
  • Anchors & aliases — a value shared (or cyclic) in the graph is emitted once behind &N and referenced thereafter with *N; the loader resolves them.
  • !ruby/object:Class mappings of instance variables (deterministic key order — host-supplied Order, else lexicographic), plus !ruby/range, !ruby/class / !ruby/module, and !ruby/regexp.
  • Complex / nil mapping keys via the explicit ? key / : value form.
  • Document markers (---, ...), comments, and #coding headers.

CGO-free, dependency-free, 100% test coverage, gofmt + go vet clean, and green across the six 64-bit Go targets (amd64, arm64, riscv64, loong64, ppc64le, s390x).

Install

go get github.com/go-ruby-yaml/yaml

Usage

package main

import (
	"fmt"

	"github.com/go-ruby-yaml/yaml"
)

func main() {
	// Build a Ruby value tree from the package's value model.
	m := yaml.NewMap()
	m.Set(yaml.Symbol("checked"), true)
	m.Set("hosts", []any{"web", "db"})
	m.Set("range", &yaml.Range{Begin: 1, End: 5})

	doc, _ := yaml.Dump(m) // Psych.dump
	fmt.Print(doc)
	// ---
	// :checked: true
	// hosts:
	// - web
	// - db
	// range: !ruby/range
	//   begin: 1
	//   end: 5
	//   excl: false

	v, _ := yaml.Load(doc) // Psych.load — mappings come back as *yaml.Map
	fmt.Printf("%T\n", v)  // *yaml.Map
}

Ruby value model

YAML round-trips an any drawn from a small, fixed set of Go types, so a host can map its own object graph to and from this package:

Ruby Go (Dump accepts) Go (Load returns)
nil nil nil
true / false bool bool
Integer int, int64, *big.Int int64 / *big.Int
Float float64, float32 float64
String string string
Symbol yaml.Symbol yaml.Symbol
Array []any []any
Hash *yaml.Map, map[string]any, map[Symbol]any *yaml.Map (ordered)
Time time.Time time.Time
Range *yaml.Range *yaml.Range
object *yaml.Object *yaml.Object
Class/Module yaml.Class / yaml.Module yaml.Class / yaml.Module
Regexp *yaml.Regexp *yaml.Regexp

A plain Go map is emitted in sorted-key order; a *yaml.Map preserves insertion order, and Load always returns mappings as *yaml.Map so key order round-trips.

API

// Dump serialises a Ruby value to a Psych-compatible document (Psych.dump /
// Object#to_yaml). A value outside the model returns an error.
func Dump(v any, opts ...Option) (string, error)

// Load parses a Psych-compatible document (Psych.load / YAML.load).
func Load(s string) (any, error)

// SafeLoad parses like Load but honours a permitted-class allow-list; this loader
// never evaluates code, so it is safe by construction.
func SafeLoad(s string, opts ...Option) (any, error)

func WithPermittedClasses(names ...string) Option // Psych permitted_classes:
func WithAliases(allowed bool) Option             // Psych aliases:

type Symbol string
type Class  string
type Module string
type Regexp struct { Source, Flags string }
type Range  struct { Begin, End any; Exclusive bool }
type Object struct { Class string; IVars map[string]any; Order []string }
type Map    struct { /* insertion-ordered Hash */ }
func NewMap() *Map
func (m *Map) Set(key, val any)
func (m *Map) Get(key any) (any, bool)
func (m *Map) Pairs() []Pair
func (m *Map) Len() int

Tests & coverage

The suite pairs deterministic, ruby-free tests (which alone hold coverage at 100%, so the qemu cross-arch and Windows lanes pass the gate) with a differential MRI oracle: a wide corpus is dumped here and parsed by the system ruby (Psych.parse + YAML.unsafe_load), and MRI-dumped documents are loaded here — round-tripping anchors, !ruby/object, Time, big integers, and ranges in both directions. The oracle scripts $stdout.binmode so Windows text-mode never pollutes the bytes, and skip themselves where ruby is absent.

COVERPKG=$(go list ./... | paste -sd, -)
go test -race -coverpkg="$COVERPKG" -coverprofile=cover.out ./...
go tool cover -func=cover.out | tail -1   # 100.0%

License

BSD-3-Clause — see LICENSE. Copyright the go-ruby-yaml/yaml authors.

WebAssembly

Being pure Go (CGO=0), this library also compiles to WebAssembly — both GOOS=js GOARCH=wasm (browser / Node.js) and GOOS=wasip1 GOARCH=wasm (WASI). CI builds both targets on every push, alongside the six 64-bit native/qemu arches.

GOOS=js     GOARCH=wasm go build ./...   # browser / Node
GOOS=wasip1 GOARCH=wasm go build ./...   # WASI (wasmtime, wasmer, wasmedge, …)

Documentation

Overview

Package yaml is a pure-Go (CGO-free) Psych-compatible YAML emitter and loader for the Ruby value model. It is the deterministic, interpreter-independent core of MRI 4.0.5's Psych: Dump renders a tree of Ruby values to a Psych-compatible document and Load parses such a document back, so Load(Dump(x)) round-trips the structures Ruby programs (and Puppet's state/run-summary files) persist — without any Ruby runtime.

Ruby value model

A Ruby value is represented by an [any] drawn from a small, fixed set of Go types so a host (such as go-embedded-ruby) can map its own object graph to and from this package:

Ruby            Go (in)                          Go (out, from Load)
----            -------                          -------------------
nil             nil                              nil
true / false    bool                             bool
Integer         int, int64, *big.Int             int64 or *big.Int
Float           float64, float32                 float64
String          string                           string
Symbol          Symbol                           Symbol
Array           []any, []Value                   []any
Hash            *Map (ordered), map[...]any      *Map (insertion order)
Time            time.Time                        time.Time
Range           *Range                           *Range
Object          *Object (tag + ivars)            *Object
Class / Module  Class / Module                   Class / Module
Regexp          *Regexp                          *Regexp

Dump also accepts a plain Go map[string]any / map[Symbol]any (sorted by key for determinism); Load always yields an ordered *Map for a mapping so key order is preserved on round-trip.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Dump

func Dump(v Value, opts ...Option) (string, error)

Dump serialises a Ruby value to a Psych-compatible YAML document string, matching Psych.dump / Object#to_yaml. v is drawn from the package value model (see the package doc); a value outside that model returns an error rather than panicking. The opts are accepted for Psych parity and do not affect emission.

Types

type Class

type Class string

Class is a Ruby Class reference, emitted/loaded as the `!ruby/class 'Name'` tag. Its value is the fully-qualified class name (e.g. "String").

type Map

type Map struct {
	// contains filtered or unexported fields
}

Map is an insertion-ordered Ruby Hash. Load returns mappings as *Map so key order round-trips; Dump accepts *Map, a plain Go map (emitted in sorted key order), or a []Pair via NewMap.

func NewMap

func NewMap() *Map

NewMap returns an empty ordered Map.

func (*Map) Get

func (m *Map) Get(key Value) (Value, bool)

Get returns the value for a comparable key and whether it was present.

func (*Map) Len

func (m *Map) Len() int

Len reports the number of entries.

func (*Map) Pairs

func (m *Map) Pairs() []Pair

Pairs returns the entries in insertion order. The slice must not be mutated.

func (*Map) Set

func (m *Map) Set(key, val Value)

Set inserts or replaces the entry for key. A comparable key replaces an existing equal key; a non-comparable key (slice / map / *Object …) is always appended (Psych's complex-key mappings do not deduplicate here).

type Module

type Module string

Module is a Ruby Module reference, emitted/loaded as `!ruby/module 'Name'`.

type Object

type Object struct {
	Class string
	IVars map[string]Value
	// Order, when non-nil, fixes the emission order of IVars keys; names absent
	// from Order are appended in lexicographic order. When nil, all keys are
	// emitted in lexicographic order.
	Order []string
}

Object is a generic tagged Ruby object — anything Psych writes as a `!ruby/object:ClassName` mapping of instance variables. Class is the tag's class name ("" or "Object" emits the bare `!ruby/object`). IVars holds the instance variables by bare name (no leading "@"); Dump orders them by Order when set, else lexicographically (documented, deterministic). A host binds its own object instances to and from this shape.

type Option

type Option func(*options)

Option configures Dump / SafeLoad. Options are accepted for parity with Psych.dump / Psych.safe_load (which take keyword options); this loader is safe by construction — it instantiates only the *Object shapes named by the `!ruby/object:` tags actually present and never evaluates anything — so the options are tolerated and currently advisory. They exist so callers (and a host binding) can pass Psych-style configuration without breaking.

func WithAliases

func WithAliases(allowed bool) Option

WithAliases mirrors Psych.safe_load(aliases: true). Aliases are resolved regardless; the option is accepted for parity.

func WithPermittedClasses

func WithPermittedClasses(names ...string) Option

WithPermittedClasses restricts SafeLoad to materialise `!ruby/object:` tags only for the named classes (Psych's permitted_classes:). Other tagged mappings load as their plain ordered mapping.

type Pair

type Pair struct {
	Key Value
	Val Value
}

Pair is one entry of an ordered mapping.

type Range

type Range struct {
	Begin     Value
	End       Value
	Exclusive bool
}

Range is a Ruby Range, emitted/loaded as the `!ruby/range` mapping with begin / end / excl members. A nil Begin or End models a beginless / endless range.

type Regexp

type Regexp struct {
	Source string
	Flags  string
}

Regexp is a Ruby Regexp, emitted/loaded inline as `!ruby/regexp /source/flags` (Psych's representation). Flags is the trailing option letters (e.g. "mix").

type Symbol

type Symbol string

Symbol is a Ruby Symbol (`:name`). Dump emits the bare `:name` form (or the double-quoted `:"a\nb"` form when the name carries control characters), and Load maps both `:name` scalars and the `!ruby/symbol` tag back to a Symbol.

type SyntaxError

type SyntaxError struct{ Message string }

SyntaxError is the error type Load / SafeLoad return for a malformed document (mirroring Psych::SyntaxError). It carries a human-readable message.

func (*SyntaxError) Error

func (e *SyntaxError) Error() string

Error implements error.

type Value

type Value = any

Value is the interface satisfied by every Ruby value this package handles. It is purely documentary — the public API uses any — but a host may use it to constrain its own adapters.

func Load

func Load(s string) (Value, error)

Load parses a Psych-compatible YAML document into a Ruby value, matching Psych.load / YAML.load. Mappings load as an ordered *Map (key order preserved), sequences as []any, and the Psych tags into the package's Symbol / Object / Range / Class / Module / Regexp / time.Time shapes. A blank document loads as nil.

func SafeLoad

func SafeLoad(s string, opts ...Option) (Value, error)

SafeLoad parses like Load but honours the safe-load options (permitted classes). This loader is already safe by construction — it never evaluates code and only builds the *Object shapes whose tags appear — so SafeLoad and Load differ only in the optional class allow-list.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL