如何在Rust中使用Clap解析常见的子命令参数?

3

我正在尝试构建一个命令行界面,它应该将<command_name>作为第一个参数,<path_to_file>作为最后一个参数,并在两者之间接受选项,因此在控制台中调用会像这样:

programm command_one --option True file.txt

我已经设置好了,像这样:

// ./src/main.rs
use clap::{Args, Parser, Subcommand};

#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Cli {
   #[command(subcommand)]
   command: Commands,
}

#[derive(Args, Debug)]
struct CommandOneArgs {
   file: String,
   #[arg(short, long)]
   option_for_one: Option<String>,
}

#[derive(Args, Debug)]
struct CommandTwoArgs {
   file: String,
   #[arg(short, long)]
   option_for_two: Option<String>,
}


#[derive(Subcommand, Debug)]
enum Commands {
   CmdOne(CommandOneArgs)
   CmdTwo(CommandTwoArgs)
}


fn main() {
   let args = Cli::parse();
   match &args.command {
      Commands::CmdOne(cmd_args) => {println!({:?}, cmd_args)}
      Commands::CmdTwo(cmd_args) => {println!({:?}, cmd_args)}
      _ => {}
   }

但是这里有一个我无法解决的问题:
实际上,在匹配的分支中,我将使用获取的参数调用一些函数;
但是我需要为所有命令做出共同的准备工作,例如从路径读取文件。
因此,在匹配表达式之前,我需要提取file属性:
fn main() {
   let args = Cli::parse();
   /// something like that
   // let file_path = args.command.file;
   // println!("reading from: {}", file_path)
   match &args.command {
      Commands::CmdOne(cmd_args) => {println!({:?}, cmd_args)}
      Commands::CmdTwo(cmd_args) => {println!({:?}, cmd_args)}
      _ => {}
   }

我不能以注释的方式做到那样。

而且我不能向 Cli 结构体添加位置参数,因为接口看起来会像这样:programm <POSITIONAL ARG> command_one ...
我有一些假设应该使用泛型,但我不知道如何使用。

2个回答

4

我也刚开始使用clap。虽然它并不完全符合您的问题,但我刚刚遇到了这个Github讨论

如果您所有的子命令都需要此参数,则它将适合您的需求(这是我的情况)。代码示例来自我的项目,但想法是使用Arg结构体global方法,该方法允许将参数与所有子Subcommand匹配:

#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Cli {
    #[command(subcommand)]
    command: Commands,

    #[arg(global=true)]  // <-- here
    file: Option<String>
}

即使全球帮助保持不变:
$ cargo run -- -h
[...]
Usage: fed [FILE] <COMMAND>
[...]

更新了 subcommand 帮助

$ cargo run -- insert -h
[...]
Usage: fed insert -n <N> --line <LINE> [FILE]
[...]

这可能有限制,它仍然允许首先给出file参数,但可能有一些方法可以改进。如果我将来找到更好的方法,我会更新这个答案。干杯!


1

您是否考虑将检索file参数值的逻辑抽象为CommandsCli方法之一?类似这样:

use clap::{Args, Parser, Subcommand};

#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Cli {
    #[command(subcommand)]
    command: Commands,
}

impl Cli {
    fn file(&self) -> &str {
        self.command.file()
    }
}

#[derive(Args, Debug)]
struct CommandOneArgs {
    file: String,
    #[arg(short, long)]
    option_for_one: Option<String>,
}

#[derive(Args, Debug)]
struct CommandTwoArgs {
    file: String,
    #[arg(short, long)]
    option_for_two: Option<String>,
}

#[derive(Subcommand, Debug)]
enum Commands {
    CmdOne(CommandOneArgs),
    CmdTwo(CommandTwoArgs),
}

impl Commands {
    fn file(&self) -> &str {
        match self {
            Self::CmdOne(args) => &args.file,
            Self::CmdTwo(args) => &args.file,
        }
    }
}

fn main() {
    let args = Cli::parse();

    let file_path = args.file();

    println!("{file_path}");
}

如果我运行cargo run -- cmd-one hello,那么会打印出hello


确实应该可以工作,如果没有人提出其他实现方案的话,我会使用你的。我只是认为应该有比那更简单的方法,但我可能错了。 - Archirk

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接