pico-args

極めて軽量でシンプルな引数パーサー。最小限の機能と高速な処理が特徴です。

rustcliminimallightweight

フレームワーク

pico-args

概要

pico-argsは、極めて軽量でシンプルな引数パーサーです。最小限の機能と高速な処理が特徴で、依存関係ゼロで実装されています。シンプルなCLIツールや組み込みシステムでの使用に適しており、軽量性を重視する場合に選択されます。

詳細

pico-argsは、RazrFalconによって開発された最小限のコマンドライン引数パーサーです。「pico」という名前が示すように、極小サイズと高速処理を目指して設計されており、標準ライブラリ以外の依存関係を持ちません。複雑な機能は提供せず、基本的な引数解析のみに焦点を当てることで、軽量性と高いパフォーマンスを実現しています。

主な特徴

  • 極軽量: 非常に小さなフットプリントと最小限の依存関係
  • 高速処理: オーバーヘッドを最小限に抑えた高速な引数解析
  • シンプルAPI: 直感的で理解しやすいAPIデザイン
  • ゼロ依存: 標準ライブラリ以外の依存関係なし
  • 型安全: Rustの型システムを活用した安全な引数処理
  • カスタムパーサー: 独自の値変換関数の実装が可能
  • エラーハンドリング: 明確なエラー型による適切なエラー処理

メリット・デメリット

メリット

  • 最小限のフットプリント: 非常に軽量でリソース使用量が少ない
  • 高速: 引数解析のオーバーヘッドが極めて少ない
  • シンプル: 学習コストが低く、すぐに使い始められる
  • 依存関係なし: 外部ライブラリに依存しない独立性
  • 組み込み向け: リソース制約がある環境に適している

デメリット

  • 機能制限: 高度なCLI機能(ヘルプ生成、サブコマンド)は提供されない
  • 手動実装: ヘルプメッセージや複雑な構造は手動で実装する必要
  • 大規模向けではない: 複雑なCLIアプリケーションには不向き
  • エコシステム: 他のCLIライブラリとの統合機能は限定的

主要リンク

書き方の例

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

#[derive(Debug)]
struct Config {
    // 必須の数値パラメータ
    count: u32,
    // オプショナルな文字列パラメータ
    name: Option<String>,
    // デフォルト値を持つパラメータ
    timeout: u64,
    // 入力ファイルパス
    input: PathBuf,
    // 出力ファイルパス(オプション)
    output: Option<PathBuf>,
    // フラグ
    verbose: bool,
}

fn main() {
    let config = match parse_args() {
        Ok(config) => config,
        Err(e) => {
            eprintln!("引数解析エラー: {}", e);
            std::process::exit(1);
        }
    };

    println!("設定: {:#?}", config);
    
    if config.verbose {
        println!("詳細モードが有効です");
    }
    
    println!("入力ファイル: {:?}", config.input);
    if let Some(output) = &config.output {
        println!("出力ファイル: {:?}", output);
    }
}

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

    // ヘルプフラグの確認
    if pargs.contains(["-h", "--help"]) {
        print_help();
        std::process::exit(0);
    }

    // 各引数の解析
    let config = Config {
        // 必須パラメータ(カスタムパーサー使用)
        count: pargs.value_from_fn("--count", |s| {
            s.parse::<u32>().map_err(|_| "数値が必要です")
        })?,
        
        // オプショナルパラメータ
        name: pargs.opt_value_from_str("--name")?,
        
        // デフォルト値付きパラメータ
        timeout: pargs.opt_value_from_str("--timeout")?.unwrap_or(30),
        
        // フラグ(存在するかどうか)
        verbose: pargs.contains(["-v", "--verbose"]),
        
        // 必須の位置引数(入力ファイル)
        input: pargs.free_from_str()?,
        
        // オプショナルな位置引数(出力ファイル)
        output: pargs.opt_free_from_str()?,
    };

    // 未使用の引数をチェック
    let remaining = pargs.finish();
    if !remaining.is_empty() {
        eprintln!("警告: 未認識の引数: {:?}", remaining);
    }

    Ok(config)
}

fn print_help() {
    const HELP: &str = "\
使用法: myapp [オプション] <入力ファイル> [出力ファイル]

オプション:
    --count COUNT       処理回数(必須)
    --name NAME         処理名(オプション)
    --timeout SECONDS   タイムアウト秒数(デフォルト: 30)
    -v, --verbose       詳細出力を有効にする
    -h, --help          このヘルプを表示

引数:
    <入力ファイル>       処理する入力ファイル
    [出力ファイル]       出力先ファイル(オプション)

例:
    myapp --count 10 --verbose input.txt output.txt
    myapp --count 5 --name \"テスト\" input.txt
";
    print!("{}", HELP);
}

// より高度な使用例:カスタムパーサーと複雑な型
fn advanced_example() -> Result<(), pico_args::Error> {
    let mut pargs = pico_args::Arguments::from_env();
    
    // カスタムパーサーでサイズを解析(例: "10MB", "1GB")
    let size: u64 = pargs.value_from_fn("--size", |s| {
        parse_size(s).ok_or("無効なサイズ形式")
    })?;
    
    // 複数の値を取得(カンマ区切り)
    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);
    println!("タグ: {:?}", 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()
    }
}