Skip to content

amterp/ra

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

62 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Ra

ra-logo

Latest Release Go Reference Go Report Card License: MIT

Flexible CLI argument parsing for Go. Ra lets you build sophisticated command-line tools where arguments work as both positional parameters and named flags - giving your users the flexibility to use whichever style feels natural.

Powers the argument parsing in Rad! ⚡️

Quick Start

go get github.com/amterp/ra
package main

import (
	"fmt"
	"os"
	"github.com/amterp/ra"
)

func main() {
	cmd := ra.NewCmd("myapp")

	// This arg works both positionally AND as a flag
	input, _ := ra.NewString("input").Register(cmd)
	format, _ := ra.NewString("format").SetDefault("json").Register(cmd)
	verbose, _ := ra.NewBool("verbose").SetShort("v").Register(cmd)

	cmd.ParseOrExit(os.Args[1:])

	fmt.Printf("Input: %s, Format: %s, Verbose: %t\n", *input, *format, *verbose)
}

Both of these work identically:

myapp data.txt json --verbose     # positional style
myapp --input data.txt --format json -v  # flag style  

Key Features

  • 🎭 Dual-Nature Arguments: Every argument works as both a positional parameter and a named flag
  • 🔒 Rich Constraints: Enum values, regex patterns, min/max ranges, and relational constraints (requires/excludes)
  • 🧠 Smart Parsing: Bool flag clustering (-abc), negative number handling, variadic arguments
  • 🛡️ Type Safety: Generic flag types with compile-time safety and automatic validation
  • 🌳 Subcommands: Nested commands with global flag inheritance
  • 💫 User-Friendly: Auto-help generation, colorized output, clear error messages

Why Ra?

vs cobra/pflag: While cobra and pflag are excellent for traditional flag-based CLIs, Ra's dual-nature arguments provide more flexibility. In cobra, you have to choose between positional args OR flags - Ra lets you have both.

// Cobra: Separate definitions for flags and positional args
cmd.Flags().StringVarP(&input, "input", "i", "", "input file")
cmd.Args = cobra.ExactArgs(1) // The positional arg is separate

// Ra: A single declaration enables both styles  
input, _ := ra.NewString("input").Register(cmd)
// User can use `myapp file.txt` OR `myapp --input file.txt`
Feature Ra cobra/pflag
Argument Style Dual-nature (flags & positional) Primarily flags-only
POSIX Compliance Flexible / Non-prescriptive Strict patterns
Built-in Validation Rich constraints (enum, regex, ranges) Basic type parsing
Best For User-friendly CLIs with flexible UX Traditional POSIX-style tools

Perfect for CLIs that need more flexibility than basic flag parsing but less complexity than full frameworks.

Example

package main

import (
	"fmt"
	"os"
	"github.com/amterp/ra"
)

func main() {
	cmd := ra.NewCmd("fileproc")
	cmd.SetDescription("A file processor with various output formats")

	// Required input file (positional or --input)
	input, _ := ra.NewString("input").Register(cmd)

	// Format with enum constraint and default
	format, _ := ra.NewString("format").
		SetShort("f").
		SetDefault("json").
		SetEnumConstraint([]string{"json", "xml", "yaml"}).
		Register(cmd)

	// Timeout with range constraint
	timeout, _ := ra.NewInt("timeout").
		SetDefault(30).
		SetMin(1, true).SetMax(300, true).
		Register(cmd)

	// Mutually exclusive flags
	verbose, _ := ra.NewBool("verbose").
		SetShort("v").
		SetExcludes([]string{"quiet"}).
		Register(cmd)

	quiet, _ := ra.NewBool("quiet").
		SetShort("q").
		SetExcludes([]string{"verbose"}).
		Register(cmd)

	// Variadic tags
	tags, _ := ra.NewStringSlice("tags").
		SetVariadic(true).
		SetOptional(true).
		Register(cmd)

	// Subcommand
	validateCmd := ra.NewCmd("validate")
	validateInput, _ := ra.NewString("input").Register(validateCmd)
	strict, _ := ra.NewBool("strict").Register(validateCmd)
	validateUsed, _ := cmd.RegisterCmd(validateCmd)

	// Parse
	err := cmd.ParseOrError(os.Args[1:])
	if err == ra.HelpInvokedErr {
		return // Help was shown
	} else if err != nil {
		fmt.Fprintf(os.Stderr, "Error: %v\n", err)
		os.Exit(1)
	}

	// Handle subcommand
	if *validateUsed {
		fmt.Printf("Validating: %s (strict: %t)\n", *validateInput, *strict)
		return
	}

	// Main processing
	fmt.Printf("Processing %s -> %s format (timeout: %ds)\n",
		*input, *format, *timeout)

	if *verbose {
		fmt.Println("Verbose mode enabled")
	} else if *quiet {
		fmt.Println("Quiet mode enabled")
	}

	if len(*tags) > 0 {
		fmt.Printf("Tags: %v\n", *tags)
	}
}

Usage examples:

# All equivalent ways to call the same thing:
fileproc data.txt                           # positional
fileproc --input data.txt                   # flag style
fileproc data.txt --format xml -v          # mixed style
fileproc data.txt xml 60 --tags api,prod   # mostly positional

# Subcommand
fileproc validate config.json --strict

# Auto-generated help
fileproc --help
# Output:
# A file processor with various output formats
#
# Usage:
#   fileproc [validate] <input> [format] [timeout] [OPTIONS]
#
# Commands:
#   validate
#
# Arguments:
#       --input str         
#   -f, --format str        Default: json. Valid values: [json, xml, yaml]
#       --timeout int       Default: 30. Range: [1, 300]
#   -v, --verbose           Excludes: quiet
#   -q, --quiet             Excludes: verbose  
#       --tags [strs...]    (optional)
#   -h, --help              Print usage information

# Constraint validation errors
fileproc data.txt --format csv
# Output: Error: Invalid 'format' value: csv (valid values: json, xml, yaml)

fileproc data.txt --timeout 500  
# Output: Error: 'timeout' value 500 is > maximum 300

Documentation

Requirements

  • Go 1.24+

License

MIT License.

Contributing

See CONTRIBUTING.md for development guidelines.

About

Flexible CLI argument parsing for Go. Powers Rad!

Topics

Resources

License

Contributing

Stars

Watchers

Forks

Packages

No packages published