format

package module
v0.0.0-...-7fa8542 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: 6 Imported by: 0

README

go-ruby-format/format

format — go-ruby-format

Docs License Go Coverage

A pure-Go (no cgo) reimplementation of Ruby's format-string engine — the computation behind Kernel#sprintf, Kernel#format, and String#% — matching MRI (CRuby) 4.0 byte-for-byte across every conversion, flag, width/precision form, named/numbered reference, and the exact ArgumentError / KeyError / TypeError messages.

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

Install

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

Usage

package main

import (
	"fmt"
	"math/big"

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

func main() {
	// Plain Go values: int, int64, *big.Int, float64, string, bool, nil,
	// []any, format.Symbol.
	out, _ := format.Sprintf("%05.2f -> 0x%x", 3.14159, 255)
	fmt.Println(out) // 03.14 -> 0xff

	// Arbitrary-precision Bignum, full width.
	z := new(big.Int).Exp(big.NewInt(10), big.NewInt(30), nil)
	out, _ = format.Sprintf("%d", z)
	fmt.Println(out) // 1000000000000000000000000000000

	// Ruby's two's-complement notation for negative non-decimal bases.
	out, _ = format.Sprintf("%#x", -255)
	fmt.Println(out) // 0x..f01

	// Named references from a hash; KeyError on a missing key.
	out, _ = format.Sprintf("%<name>s is %<age>d", map[string]any{
		"name": "Ada", "age": 36,
	})
	fmt.Println(out) // Ada is 36

	// MRI-matching errors.
	_, err := format.Sprintf("%d")
	fmt.Println(err) // ArgumentError: too few arguments
}

Conversions

Verb Meaning
d i u signed decimal integer (Bignum-aware)
f fixed-point float
e E scientific float
g G shortest float (trailing zeros trimmed; kept under #)
a A hexadecimal float
s to_s
p inspect
x X o b B hex / octal / binary integer (.. two's-complement form)
c character (code point or one-character string)
%% a literal percent

Flags - + space 0 # · width/precision numeric, * (from args), and named · %n$ absolute argument references · %<name> and %{name} hash references · Bignum at full precision · the Inf / -Inf / NaN spellings.

Argument model

Sprintf(format string, args ...any) (string, error) accepts plain Go values and adapts them internally. A host such as rbgo that already holds typed value objects implements the small Value interface and calls Format(format string, args []Value, named *NamedArgs) (string, error), so its objects are formatted directly with no intermediate copy. A *NamedArgs, map[string]Value, or map[string]any supplies the hash for named references.

Tests & coverage

The package is verified two ways:

  • a differential test runs a broad corpus through both this package and the live MRI ruby oracle, comparing output — and raised exception class and message — byte-for-byte (it self-skips where ruby is absent, e.g. the qemu and Windows lanes);
  • a deterministic golden test embeds the MRI-captured results so the oracle-free lanes still exercise the entire surface and hold the 100.0% coverage gate.
go test ./...

CI enforces 100% coverage, go vet, and CGO=0 builds on all six 64-bit Go targets: amd64, arm64, riscv64, loong64, ppc64le, s390x.

License

BSD-3-Clause — see LICENSE. Copyright the go-ruby-format/format 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 format is a pure-Go (no cgo) implementation of Ruby's format-string engine — the computation behind Kernel#sprintf, Kernel#format, and String#% — compatible with MRI (CRuby) 4.0 byte-for-byte.

It is the formatting backend for go-embedded-ruby (rbgo) but is a standalone, reusable module with no dependency on the Ruby runtime, mirroring the way go-ruby-regexp provides Onigmo and go-ruby-erb provides ERB.

Entry points

Sprintf is the convenience form taking plain Go values:

out, err := format.Sprintf("%05.2f -> 0x%x", 3.14159, 255)
// out == "03.14 -> 0xff"

Format is the explicit form for hosts (rbgo) that already hold typed values:

out, err := format.Format("%<name>s is %<age>d", nil, named)

Argument model

A plain Go caller may pass int, int64, *big.Int, float64, string, bool, nil, []any, and Symbol; a *NamedArgs, map[string]Value, or map[string]any supplies the hash backing %<name>s and %{name} references. A host that holds its own value objects (an interpreter) implements the Value interface so its objects are formatted directly, without an intermediate copy.

Coverage

Every MRI conversion is implemented:

  • d, i, u — signed decimal integer
  • f — fixed-point float
  • e, E — scientific float
  • g, G — shortest float, trailing zeros trimmed (kept under #)
  • a, A — hexadecimal float
  • s — to_s
  • p — inspect
  • x, X — hexadecimal integer
  • o — octal integer
  • b, B — binary integer
  • c — character (code point or one-character string)
  • %% — a literal percent

with the -, +, space, 0, and # flags; numeric, *, and named width/precision; %n$ absolute argument references; %<name> and %{name} hash references; arbitrary- precision Bignum integers; Ruby's two's-complement ".." notation for negative x/o/b values; the Inf/-Inf/NaN spellings; and the MRI ArgumentError, KeyError, and TypeError messages — all validated against the live CRuby oracle by a broad differential test, with a deterministic golden corpus so the oracle-free CI lanes still exercise the full surface.

Package format implements Ruby's format-string engine — the computation behind Kernel#sprintf, Kernel#format, and String#% — as a standalone, pure-Go (no cgo) library compatible with MRI (CRuby) 4.0.

It is the formatting backend for go-embedded-ruby (rbgo) but depends on nothing from the Ruby runtime: a host binds it by passing its own values through the small Value interface (see value.go), or a plain Go caller passes int/int64/*big.Int/float64/string/bool/nil/[]any and a NamedArgs/map for named references.

Every conversion, flag, width/precision form, named/numbered reference, and the MRI ArgumentError/KeyError/TypeError messages are matched against CRuby byte-for-byte (see the differential test suite).

Code generated from the MRI oracle; DO NOT EDIT. Regenerate with: GEN_GOLDEN=1 go test -run TestGenGolden

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Format

func Format(formatStr string, args []Value, named *NamedArgs) (out string, err error)

Format is the explicit-arg form of Sprintf: positional Values plus the named hash for %<name>/%{name} references (nil when there are none). It is the primary entry point for hosts (rbgo) that already hold Values.

func Sprintf

func Sprintf(formatStr string, args ...any) (string, error)

Sprintf formats per Ruby's Kernel#sprintf / Kernel#format / String#%. The arguments are the Ruby values to format, given either as plain Go values (int, int64, *big.Int, float64, string, bool, nil, []any, Symbol) or as Values; a *NamedArgs or map[string]any/map[string]Value supplies the hash for %<name>s and %{name} references. It returns the formatted string or an *Error carrying the Ruby exception class and message MRI would raise.

Types

type Error

type Error struct {
	Class   string
	Message string
}

Error is the error returned by Sprintf/Format. Class names the Ruby exception class MRI would raise ("ArgumentError", "KeyError", "TypeError") so a host (rbgo) can re-raise the matching Ruby exception; Message is MRI's message text. Error() renders "Class: Message" for standalone Go callers.

func (*Error) Error

func (e *Error) Error() string

Error implements the error interface as "Class: Message".

type Kind

type Kind int

Kind enumerates the value families the conversions dispatch on.

const (
	KindNil Kind = iota
	KindInteger
	KindFloat
	KindString
	KindSymbol
	KindArray
	KindHash
	KindOther
)

The value kinds. Composite kinds (Array/Hash/Other) are rendered through ToS and Inspect; only Integer/Float/String/Symbol/Nil carry conversion-specific behavior.

type NamedArgs

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

NamedArgs supplies the hash backing %<name>s and %{name} references. It is an ordered name->Value map; callers that do not care about iteration order may pass a plain map[string]any to Sprintf, which is wrapped automatically.

func NewNamedArgs

func NewNamedArgs(m map[string]Value) *NamedArgs

NewNamedArgs builds a NamedArgs from a name->Value map. Iteration order is the sorted key order, which only affects ToS/Inspect of the hash itself.

func (*NamedArgs) ClassName

func (n *NamedArgs) ClassName() string

ClassName reports "Hash".

func (*NamedArgs) Float

func (n *NamedArgs) Float() (float64, error, bool)

Float reports that a Hash is not a float.

func (*NamedArgs) Inspect

func (n *NamedArgs) Inspect() string

Inspect renders the hash like Ruby's Hash#inspect, e.g. {x: 1, y: "a"}.

func (*NamedArgs) Int

func (n *NamedArgs) Int() (*big.Int, error, bool)

Int reports that a Hash is not an integer.

func (*NamedArgs) Kind

func (n *NamedArgs) Kind() Kind

Kind reports KindHash.

func (*NamedArgs) ToS

func (n *NamedArgs) ToS() string

ToS renders the hash like Ruby's Hash#to_s (== inspect).

type Symbol

type Symbol string

Symbol is the plain-Go representation of a Ruby Symbol argument, so a Go caller can pass format.Symbol("name") and get :name from %p and name from %s.

type Value

type Value interface {
	// Kind reports which family of conversions the value natively satisfies.
	Kind() Kind
	// ToS is the Ruby to_s rendering, used by %s and as the textual value of
	// %{name} references.
	ToS() string
	// Inspect is the Ruby inspect rendering, used by %p.
	Inspect() string
	// ClassName names the value's Ruby class for TypeError messages
	// ("Integer", "Float", "String", "Symbol", "Array", "Hash", "nil", ...).
	ClassName() string
	// Int returns the value as an arbitrary-precision integer for the integer
	// conversions (d/i/u/x/X/o/b/B/c). ok is false when the value cannot be
	// interpreted as an integer at all (e.g. a Hash); a String that is not a
	// valid Integer() literal reports ok true with a non-nil parse error so the
	// caller can raise ArgumentError rather than TypeError.
	Int() (z *big.Int, err error, ok bool)
	// Float returns the value as a float64 for the float conversions
	// (f/e/E/g/G/a/A). The contract mirrors Int.
	Float() (f float64, err error, ok bool)
}

Value is the argument model for the formatter. It is the bridge between a host language's value space (most notably rbgo's Ruby objects) and what the conversions need: an integer (possibly a Bignum), a float, a string, a symbol, nil, or a composite (array/hash) that the %s/%p conversions render.

Callers may pass plain Go values to Sprintf/Format — int, int64, *big.Int, float64, string, bool, nil, []any, NamedArgs/map — which are adapted to this interface internally (see toValue). Implementing Value directly lets a host (rbgo) format its own objects without an intermediate copy: a Ruby object wrapper need only report its kind plus a to_s and an inspect string and, for numbers, an integer/float view.

Jump to

Keyboard shortcuts

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