argwild

package module
v0.0.1 Latest Latest
Warning

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

Go to latest
Published: Jul 18, 2026 License: MIT Imports: 5 Imported by: 0

README

argwild

argwild is a Go module that parses command lines into a reusable, structured form. It can read the process's own arguments or parse an arbitrary string, and either way it returns a value you can inspect as many times as you like.

It is designed for rich, ffmpeg-style command lines where switches and operands interleave, and it integrates with PawScript's PSL (PawScript Serialized List) format: parenthesized argument values are parsed as PSL, and the entire parse tree can be serialized back to PSL.

import "github.com/phroun/argwild"

r, err := argwild.ParseString(`--verbose -o=2 input.mp4 --codec=x264 output.mp4`)
// r.Stanzas:
//   args[--verbose -o=2]
//   operand(input.mp4)
//   args[--codec=x264]
//   operand(output.mp4)

Installation

go get github.com/phroun/argwild

Requires Go 1.24+. Builds on Linux, macOS, and Windows (amd64 and arm64). The only transitive dependency is github.com/phroun/pawscript (plus golang.org/x/term/golang.org/x/sys) — no GUI toolkits are pulled in.

Concepts

The result of a parse is an ordered sequence of stanzas. Each stanza is one of two things:

  • an ArgSet — a run of consecutive switches, or
  • an Operand — a single positional value.

Operands act as phase dividers. This lets you express "global switches, then an operand, then switches that apply to the next phase, then another operand," exactly like ffmpeg. argwild preserves order faithfully and stays neutral about whether an ArgSet binds to the operand before or after it — that is your call.

Switches

A switch is led by one of three introducers:

Lead Example Notes
- -o Short: the name is exactly one character.
-- --option Long: multi-character name.
+ +32, +x=4 + then a digit is a value-only switch with an empty name (the line-number form); + then a letter behaves like a short switch.

There is no getopt-style clustering: -abc is the switch a with the value bc, not -a -b -c.

Every switch records one of four states:

State Written Meaning
bare -o present
on -o+ explicitly enabled
off -o- explicitly disabled
valued -o=x carries one or more values

The bare / on / off distinction is preserved so you can tell -o from -o+ from -o-.

Values

A switch value can be:

  • bare-oArg
  • quoted-o"Quoted Arg" (" or '; \" and \\ escapes)
  • number-o12 (integer or float)
  • PSL block-o(1, 2, 3) — parsed with pawscript into a PSL list or map

Values comma-chain into lists:

-oA1,A2            -> [A1, A2]
-o"A1","A2"        -> [A1, A2]
-o(1,2),(3,4)      -> [ (1,2), (3,4) ]   (a list of PSL blocks)

-oArg and -o=Arg are equivalent; -o"x" and -o="x" are equivalent. The full grammar applies to short, long, and + switches alike.

Spacing rules

Attached values always bind. After a space, a value binds only if it is quoted, numeric, or a PSL block — a bare word does not:

-o 12                          -> -o = 12
-o "Quoted Arg", "b", 3        -> -o = ["Quoted Arg", "b", 3]
-o NotThis                     -> -o is bare;  NotThis is an operand

A trailing comma binds whatever follows, across spaces and including bare words:

-o 2, 5                        -> -o = [2, 5]
-oA1, NotThis                  -> -o = [A1, "NotThis"]

Because -o NotThis leaves -o bare, binding a bare value needs the attached or = form: -c=x264, not -c x264. (-c x264 yields a bare -c and an operand x264.)

End of options

-- on its own ends option parsing; everything after it is treated as operands. A lone - is an operand (the conventional stdin marker).

Entry points

// Parse a raw string with full fidelity (quoting, spacing, PSL, commas).
r, err := argwild.ParseString(line)

// Parse an already-split argument vector. Elements containing whitespace are
// re-quoted so they are treated as single quoted tokens.
r, err := argwild.ParseArgs(os.Args[1:])

// Convenience for os.Args[1:].
r, err := argwild.Parse()

ParseString is the full-fidelity path and the right choice for command lines a user typed into a prompt. In ParseArgs/Parse, the shell has already removed quotes, so argwild reconstructs an equivalent line by re-quoting any element that contains whitespace; the spacing rules then apply across elements (e.g. {"-o","12"} binds, {"-o","NotThis"} does not).

Inspecting the result

for _, st := range r.Stanzas {
    switch s := st.(type) {
    case *argwild.ArgSet:
        for _, sw := range s.Switches {
            switch {
            case sw.IsOn():      // -o+
            case sw.IsOff():     // -o-
            case sw.HasValues(): // -o=...
                v, _ := sw.First()
                _ = v.Interface() // string | int64 | float64 | PSL value
            default:             // -o (bare)
            }
        }
    case *argwild.Operand:
        _ = s.Value.AsString()
    }
}

// Convenience filters:
r.Operands() // []*Operand in order
r.ArgSets()  // []*ArgSet in order

Value.Interface() returns a plain Go value (string, int64, float64, or a pawscript PSLList/PSLMap); Value.AsString() returns a display string.

PSL round-trip

The whole parse tree maps onto PSL and can be handed back to pawscript:

psl := r.ToPSL()            // pawscript.PSLList of stanza maps
s   := r.ToPSLString(false) // compact PSL string
s   := r.ToPSLString(true)  // pretty, indented

The shape is an ordered list of stanza maps:

operand:  (kind: "operand", value: <value>)
arg set:  (kind: "args", switches: (<switch>, ...))
switch:   (lead: "-"|"--"|"+", name: <string>,
           state: "bare"|"on"|"off"|"valued", values: (<value>, ...))

This serializes with pawscript's PSL serializer and reparses losslessly with pawscript.ParsePSLList.

License

See LICENSE.

Documentation

Overview

Package argwild parses command lines into a reusable, structured form.

argwild is built around three ideas:

  • Switches. A switch is an option token led by "-" (short, single-character name), "--" (long name), or "+" (a value-only or short-style token, used for things like the "+32" line-number convention). Every switch records whether it was bare, explicitly turned on ("-o+"), explicitly turned off ("-o-"), or given one or more values.

  • Operands. Any positional token that is not a switch. Operands act as phase dividers, ffmpeg-style: global switches, an operand, switches that apply to the next phase, another operand, and so on.

  • Stanzas. The parse result is an ordered sequence of stanzas. Each stanza is either an ArgSet (a run of consecutive switches) or an Operand. Order is preserved exactly; argwild stays neutral about whether an ArgSet binds to the operand before or after it — that is the caller's decision.

A switch value may be a bare word, a quoted string, a number, or a PSL block written in parentheses. PSL (PawScript Serialized List) blocks are parsed with the pawscript library, and the entire parse tree can be handed back to pawscript for serialization via Result.ToPSL and Result.ToPSLString.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type ArgSet

type ArgSet struct {
	Switches []Switch
}

ArgSet is a run of consecutive switches with no operand between them.

func (*ArgSet) String

func (a *ArgSet) String() string

String implements fmt.Stringer for debugging.

type Lead

type Lead int

Lead identifies which introducer began a switch.

const (
	// LeadShort is a single dash: "-o". The name is exactly one character and
	// everything after it attaches as a value.
	LeadShort Lead = iota
	// LeadLong is a double dash: "--option". The name may be multiple
	// characters; values attach via "=", a space (for quoted/numeric values),
	// or an immediately following quote or PSL block.
	LeadLong
	// LeadPlus is a plus sign: "+". When followed by a digit the name is empty
	// and the digits are a value (the "+32" line-number form). When followed by
	// a letter it behaves like a short switch: "+x=4".
	LeadPlus
)

func (Lead) String

func (l Lead) String() string

String renders the lead as it appears in source ("-", "--", or "+").

type Operand

type Operand struct {
	Value Value
}

Operand is a single positional value.

func (*Operand) String

func (o *Operand) String() string

String implements fmt.Stringer for debugging.

type ParseError

type ParseError struct {
	Pos int
	Msg string
}

ParseError describes a failure to parse a command line, with the rune offset at which the problem was detected.

func (*ParseError) Error

func (e *ParseError) Error() string

type Result

type Result struct {
	Stanzas []Stanza
}

Result is the top-level parse output: an ordered sequence of stanzas.

func Parse

func Parse() (*Result, error)

Parse parses the current process's command-line arguments (os.Args without the program name) into a Result. It is equivalent to ParseArgs(os.Args[1:]).

func ParseArgs

func ParseArgs(args []string) (*Result, error)

ParseArgs parses an already-split argument vector, such as os.Args[1:].

Each element is an atomic token: elements containing whitespace are re-quoted so the parser treats them as single quoted values, reconstructing an equivalent command line before applying the full grammar. This means the spacing rules still apply across elements — for example ParseArgs([]string{ "-o", "12"}) binds 12 to -o (space before a numeric value binds), while ParseArgs([]string{"-o", "NotThis"}) leaves -o bare and NotThis an operand (space before a bare word does not bind).

For complete control over quoting and spacing, parse a raw string with ParseString instead.

func ParseString

func ParseString(s string) (*Result, error)

ParseString parses a raw command-line string into a Result.

This is the full-fidelity entry point: it understands quoting, the spacing rules, comma-chained values, and parenthesized PSL blocks directly from the text, so it is the right choice for command lines a user typed into a prompt.

Example

ExampleParseString shows the ffmpeg-style stanza decomposition: global switches, an operand, phase switches, another operand.

package main

import (
	"fmt"

	"github.com/phroun/argwild"
)

func main() {
	r, err := argwild.ParseString(`--verbose -o=2 input.mp4 --codec=x264 output.mp4`)
	if err != nil {
		panic(err)
	}
	for _, st := range r.Stanzas {
		fmt.Println(st)
	}
}
Output:
args[--verbose -o=2]
operand(input.mp4)
args[--codec=x264]
operand(output.mp4)

func (*Result) ArgSets

func (r *Result) ArgSets() []*ArgSet

ArgSets returns every argument set in order, ignoring operands.

func (*Result) Operands

func (r *Result) Operands() []*Operand

Operands returns every operand in order, ignoring argument sets. This is a convenience for callers that only care about the phase dividers.

func (*Result) String

func (r *Result) String() string

String implements fmt.Stringer for debugging.

func (*Result) ToPSL

func (r *Result) ToPSL() ps.PSLList

ToPSL converts the whole parse result into a pawscript PSLList so it can be serialized with pawscript's PSL serializer and reparsed losslessly.

The shape is an ordered list of stanza maps:

operand stanza: (kind: "operand", value: <value>)
arg-set stanza: (kind: "args", switches: (<switch>, <switch>, ...))

Each switch is:

(lead: "-"|"--"|"+", name: <string>, state: "bare"|"on"|"off"|"valued",
 values: (<value>, ...))

Values become plain PSL scalars (string / int64 / float64) or, for PSL-block arguments, the nested PSL structure itself.

func (*Result) ToPSLString

func (r *Result) ToPSLString(pretty bool) string

ToPSLString serializes the parse result to a PSL string. When pretty is true the output is indented across multiple lines.

Example

ExampleResult_ToPSLString shows the whole parse serialized to PSL, ready to hand back to the pawscript library.

package main

import (
	"fmt"

	"github.com/phroun/argwild"
)

func main() {
	r, _ := argwild.ParseString(`+32 file.txt -o "a, b", 3`)
	fmt.Println(r.ToPSLString(false))
}
Output:
((kind: "args", switches: ((lead: "+", name: "", state: "valued", values: (32)))), (kind: "operand", value: "file.txt"), (kind: "args", switches: ((lead: "-", name: "o", state: "valued", values: ("a, b", 3)))))

type Stanza

type Stanza interface {

	// String renders the stanza for debugging.
	String() string
	// contains filtered or unexported methods
}

Stanza is one element of a parse result: either an *ArgSet or an *Operand. Use a type switch to distinguish them.

type State

type State int

State describes how a switch was written with respect to on/off/value.

const (
	// StateBare is a switch with no polarity marker and no value: "-o".
	StateBare State = iota
	// StateOn is an explicitly enabled switch: "-o+".
	StateOn
	// StateOff is an explicitly disabled switch: "-o-".
	StateOff
	// StateValued is a switch carrying one or more values. Values is non-empty.
	StateValued
)

func (State) String

func (s State) String() string

String renders the state as a short label.

type Switch

type Switch struct {
	Lead   Lead
	Name   string // "o", "option", or "" for the "+32" value-only form.
	State  State
	Values []Value
}

Switch is a single parsed option.

Example

ExampleSwitch shows inspecting on/off/bare state and typed values.

package main

import (
	"fmt"

	"github.com/phroun/argwild"
)

func main() {
	r, _ := argwild.ParseString(`-a -b+ -c- -d=5`)
	for _, sw := range r.ArgSets()[0].Switches {
		switch {
		case sw.IsOn():
			fmt.Printf("%s: on\n", sw.Name)
		case sw.IsOff():
			fmt.Printf("%s: off\n", sw.Name)
		case sw.HasValues():
			v, _ := sw.First()
			fmt.Printf("%s: value %s\n", sw.Name, v.AsString())
		default:
			fmt.Printf("%s: bare\n", sw.Name)
		}
	}
}
Output:
a: bare
b: on
c: off
d: value 5

func (Switch) First

func (s Switch) First() (Value, bool)

First returns the switch's first value, if any.

func (Switch) HasValues

func (s Switch) HasValues() bool

HasValues reports whether the switch carries one or more values.

func (Switch) IsBare

func (s Switch) IsBare() bool

IsBare reports whether the switch was written with no value and no polarity.

func (Switch) IsOff

func (s Switch) IsOff() bool

IsOff reports whether the switch was explicitly disabled ("-o-").

func (Switch) IsOn

func (s Switch) IsOn() bool

IsOn reports whether the switch was explicitly enabled ("-o+").

func (Switch) String

func (s Switch) String() string

String implements fmt.Stringer for debugging.

type Value

type Value struct {
	Kind ValueKind

	// Text holds the literal text for KindBare and KindQuoted values (without
	// the surrounding quotes), and the original source spelling for KindNumber.
	Text string

	// Int and Float hold the parsed number when Kind == KindNumber. IsFloat
	// reports which one is authoritative.
	Int     int64
	Float   float64
	IsFloat bool

	// PSL holds the parsed PSL structure when Kind == KindPSL. It is a
	// pawscript PSLList or PSLMap (both are Go maps/slices of interface{}), so
	// it embeds directly into a serialized parse tree.
	PSL interface{}
}

Value is a single argument value attached to a switch or standing alone as an operand.

func (Value) AsString

func (v Value) AsString() string

AsString returns a human-readable rendering of the value. For PSL blocks it returns the block's serialized form.

func (Value) Interface

func (v Value) Interface() interface{}

Interface returns the value as a plain Go value suitable for embedding in a PSL structure: string for bare and quoted values, int64 or float64 for numbers, and the parsed PSL value for PSL blocks.

func (Value) String

func (v Value) String() string

String implements fmt.Stringer for debugging.

type ValueKind

type ValueKind int

ValueKind identifies the lexical form of a Value.

const (
	// KindBare is an unquoted, non-numeric word: the "Arg" in "-oArg".
	KindBare ValueKind = iota
	// KindQuoted is a quoted string: the "Quoted Arg" in `-o"Quoted Arg"`.
	KindQuoted
	// KindNumber is an integer or floating-point literal: the 12 in "-o12".
	KindNumber
	// KindPSL is a parenthesized PSL block, parsed into PSL. The parsed result
	// is stored in Value.PSL.
	KindPSL
)

func (ValueKind) String

func (k ValueKind) String() string

String renders the kind as a short label.

Jump to

Keyboard shortcuts

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