optparse

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

README

go-ruby-optparse/optparse

optparse — go-ruby-optparse

Docs License Go Coverage

A pure-Go (no cgo) reimplementation of the deterministic, interpreter-independent core of Ruby's OptionParser (stdlib optparse) — the argv-parsing engine. It models the option specifications declared with on(...), tokenizes an argv, performs long/short/abbreviation matching, handles =/bundled/--[no-]/optional-argument forms, coerces values, and reports the full OptionParser::ParseError taxonomy — matching MRI 4.0.5 byte-for-byte.

It is the OptionParser backend for go-embedded-ruby, but is a standalone, reusable module with no dependency on the Ruby runtime.

What it is — and isn't. Modeling the option specs, tokenizing argv, matching long/short/abbreviated names, coercing arguments (Integer/Float/Array/custom lists) and computing the error taxonomy is fully deterministic and needs no interpreter, so it lives here as pure Go. The per-match Ruby blocks that on(...) registers do need a Ruby interpreter and stay in the consumer (e.g. rbgo): this library parses argv and returns the ordered matches + coerced values + leftover operands + MRI-exact errors, and the host dispatches the blocks over those matches.

Features

Faithful port of MRI's lib/optparse.rb parsing engine, validated against the ruby binary on every supported platform:

  • All flag forms — long --name, short -n, both -n, --name.
  • Argument styles — required --name VALUE / -n VALUE, optional --name [VALUE], =-joined --name=VALUE / -n=VALUE, glued short -nVALUE.
  • Bundled shorts-xvf, with the last switch in a bundle taking its argument.
  • Negation--[no-]flag matches both --flag (true) and --no-flag (false).
  • Abbreviation & completion — unique-prefix long abbreviation (--verb--verbose), short→long completion (-n--name), with AmbiguousOption on a tie.
  • CoercionInteger (decimal + 0x/0b/0o, _ separators, signed, arbitrary precision), Float, Array (comma-split), String, and custom candidate lists / value maps with prefix completion.
  • Parse modesparse! / permute! (options anywhere), order! (stop at the first operand, or a callback per operand), and getopts.
  • Terminators-- ends option parsing; a bare - is an operand.
  • Full error treeInvalidOption, MissingArgument, InvalidArgument, AmbiguousOption, AmbiguousArgument, NeedlessArgument, each with MRI-exact message / reason / args and recover.
  • Help layouthelp / to_s / summarize with MRI's column alignment, separators, and on_head ordering.

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).

Usage

package main

import (
	"fmt"

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

func main() {
	p := optparse.New()
	p.Banner = "Usage: tool [options] FILE..."

	// Declare options from MRI `on(...)` flag strings (+ optional coercion).
	verbose := p.Define([]string{"-v", "--verbose", "run verbosely"}, "", nil, nil)
	count := p.Define([]string{"--count N", "how many"}, optparse.CoerceInteger, nil, nil)
	mode := p.Define([]string{"--mode M", "fast|slow"},
		optparse.CoerceList, []string{"fast", "slow"}, nil)

	matches, rest, err := p.ParseBang([]string{"-v", "--count", "0x10", "--mode", "fa", "file.txt"})
	if err != nil {
		// err is a *optparse.ParseError with MRI-exact Error()/Class()/Args.
		fmt.Println(err) // e.g. "invalid argument: --count xx"
		return
	}

	// Dispatch: in rbgo each match's SpecIndex selects the Ruby block to call.
	for _, m := range matches {
		switch m.SpecIndex {
		case verbose:
			fmt.Println("verbose:", m.Value) // true
		case count:
			fmt.Println("count:", m.Value) // int64(16)
		case mode:
			fmt.Println("mode:", m.Value) // "fast"
		}
	}
	fmt.Println("operands:", rest) // ["file.txt"]
	fmt.Print(p.Help())
}

MakeSpec builds an optparse.Spec directly from on(...) strings; On / OnHead register a Spec and return its index; Order, Permute and Getopts cover the remaining MRI parse entry points.

Tests & coverage

The test suite is differential: every case is parsed by both this engine and the same pure-Ruby OptionParser that go-embedded-ruby ships (extracted into testdata/optionparser.rb), and the serialized result — matches, coerced values, leftovers, or the error class/reason/args/message — is compared byte-for-byte. The Ruby oracle self-skips where ruby is absent (and binds $stdout to binary so Windows never injects CRLF); a parallel set of deterministic, ruby-free tests locks the same expectations and reaches 100% coverage on its own, so the no-ruby and qemu CI lanes stay green.

go test ./...                                   # full suite (uses ruby if present)
go test -race -coverprofile=cover.out ./...     # race + coverage
go tool cover -func=cover.out | tail -1         # total: 100.0%

License

BSD-3-Clause. See LICENSE.

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 optparse is a pure-Go (no cgo) reimplementation of the deterministic, interpreter-independent core of Ruby's OptionParser (stdlib `optparse`).

It models option *specifications* (the `on(...)` definitions), tokenizes an argv, performs long/short/abbreviation matching, handles `=`/bundled/`--[no-]`/ optional-argument forms, coerces values (Integer/Float/Array/custom lists), and reports the full OptionParser::ParseError taxonomy with MRI-exact message/reason/args/recover. It is the parsing engine; the per-match Ruby blocks registered by `on(...)` are *not* run here — Parse returns the ordered matches and coerced values, and the consumer (e.g. rbgo) dispatches the blocks.

The behavior mirrors MRI 4.0.5 / go-embedded-ruby's pure-Ruby OptionParser byte-for-byte across a broad differential corpus.

Index

Constants

View Source
const (
	CoerceNone    = ""
	CoerceString  = "String"
	CoerceInteger = "Integer"
	CoerceFloat   = "Float"
	CoerceArray   = "Array"
	// CoerceList selects the candidate-list converter built from Spec.List.
	CoerceList = "List"
)

Coercion names the value converter applied to a matched argument before it is reported in a Match. The empty string means "no coercion" (the raw string is reported). Built-in names mirror MRI's accept defaults; CoerceList carries an explicit candidate set instead (see Spec.List).

Variables

This section is empty.

Functions

This section is empty.

Types

type ArgStyle

type ArgStyle int

ArgStyle describes whether a switch takes an argument and how.

const (
	// ArgNone is a flag with no argument.
	ArgNone ArgStyle = iota
	// ArgRequired is a switch with a mandatory argument ("--name VALUE").
	ArgRequired
	// ArgOptional is a switch with an optional argument ("--name [VALUE]").
	ArgOptional
)

type ErrorKind

type ErrorKind int

ErrorKind identifies which node of the OptionParser::ParseError tree an error belongs to. It maps 1:1 onto MRI's exception classes.

const (
	// KindParseError is the base OptionParser::ParseError.
	KindParseError ErrorKind = iota
	// KindInvalidOption is OptionParser::InvalidOption.
	KindInvalidOption
	// KindMissingArgument is OptionParser::MissingArgument.
	KindMissingArgument
	// KindInvalidArgument is OptionParser::InvalidArgument.
	KindInvalidArgument
	// KindAmbiguousOption is OptionParser::AmbiguousOption.
	KindAmbiguousOption
	// KindAmbiguousArgument is OptionParser::AmbiguousArgument (an InvalidArgument).
	KindAmbiguousArgument
	// KindNeedlessArgument is OptionParser::NeedlessArgument.
	KindNeedlessArgument
)

type GetoptsResult

type GetoptsResult struct {
	// TakesArg is true for "name:" specs (the option takes an argument).
	TakesArg bool
	// Flag is the boolean value for a no-argument option (false if absent).
	Flag bool
	// Str is the supplied argument for an arg-taking option, or nil if absent.
	Str *string
}

GetoptsResult is the value of one getopts entry: a boolean flag, or a string argument (nil Str + TakesArg means the arg-taking option was absent).

type Match

type Match struct {
	// SpecIndex is the index (registration order) of the matched Spec.
	SpecIndex int
	// Value is the coerced argument. For a flag it is a bool: true, or false for
	// the negated form of a "--[no-]flag". For an optional argument that was not
	// supplied it is nil. Otherwise it is the coerced argument (string, int64,
	// *big.Int, float64, []string, or a candidate-list value).
	Value any
	// Raw is the argument string as it appeared (before coercion), or "" for a flag.
	Raw string
}

Match is one resolved option occurrence. The consumer dispatches the Ruby block registered for SpecIndex, passing Value.

type ParseError

type ParseError struct {
	// Kind selects the ParseError subtype.
	Kind ErrorKind
	// Args are the offending tokens, in MRI order. Error() renders them as
	// "<reason>: <args joined by space>".
	Args []string
}

ParseError is the Go analogue of OptionParser::ParseError. Its Error string, Reason, Args, Class and Recover all match MRI byte-for-byte.

func (*ParseError) Class

func (e *ParseError) Class() string

Class returns the MRI exception class name (e.g. "OptionParser::InvalidOption"), for a consumer that re-raises the matching Ruby exception.

func (*ParseError) Error

func (e *ParseError) Error() string

Error renders the message exactly as MRI's #message: "reason: arg arg".

func (*ParseError) Reason

func (e *ParseError) Reason() string

Reason returns the MRI class-level reason string (e.g. "invalid option").

func (*ParseError) Recover

func (e *ParseError) Recover(argv []string) []string

Recover prepends this error's offending Args to argv and returns it, mirroring MRI's ParseError#recover (argv[0,0] = @args).

type Parser

type Parser struct {

	// Banner is the first help line; empty means "Usage: <ProgramName> [options]".
	Banner string
	// ProgramName is used in the default banner; empty means "optparse".
	ProgramName string
	// SummaryWidth is the option-column width (MRI default 32).
	SummaryWidth int
	// SummaryIndent is the left indent (MRI default "    ").
	SummaryIndent string
	// contains filtered or unexported fields
}

Parser holds the registered option specs plus the help/summary layout. The zero value is not ready; use New.

func New

func New() *Parser

New returns a Parser with MRI's default summary layout.

func (*Parser) Define

func (p *Parser) Define(opts []string, coerce string, list, values []string) int

Define registers an option from raw MRI `on(...)` flag/description strings, returning the assigned spec index. It is sugar over MakeSpec + On.

func (*Parser) Getopts

func (p *Parser) Getopts(argv []string, specs ...string) (map[string]*GetoptsResult, error)

Getopts implements MRI #getopts: it parses each single-letter/word from the spec strings (a trailing ":" means the option takes an argument), registers the corresponding switches, parses argv with ParseBang, and returns a map from option name to its result. It mirrors the pure-Ruby OptionParser#getopts, including its scan grammar (so "ab" is one name "ab", not two flags).

On a parse error the partially-built result and the error are returned.

func (*Parser) Help

func (p *Parser) Help() string

Help renders the full help text (MRI #help / #to_s).

func (*Parser) On

func (p *Parser) On(spec Spec) int

On registers a Spec (MRI #on / #on_tail) and returns the assigned spec index.

func (*Parser) OnHead

func (p *Parser) OnHead(spec Spec) int

OnHead registers a Spec at the head of the help list (MRI #on_head).

func (*Parser) Order

func (p *Parser) Order(argv []string, nonOpt func(string)) (matches []Match, rest []string, err error)

Order is MRI #order!: parsing stops at the first non-option, which (with all the remaining argv) is returned as rest. If nonOpt is non-nil it is instead called for each leading non-option token and parsing continues (MRI's block form).

func (*Parser) ParseBang

func (p *Parser) ParseBang(argv []string) (matches []Match, rest []string, err error)

ParseBang consumes argv as MRI #parse! (== #permute!): options anywhere, with operands gathered and returned as rest. It returns the ordered matches (for the consumer to dispatch the blocks), the leftover operands, and the first *ParseError encountered.

func (*Parser) Permute

func (p *Parser) Permute(argv []string) (matches []Match, rest []string, err error)

Permute is MRI #permute! (the same as ParseBang).

func (*Parser) Separator

func (p *Parser) Separator(text string) *Parser

Separator appends a literal line to the help output (MRI #separator).

func (*Parser) Summarize

func (p *Parser) Summarize() []string

Summarize returns the per-switch/separator help lines (MRI #summarize), each already terminated with "\n". It does not include the banner.

type Spec

type Spec struct {
	// Short are the short forms, each including the leading dash (e.g. "-v").
	Short []string
	// Long are the long forms, each including the leading dashes (e.g. "--verbose").
	// For a negatable switch this holds the affirmative form only ("--color"); the
	// "--no-color" alias is synthesized at registration time.
	Long []string
	// ArgStyle is none/required/optional.
	ArgStyle ArgStyle
	// ArgName is the placeholder shown in help (e.g. "VALUE"), or "" if none.
	ArgName string
	// Negatable marks a "--[no-]flag" switch.
	Negatable bool
	// Coerce names the value converter (CoerceInteger, CoerceFloat, ...). Empty
	// means no coercion.
	Coerce string
	// List is the ordered candidate set for CoerceList. For a value→value map use
	// Keys with parallel Values; for a plain list leave Values nil and the key is
	// returned as itself.
	List []string
	// Values, when non-nil and the same length as List, supplies the value each
	// candidate maps to (MRI's Hash form of `on(...)`). Stored as the raw matched
	// candidate string; the consumer maps it back to a Ruby object.
	Values []string
	// Desc are the description lines shown in help.
	Desc []string
}

Spec is the parsed form of one MRI `on(...)` declaration. A consumer builds it from the Ruby call (rbgo maps the `on(*args, &block)` arguments onto these fields) and registers it with Parser.On; the matching Ruby block is held by the consumer and dispatched against the resulting Match.

func MakeSpec

func MakeSpec(opts []string, coerce string, list, values []string) Spec

MakeSpec builds a Spec from the string arguments of an MRI `on(...)` call (the option flag strings such as "-v", "--verbose", "--name VALUE", "--[no-]color", "--name [VALUE]", and any trailing description strings). The optional coerce argument supplies a type/list converter that MRI would pass as a Class, Array, or Hash positional; pass "" for none.

For a candidate list, set coerce to CoerceList and provide list (and optionally values); for a built-in type pass CoerceInteger/CoerceFloat/CoerceArray/ CoerceString. rbgo maps the Ruby `on(*args)` positional arguments onto these.

Jump to

Keyboard shortcuts

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