Skip to content

AutoPallet/roargs

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

37 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

roargs

A Rust argument parser optimized for build time and binary size, whose parsers are .rodata not code.

Roargs is also unusually powerful in allowing flexible composition of sub-parsers and argument groups using algebraic data types.

Most derive-based parsers generate an imperative parse function per struct. Unfortunately, this is a lot of work for the compiler and linker, and can produce large binaries on embedded. roargs instead emits one small static descriptor per struct and ships a single shared engine that interprets them.

For our application, we have an embedded application that includes >100 distinct argument parsers, many of which share common arguments, so this difference really matters.

Measured against gumdrop on 124 small parser structs in one binary (opt-level = "s", details in research/):

roargs gumdrop
marginal size per parser struct 495 B 1,809 B
cold build, 30 parsers 0.86 s 2.92 s
dependencies (incl. derive) 0 syn 1.x

Status This crate is being used internally, but is still under active development and the API may change.

Installation

[dependencies]
roargs = { git = "https://github.com/AutoPallet/roargs.git" }

Examples

use roargs::Options;

/// Shared logging flags.
#[derive(Options)]
struct Logging {
    /// print each step
    verbose: bool,
    /// silence everything else
    quiet: bool,
}

/// Feed the cat.
#[derive(Options)]
struct Feed {
    /// grams of kibble
    #[options(default = "20.0")]
    grams: f32,
    #[options(flatten)]
    logging: Logging,
    // Vec<> arguments can be repeated
    name: Vec<String>
}

fn main() {
    // argv convenience wrapper: prints help/errors and exits.
    let args: Feed = roargs::parse_args();
    if args.logging.verbose {
        println!("dispensing {} g", args.grams);
    }
}
use roargs::Options;

/// Creep silently closer.
#[derive(Options)]
struct Stalk {
    /// distance to close, in tail-lengths
    #[options(required)]
    dist: f32,
}

/// Commit to the leap.
#[derive(Options)]
struct Pounce {
    /// how high to arc
    #[options(required)]
    height: f32,
    /// wiggle before takeoff
    wiggle: bool,
}

#[derive(Options)]
enum Tactic {
    Stalk(Stalk),
    Pounce(Pounce),
}

/// Send the cat after the mouse.
#[derive(Options)]
struct Chase {
    #[options(free, required)]
    x: f32,
    #[options(free, required)]
    y: f32,
    /// Rehearse without moving.
    dry_run: bool,
    tactic: Option<Tactic>,  // scoped enum: `--tactic pounce --height 2.0`
}

fn main() {
    let c: Chase = roargs::parse_args();
    let how = match c.tactic {
        Some(Tactic::Stalk(s)) => format!("stalking the last {} tail-lengths", s.dist),
        Some(Tactic::Pounce(p)) if p.wiggle => format!("wiggle-pouncing {} high", p.height),
        Some(Tactic::Pounce(p)) => format!("pouncing {} high", p.height),
        None => "just ambling over".to_string(),
    };
    let dry = if c.dry_run { "[dry run] " } else { "" };
    println!("{dry}cat charges to ({}, {}), {how}", c.x, c.y);
}

Subcommands and nested parsers

Any parser can be embedded into another parser in one of several ways:

#[derive(Options)]
struct SubParser {
  flag: bool
}

#[derive(Options)]
struct Cmd {
  /// All flags and arguments are treated as part of this command at the top-level,
  /// but grouped visually in help text.
  #[options(flatten)]
  flattened: SubParser,

  /// The user must pass --nested followed by (optionally) flags from SubParser. 
  /// Passing any other flag from Cmd will exit the nested scope.
  nested: SubParser,

  /// Like other flags, subparsers can be optional
  maybe: Option<SubParser>,
}

// Example usage: cmd --flag --nested --flag --maybe

derive(Options) on an enum produces a subcommand parser, which can be used positionally to approximate a normal subcommand or used directly as a normal or repeated argument.

#[derive(Options)]
enum Subcommand {
    Status,
    /// bare value payload: `commit "message"`
    Commit(String),
}

#[derive(Options)]
struct Git {
    verbose: bool,
    #[options(free)]
    cmd: Subcommand,
}

Supported Attributes

Attribute Applies to Effect
free fields positional: bare tokens fill values
required fields error if the field is never set
long = "name" fields, enum variants override the auto kebab-cased name
short = "c" fields explicit short flag (auto shorts: unique first letter, never h)
help = "text" fields help text (/// doc comments work too)
default = "20.0" fields value parsed via FromStr when the flag is absent
default_expr = "…" fields Rust expression evaluated as the default
flatten struct fields inline the sub-struct's fields, no opener token
command enum fields classic subcommand: the rest of the line belongs to it
default unit enum variants variant taken when nothing selects one (std #[default] is honored too)

Feature notes that used to live here (scoped-nesting semantics, auto short-flag rules, arg_value!, the no-print engine contract and host helpers, VersionAbort) moved to docs/features.md. Design notes: research/descriptor-format.md; benchmark harness: research/size-bench/.

License

MIT or Apache-2.0, at your option.

About

No description, website, or topics provided.

Resources

License

Apache-2.0, MIT licenses found

Licenses found

Apache-2.0
LICENSE-APACHE
MIT
LICENSE-MIT

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors