PrivateRookie

PrivateRookie

多少事,从来急.天地转,光阴迫.

2023-05-11-如何鼓掌

HowTo 系列旨在提供常見工程問題解決辦法,多包含示例代碼。本期的主題是命令行解析工具 clap

添加依賴#

cargo add clap -F derive -F env

從環境變量讀取參數值#

使用 env 屬性可以讓 clap 嘗試從環境變量中讀取。

use clap::Parser;

#[derive(Parser)]
struct Cmd {
    #[arg(env="USER")]
    user: String,
}

fn main() {
    let cmd = Cmd::parse();
    println!("user: {}", cmd.user);
}
echo $USER
rookie
cargo run
user: rookie
# 從命令行傳入的值會覆蓋環境變量值
cargo run -- xd
user: xd

校驗參數 / 轉換#

常見場景如傳入路徑時需要校驗路徑是否存在。

use std::path::PathBuf;

use clap::Parser;

#[derive(Parser)]
struct Cmd {
    #[arg(value_parser=ensure_file)]
    file: PathBuf,
}

fn ensure_file(path: &str) -> Result<PathBuf, String> {
    let path = PathBuf::from(path);
    if path.exists() {
        Ok(path)
    } else {
        Err("file not exists".into())
    }
}

fn main() {
    let cmd = Cmd::parse();
    dbg!(cmd.file);
}
cargo run -- Cargo.toml
[src/main.rs:22] cmd.file = "Cargo.toml"
# 文件不存在時報錯
cargo run -- Cargo.tomx
error: invalid value 'Cargo.tomx' for '<FILE>': file not exists

當然也可以一步到位將直接讀取路徑後解序列化成某個類型,這在讀取配置文件時非常有用。

cargo add serde -F derive
cargo add toml
use std::fs::read_to_string;

use clap::Parser;
use serde::Deserialize;

#[derive(Parser)]
struct Cmd {
    #[arg(value_parser=load_conf)]
    conf: Config,
}

#[derive(Debug, Clone, Deserialize)]
pub struct Config {
    pub name: String,
}

fn load_conf(path: &str) -> Result<Config, String> {
    let s = read_to_string(path).map_err(|e| e.to_string())?;
    toml::from_str(&s).map_err(|e| e.to_string())
}

fn main() {
    let cmd = Cmd::parse();
    dbg!(cmd.conf);
}
echo 'name = "rookie"' > demo.toml
cargo run -- demo.toml
[src/main.rs:24] cmd.conf = Config {
    name: "rookie",
}
cargo run -- demo.tomx
error: invalid value 'demo.tomx' for '<CONF>': No such file or directory (os error 2)

自動展開#

創建有多個子命令的程序時,不同子命令往往共享某些參數,可以將這些參數放入同一個結構體,方便管理,同時借助 #[command(flatten)] 屬性,可以在解析時自動展開對應的命令行參數。

use std::path::PathBuf;

use clap::Parser;

#[derive(Debug, Parser)]
enum Cmd {
    Fetch(FetchCmd),
    Search(SearchCmd),
}

#[derive(Debug, Parser)]
struct FetchCmd {
    name: String,
    #[command(flatten)]
    fmt: FmtOptions,
}
#[derive(Debug, Parser)]
struct SearchCmd {
    pattern: String,
    limit: u64,
    offset: u64,
    #[command(flatten)]
    fmt: FmtOptions,
}

#[derive(Debug, Parser)]
struct FmtOptions {
    #[arg(short, long)]
    format: String,
    #[arg(short, long)]
    wide: u64,
    #[arg(short, long)]
    output: PathBuf,
}

fn main() {
    let cmd = Cmd::parse();
    dbg!(cmd);
}
cargo run -- fetch -h
Usage: demo fetch --format <FORMAT> --wide <WIDE> --output <OUTPUT> <NAME>

Arguments:
  <NAME>  

Options:
  -f, --format <FORMAT>  
  -w, --wide <WIDE>      
  -o, --output <OUTPUT>  
  -h, --help

cargo run -- search -h
Usage: demo search --format <FORMAT> --wide <WIDE> --output <OUTPUT> <PATTERN> <LIMIT> <OFFSET>

Arguments:
  <PATTERN>  
  <LIMIT>    
  <OFFSET>   

Options:
  -f, --format <FORMAT>  
  -w, --wide <WIDE>      
  -o, --output <OUTPUT>  
  -h, --help             Print help

解析 enum 值#

enum 在表示有限選項時非常有用,clap 提供了 ValueEnum 宏,能簡化 enum 解析。

use clap::{Parser, ValueEnum};

#[derive(Debug, Parser)]
struct Cmd {
    color: Color,
}

#[derive(Debug, Clone, Copy, ValueEnum)]
enum Color {
    Red,
    Green,
    Blue,
}

fn main() {
    let cmd = Cmd::parse();
    dbg!(cmd);
}
cargo run -- -h
Usage: demo <COLOR>

Arguments:
  <COLOR>  [possible values: red, green, blue]

Options:
  -h, --help  Print help
載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。