Skip to content

go-ruby-optparse/optparse

Repository files navigation

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.

About

Pure-Go MRI-compatible Ruby optparse, CGO=0 — bound by go-embedded-ruby/rbgo

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages