pico-args

An extremely lightweight and simple argument parser. Features minimal functionality and fast processing.

rustcliminimallightweight

Framework

pico-args

Overview

pico-args is an extremely lightweight and simple argument parser. It features minimal functionality and fast processing, implemented with zero dependencies. It's suitable for simple CLI tools or embedded systems, chosen when lightweight nature is prioritized.

Details

pico-args is a minimal command-line argument parser developed by RazrFalcon. As the name "pico" suggests, it's designed for ultra-small size and high-speed processing, with no dependencies other than the standard library. By focusing only on basic argument parsing without providing complex features, it achieves lightweight nature and high performance.

Key Features

  • Ultra-lightweight: Very small footprint and minimal dependencies
  • Fast Processing: High-speed argument parsing with minimal overhead
  • Simple API: Intuitive and easy-to-understand API design
  • Zero Dependencies: No dependencies other than the standard library
  • Type Safety: Safe argument processing leveraging Rust's type system
  • Custom Parsers: Enables implementation of custom value conversion functions
  • Error Handling: Proper error handling with clear error types

Pros and Cons

Pros

  • Minimal Footprint: Very lightweight with minimal resource usage
  • High Speed: Extremely low overhead for argument parsing
  • Simple: Low learning cost, can start using immediately
  • No Dependencies: Independence without relying on external libraries
  • Embedded-friendly: Suitable for resource-constrained environments

Cons

  • Limited Features: Advanced CLI features (help generation, subcommands) not provided
  • Manual Implementation: Help messages and complex structures must be manually implemented
  • Not for Large Scale: Unsuitable for complex CLI applications
  • Ecosystem: Limited integration features with other CLI libraries

Key Links

Example Usage

use pico_args;
use std::path::PathBuf;

#[derive(Debug)]
struct Config {
    // Required numeric parameter
    count: u32,
    // Optional string parameter
    name: Option<String>,
    // Parameter with default value
    timeout: u64,
    // Input file path
    input: PathBuf,
    // Output file path (optional)
    output: Option<PathBuf>,
    // Flag
    verbose: bool,
}

fn main() {
    let config = match parse_args() {
        Ok(config) => config,
        Err(e) => {
            eprintln!("Argument parsing error: {}", e);
            std::process::exit(1);
        }
    };

    println!("Config: {:#?}", config);
    
    if config.verbose {
        println!("Verbose mode enabled");
    }
    
    println!("Input file: {:?}", config.input);
    if let Some(output) = &config.output {
        println!("Output file: {:?}", output);
    }
}

fn parse_args() -> Result<Config, pico_args::Error> {
    let mut pargs = pico_args::Arguments::from_env();

    // Check for help flag
    if pargs.contains(["-h", "--help"]) {
        print_help();
        std::process::exit(0);
    }

    // Parse each argument
    let config = Config {
        // Required parameter (using custom parser)
        count: pargs.value_from_fn("--count", |s| {
            s.parse::<u32>().map_err(|_| "number required")
        })?,
        
        // Optional parameter
        name: pargs.opt_value_from_str("--name")?,
        
        // Parameter with default value
        timeout: pargs.opt_value_from_str("--timeout")?.unwrap_or(30),
        
        // Flag (whether it exists)
        verbose: pargs.contains(["-v", "--verbose"]),
        
        // Required positional argument (input file)
        input: pargs.free_from_str()?,
        
        // Optional positional argument (output file)
        output: pargs.opt_free_from_str()?,
    };

    // Check for unused arguments
    let remaining = pargs.finish();
    if !remaining.is_empty() {
        eprintln!("Warning: unrecognized arguments: {:?}", remaining);
    }

    Ok(config)
}

fn print_help() {
    const HELP: &str = "\
Usage: myapp [OPTIONS] <input-file> [output-file]

Options:
    --count COUNT       Number of processes (required)
    --name NAME         Process name (optional)
    --timeout SECONDS   Timeout in seconds (default: 30)
    -v, --verbose       Enable verbose output
    -h, --help          Show this help

Arguments:
    <input-file>        Input file to process
    [output-file]       Output file destination (optional)

Examples:
    myapp --count 10 --verbose input.txt output.txt
    myapp --count 5 --name \"test\" input.txt
";
    print!("{}", HELP);
}

// Advanced usage example: custom parsers and complex types
fn advanced_example() -> Result<(), pico_args::Error> {
    let mut pargs = pico_args::Arguments::from_env();
    
    // Parse size with custom parser (e.g., "10MB", "1GB")
    let size: u64 = pargs.value_from_fn("--size", |s| {
        parse_size(s).ok_or("invalid size format")
    })?;
    
    // Get multiple values (comma-separated)
    let tags: Vec<String> = pargs.opt_value_from_fn("--tags", |s| {
        Ok(s.split(',').map(|s| s.trim().to_string()).collect())
    })?.unwrap_or_default();
    
    println!("Size: {} bytes", size);
    println!("Tags: {:?}", tags);
    
    Ok(())
}

fn parse_size(s: &str) -> Option<u64> {
    let s = s.to_lowercase();
    if let Some(s) = s.strip_suffix("gb") {
        s.parse::<u64>().ok().map(|n| n * 1_000_000_000)
    } else if let Some(s) = s.strip_suffix("mb") {
        s.parse::<u64>().ok().map(|n| n * 1_000_000)
    } else if let Some(s) = s.strip_suffix("kb") {
        s.parse::<u64>().ok().map(|n| n * 1_000)
    } else {
        s.parse().ok()
    }
}