藤本  結衣

藤本 結衣

1650431123

Clapを使用したRustでのコマンドライン引数の解析

この記事では、Rustアプリケーションに渡されたコマンドライン引数を手動で解析する方法、手動解析が大規模なアプリに適していない理由、およびClapライブラリがこれらの問題の解決にどのように役立つかを説明します。

注意として、変数宣言、if-elseブロック、ループ、構造体などの基本的なRustの読み取りと書き込みに慣れている必要があります。

Rustアプリケーションの例を設定する

たとえば、ノードベースのプロジェクトが多数あるprojectsフォルダーがあり、「依存関係パッケージを含むすべてのパッケージのうち、どれを何回使用したか」を知りたいとします。

結局のところ、その合計1GBは、node_modulesすべて固有の依存関係になるわけではありませんね😰…?

プロジェクトでパッケージを使用する回数をカウントする素敵な小さなプログラムを作成したらどうなるでしょうか。

cargo new package-hunterこれを行うには、Rustでプロジェクトを設定しましょう。これで、src/main.rsファイルにデフォルトのメイン機能が追加されました。

fn main() {
    println!("Hello, world!");
}

次のステップは非常に単純なようです。アプリケーションに渡す引数を取得します。したがって、後で他の引数を抽出するための別の関数を記述します。

fn get_arguments() {
    let args: Vec<_> = std::env::args().collect(); // get all arguements passed to app
    println!("{:?}", args);
}
fn main() {
    get_arguments();
}

それを実行すると、エラーやパニックなしで、素晴らしい出力が得られます。

# anything after '--' is passed to your app, not to cargo
> cargo run -- svelte 
    Finished dev [unoptimized + debuginfo] target(s) in 0.01s
     Running `target/debug/package-hunter svelte`
["target/debug/package-hunter", "svelte"]

もちろん、最初の引数はアプリケーションを呼び出したコマンドであり、2番目の引数はアプリケーションに渡されたものです。かなり簡単なようです。

カウント関数を書く

これで、名前を取得するカウント関数を作成し、サブディレクトリでその名前のディレクトリをカウントすることができます。

use std::collections::VecDeque;
use std::fs;
use std::path::PathBuf;
/// Not the dracula
fn count(name: &str) -> std::io::Result<usize> {
    let mut count = 0;
    // queue to store next dirs to explore
    let mut queue = VecDeque::new();
    // start with current dir
    queue.push_back(PathBuf::from("."));
    loop {
        if queue.is_empty() {
            break;
        }
        let path = queue.pop_back().unwrap();
        for dir in fs::read_dir(path)? {
            // the for loop var 'dir' is actually a result, so we convert it
            // to the actual dir struct using ? here
            let dir = dir?;
            // consider it only if it is a directory
            if dir.file_type()?.is_dir() {
                if dir.file_name() == name {
                    // we have a match, so stop exploring further
                    count += 1;
                } else {
                    // not a match so check its sub-dirs
                    queue.push_back(dir.path());
                }
            }
        }
    }
    return Ok(count);
}

get_argumentsコマンドの後の最初の引数を返すようにを更新し、mainでは、その引数を使用して呼び出しcountます。

これをプロジェクトフォルダーの1つで実行すると、予期せず完全に機能し、1つのプロジェクトに依存関係が1回だけ含まれるため、カウントが1として返されます。

深度制限の作成

ここで、ディレクトリを上に移動して実行しようとすると、問題が発生します。通過するディレクトリが増えるため、少し時間がかかります。

理想的には、プロジェクトディレクトリのルートから実行して、その依存関係を持つすべてのプロジェクトを見つけることができますが、これにはさらに時間がかかります。

そのため、妥協して、特定の深さまでディレクトリを探索することにしました。ディレクトリの深さが指定された深さよりも大きい場合、それは無視されます。関数に別のパラメーターを追加し、それを更新して深さを考慮することができます。

/// Not the dracula
fn count(name: &str, max_depth: usize) -> std::io::Result<usize> {
...
queue.push_back((PathBuf::from("."), 0));
...
let (path, crr_depth) = queue.pop_back().unwrap();
if crr_depth > max_depth {
    continue;
}
...
// not a match so check its sub-dirs
queue.push_back((dir.path(), crr_depth + 1));
...   
}

これで、アプリケーションは2つのパラメーターを受け取ります。最初にパッケージ名、次に探索する最大深度です。

ただし、深さはオプションの引数にする必要があるため、指定しない場合はすべてのサブディレクトリを探索し、指定しない場合は指定した深さで停止します。

このために、get_arguments関数を更新して2番目の引数をオプションにすることができます。

fn get_arguments() {
    let args: Vec<_> = std::env::args().collect();
    let mdepth = if args.len() > 2 {
        args[2].parse().unwrap()
    } else {
        usize::MAX
    };
    println!("{:?}", count(&args[1], mdepth));
}

これにより、両方の方法で実行でき、次のように機能します。

> cargo run -- svelte
> cargo run -- svelte 5

残念ながら、これはあまり柔軟ではありません。のように引数を逆の順序で指定するとcargo run 5 package-name、アプリケーションは数値として解析しようとしてクラッシュしますpackage-name

フラグの追加

さて、引数に独自のフラグを持たせたい場合があります-f-dたとえば、任意の順序で引数を指定できます。(フラグのボーナスUnixポイントも!)

もう一度関数を更新し、get_arguments今回は引数に適切な構造体を追加するので、解析された引数を返すのが簡単になります。

#[derive(Default)]
struct Arguments {
    package_name: String,
    max_depth: usize,
}
fn get_arguments() -> Arguments {
    let args: Vec<_> = std::env::args().collect();
    // atleast 3 args should be there : the command name, the -f flag, 
    // and the actual file name
    if args.len() < 3 {
        eprintln!("filename is a required argument");
        std::process::exit(1);
    }
    let mut ret = Arguments::default();
    ret.max_depth = usize::MAX;
    if args[1] == "-f" {
        // it is file
        ret.package_name = args[2].clone();
    } else {
        // it is max depth
        ret.max_depth = args[2].parse().unwrap();
    }
    // now that one argument is parsed, time for seconds
    if args.len() > 4 {
        if args[3] == "-f" {
            ret.package_name = args[4].clone();
        } else {
            ret.max_depth = args[4].parse().unwrap();
        }
    }
    return ret;
}

fn count(name: &str, max_depth: usize) -> std::io::Result<usize> {
...
}

fn main() {
    let args = get_arguments();
    match count(&args.package_name, args.max_depth) {
        Ok(c) => println!("{} uses found", c),
        Err(e) => eprintln!("error in processing : {}", e),
    }
}

これで、またはのような派手な-フラグを使用して実行できます。cargo run -- -f sveltecargo run -- -d 5 -f svelte

引数とフラグに関する問題

ただし、これにはかなり深刻なバグがいくつかあります。同じ引数を2回指定して、ファイル引数を完全cargo run -- -d 5 -d 7にスキップするか、無効なフラグを指定すると、エラーメッセージなしで実行されます😭。

file_name上記の行でが空でないことを確認し、27誤った値が指定された場合に予想される内容を出力することで、これを修正できます。-dただし、を直接呼び出すため、に非数値を渡すと、これもクラッシュunwrapparseます。

また、このアプリケーションはヘルプ情報を提供しないため、新規ユーザーにとっては扱いにくい場合があります。ユーザーは、どの引数がどの順序で渡されるかわからない場合があり、アプリケーションには、-h従来のUnixプログラムのように、その情報を表示するためのフラグがありません。

これらはこの特定のアプリにとってはほんの少しの不便ですが、複雑さが増すにつれてオプションの数が増えるにつれて、これらすべてを手動で維持することがますます難しくなります。

これがクラップの出番です。

クラップとは何ですか?

-hClapは、引数の解析ロジックを生成する機能を提供するライブラリであり、引数の説明やヘルプコマンドなど、アプリケーション用のきちんとしたCLIを提供します。

Clapの使用は非常に簡単で、現在の設定にわずかな変更を加えるだけで済みます。

Clapには、多くのRustプロジェクトで使用される2つの一般的なバージョンがあります。V2とV3です。V2は主に、コマンドライン引数パーサーを構築するためのビルダーベースの実装を提供します。

V3は最近のリリース(執筆時点)であり、ビルダーの実装とともにderive proc-macrosが追加されているため、構造体に注釈を付けることができ、マクロは必要な関数を派生させます。

これらには両方とも独自の利点があり、より詳細な違いと機能のリストについては、ドキュメントとヘルプページを確認してください。これらのドキュメントとヘルプページには、例が示され、どの状況が派生し、ビルダーが適しているかが示されています。

この投稿では、proc-macroでClapV3を使用する方法を説明します。

プロジェクトにクラップを追加する

Clapをプロジェクトに組み込むには、以下をプロジェクトに追加しますCargo.toml

[dependencies]
clap = { version = "3.1.6", features = ["derive"] }

これにより、派生機能との依存関係としてClapが追加されます。

get_argumentsそれでは、関数とその呼び出しをmain:から削除しましょう。

use std::collections::VecDeque;
use std::fs;
use std::path::PathBuf;
#[derive(Default)]
struct Arguments {
    package_name: String,
    max_depth: usize,
}
/// Not the dracula
fn count(name: &str, max_depth: usize) -> std::io::Result<usize> {
    let mut count = 0;
    // queue to store next dirs to explore
    let mut queue = VecDeque::new();
    // start with current dir
    queue.push_back((PathBuf::from("."), 0));
    loop {
        if queue.is_empty() {
            break;
        }
        let (path, crr_depth) = queue.pop_back().unwrap();
        if crr_depth > max_depth {
            continue;
        }
        for dir in fs::read_dir(path)? {
            let dir = dir?;
            // we are concerned only if it is a directory
            if dir.file_type()?.is_dir() {
                if dir.file_name() == name {
                    // we have a match, so stop exploring further
                    count += 1;
                } else {
                    // not a match so check its sub-dirs
                    queue.push_back((dir.path(), crr_depth + 1));
                }
            }
        }
    }
    return Ok(count);
}
fn main() {}

次に、derive構造Arguments体に追加ParserしてDebug

use clap::Parser;
#[derive(Parser,Default,Debug)]
struct Arguments {...}

最後に、でmain、parseメソッドを呼び出します。

let args = Arguments::parse();
println!("{:?}", args);

cargo run引数なしでアプリケーションを実行すると、エラーメッセージが表示されます。

error: The following required arguments were not provided:
    <PACKAGE_NAME>
    <MAX_DEPTH>

USAGE:
    package-hunter <PACKAGE_NAME> <MAX_DEPTH>

For more information try --help

これは、手動バージョンよりも優れたエラー報告です。

また、ボーナスとして、-h引数とその順序を出力できるヘルプのフラグが自動的に提供されます。

package-hunter 

USAGE:
    package-hunter <PACKAGE_NAME> <MAX_DEPTH>

ARGS:
    <PACKAGE_NAME>    
    <MAX_DEPTH>       

OPTIONS:
    -h, --help    Print help information

そして今、に数字以外のものを提供するMAX_DEPTHと、提供された文字列が数字ではないというエラーが発生します。

> cargo run -- 5 test
error: Invalid value "test" for '<MAX_DEPTH>': invalid digit found in string

For more information try --help

それらを正しい順序で提供すると、次の出力が得られますprintln

> cargo run -- test 5
Arguments { package_name: "test", max_depth: 5 }

これらすべてに2行の新しい行があり、解析コードやエラー処理を記述する必要はありません。🎉

ヘルプメッセージの更新

現在、ヘルプメッセージは引数の名前と順序のみを示しているため、少し当たり障りのないものです。ユーザーが特定の引数の意味を理解できれば、エラーを報告したい場合はアプリケーションのバージョンでさえも役立つでしょう。

クラップは、このためのオプションも提供します。

#[derive(...)]
#[clap(author="Author Name", version, about="A Very simple Package Hunter")]
struct Arguments{...}

これで、-h出力にすべての詳細が表示-Vされ、バージョン番号を出力するためのフラグも提供されます。

package-hunter 0.1.0
Author Name
A Very simple Package Hunter

USAGE:
    package-hunter <PACKAGE_NAME> <MAX_DEPTH>

ARGS:
    <PACKAGE_NAME>    
    <MAX_DEPTH>       

OPTIONS:
    -h, --help       Print help information
    -V, --version    Print version information

マクロ自体に情報に関する複数の行を書き込むのは少し面倒な場合があるため、代わりに、構造体に使用するドキュメントコメントを追加する///と、マクロはそれを情報として使用します(両方が存在する場合は、1つマクロ内はドキュメントコメントよりも優先されます):

#[clap(author = "Author Name", version, about)]
/// A Very simple Package Hunter
struct Arguments {...}

これにより、以前と同じヘルプが提供されます。

引数に関する情報を追加するために、引数自体に同様のコメントを追加できます。

package-hunter 0.1.0
Author Name
A Very simple Package Hunter

USAGE:
    package-hunter <PACKAGE_NAME> <MAX_DEPTH>

ARGS:
    <PACKAGE_NAME>    Name of the package to search
    <MAX_DEPTH>       maximum depth to which sub-directories should be explored

OPTIONS:
    -h, --help       Print help information
    -V, --version    Print version information

これははるかに役立ちます!

-fここで、引数フラグ(および-d)やオプションのdepth引数の設定など、他の機能を復活させましょう。

クラップにフラグを追加する

Clapを使用すると、フラグ引数が途方もなく単純になります。構造体メンバーに。を使用して別のClapマクロアノテーションを追加するだけ#[clap(short, long)]です。

ここで、shortは、などのフラグの短縮バージョンを-f指しlong、はなどの完全なバージョンを指し--fileます。どちらかまたは両方を選択できます。この追加により、次のようになります。

package-hunter 0.1.0
Author Name
A Very simple Package Hunter

USAGE:
    package-hunter --package-name <PACKAGE_NAME> --max-depth <MAX_DEPTH>

OPTIONS:
    -h, --help                           Print help information
    -m, --max-depth <MAX_DEPTH>          maximum depth to which sub-directories should be explored
    -p, --package-name <PACKAGE_NAME>    Name of the package to search
    -V, --version                        Print version information

両方の引数にフラグが付いているため、位置引数はありません。cargo run -- test 5これは、Clapがフラグを探し、引数が提供されていないというエラーを出すため、実行できないことを意味します。

cargo run -- -p test -m 5 代わりに、またはを実行するcargo run -- -m 5 -p testと、両方が正しく解析され、次の出力が得られます。

Arguments { package_name: "test", max_depth: 5 }

パッケージ名は常に必要なので、位置引数にすることができ、-p毎回フラグを入力する必要はありません。

これを行うには、を削除し#[clap(short,long)]ます。これで、フラグのない最初の引数は次のように見なされpackage nameます。

> cargo run -- test -m 5
Arguments { package_name: "test", max_depth: 5 }
> cargo run -- -m 5 test
Arguments { package_name: "test", max_depth: 5 }

省略引数で注意すべきことの1つは、2つの引数が同じ文字で始まり(つまり、package-nameおよびpath)、両方で短いフラグが有効になっている場合、アプリケーションはデバッグビルドの実行時にクラッシュし、リリースビルドの混乱を招くエラーメッセージを表示することです。 。

したがって、次のいずれかを確認してください。

  • すべての引数は異なるアルファベットで始まります
  • 同じ開始アルファベットを持つ引数の1つだけにshortフラグがあります

次のステップは、max_depthオプションにすることです。

引数をオプションにする

引数をオプションとしてマークするには、その引数の型を作成します。Option<T>ここTで、は元の型引数です。したがって、この場合、次のようになります。

#[clap(short, long)]
/// maximum depth to which sub-directories should be explored
max_depth: Option<usize>,

これでうまくいくはずです。変更はヘルプにも反映され、必須の引数として最大深度がリストされていません。

package-hunter 0.1.0
Author Name
A Very simple Package Hunter

USAGE:
    package-hunter [OPTIONS] <PACKAGE_NAME>

ARGS:
    <PACKAGE_NAME>    Name of the package to search

OPTIONS:
    -h, --help                     Print help information
    -m, --max-depth <MAX_DEPTH>    maximum depth to which sub-directories should be explored
    -V, --version                  Print version information

そして、-mフラグを付けずに実行できます。

> cargo run -- test
Arguments { package_name: "test", max_depth: None }

しかし、これはまだ少し面倒です。ここで、を実行する必要があります。実行matchするmax_depth場合はNone、以前と同じように設定しusize::MAXます。

しかし、拍手はここでも私たちのために何かを持っています!作成する代わりにOption<T>、引数が指定されていない場合はデフォルト値を設定できます。

したがって、次のように変更した後:

#[clap(default_value_t=usize::MAX,short, long)]
/// maximum depth to which sub-directories should be explored
max_depth: usize,

の値を指定して、または指定せずにアプリケーションを実行できますmax_depth(の最大値はusizeシステム構成によって異なります)。

> cargo run -- test
Arguments { package_name: "test", max_depth: 18446744073709551615 }
> cargo run -- test -m 5
Arguments { package_name: "test", max_depth: 5 }

mainそれでは、前と同じようにカウント関数に接続しましょう。

fn main() {
    let args = Arguments::parse();
    match count(&args.package_name, args.max_depth) {
        Ok(c) => println!("{} uses found", c),
        Err(e) => eprintln!("error in processing : {}", e),
    }
}

これにより、元の機能が復活しましたが、コードが大幅に減り、いくつかの追加機能が追加されました。

空の文字列のバグを修正する

package-hunter期待どおりに機能していますが、残念ながら、手動の解析段階から存在し、Clapベースのバージョンに持ち込まれた微妙なバグがあります。あなたはそれが何であるかを推測できますか?

小さな小さなアプリにとってはそれほど危険なバグではありませんが、他のアプリケーションにとってはアキレス腱になる可能性があります。私たちの場合、エラーが発生したときに誤った結果が返されます。

次を実行してみてください:

> cargo run -- ""
0 uses found

ここではpackage_name、空のパッケージ名を許可しない場合に、が空の文字列として渡されます。これは、コマンドを実行するシェルが引数をアプリに渡す方法が原因で発生します。

通常、シェルはスペースを使用してプログラムに渡される引数リストを分割するため、、、、およびの3つのabc def hij個別の引数として指定されます。abcdefhij

引数にスペースを含めたい場合は、のように引用符で囲む必要があります"abc efg hij"。このようにして、シェルはこれが単一の引数であることを認識し、そのように渡します。

一方、これにより、空の文字列またはスペースのみの文字列をアプリに渡すこともできます。もう一度、拍手して救助してください!引数の空の値を拒否する方法を提供します。

#[clap(forbid_empty_values = true)]
/// Name of the package to search
package_name: String,

これで、引数として空の文字列を指定しようとすると、エラーが発生します。

> cargo run -- ""
error: The argument '<PACKAGE_NAME>' requires a value but none was supplied

ただし、これでもパッケージ名としてスペースが提供されます。つまり""、有効な引数です。これを修正するには、名前に先頭または末尾のスペースがあるかどうかを確認し、含まれている場合は拒否するカスタムバリデーターを提供する必要があります。

検証関数を次のように定義します。

fn validate_package_name(name: &str) -> Result<(), String> {
    if name.trim().len() != name.len() {
        Err(String::from(
            "package name cannot have leading and trailing space",
        ))
    } else {
        Ok(())
    }
}

次に、次のように設定しpackage_nameます。

#[clap(forbid_empty_values = true, validator = validate_package_name)]
/// Name of the package to search
package_name: String,

ここで、空の文字列またはスペースを含む文字列を渡そうとすると、次のようにエラーが発生します。

> cargo run -- "" 
error: The argument '<PACKAGE_NAME>' requires a value but none was supplied
> cargo run -- " "
error: Invalid value " " for '<PACKAGE_NAME>': package name cannot have leading and trailing space

このようにして、解析用のすべてのコードを記述せずに、カスタムロジックを使用して引数を検証できます。

クラップでロギング

アプリケーションは現在正常に動作していますが、動作しなかった場合に何が起こったかを確認する方法はありません。そのためには、アプリケーションがクラッシュしたときに何が起こったかを確認するために、アプリケーションが実行していることのログを保持する必要があります。

他のコマンドラインアプリケーションと同様に、ユーザーがログのレベルを簡単に設定できる方法が必要です。デフォルトでは、ログが乱雑にならないように、主要な詳細とエラーのみをログに記録する必要がありますが、アプリケーションがクラッシュした場合は、可能な限りすべてをログに記録するモードが必要です。

他のアプリケーションと同様に、-vフラグを使用してアプリに詳細レベルを取得させましょう。フラグなしは最小ロギング、-v中間ロギング、および-vv最大ロギングです。

これを行うために、Clapは、引数の値が発生する回数に設定されるようにする方法を提供します。これはまさにここで必要なことです。別のパラメーターを追加して、次のように設定できます。

#[clap(short、long、parse(from_occurrences))]冗長性:usize、

ここで、フラグを付けずに実行すると-v、値はゼロになります。それ以外の場合は、-vフラグが発生した回数をカウントします。

> cargo run -- test
Arguments { package_name: "test", max_depth: 18446744073709551615, verbosity: 0 }
> cargo run -- test -v
Arguments { package_name: "test", max_depth: 18446744073709551615, verbosity: 1 }
> cargo run -- test -vv
Arguments { package_name: "test", max_depth: 18446744073709551615, verbosity: 2 }
> cargo run -- -vv test -v
Arguments { package_name: "test", max_depth: 18446744073709551615, verbosity: 3 }

この値を使用すると、ロガーを簡単に初期化して、適切な量の詳細をログに記録させることができます。

この投稿は引数の解析に焦点を当てているため、ここではダミーのロガーコードを追加していませんが、最後のリポジトリにあります。

プロジェクトのカウントと検索

アプリケーションが正常に機能しているので、別の機能を追加します。それは、プロジェクトの一覧表示です。そうすれば、プロジェクトの素晴らしいリストが必要なときに、すぐにそれを取得できます。

Clapには、アプリに複数のサブコマンドを提供できる強力なサブコマンド機能があります。これを使用するには、サブコマンドとなる独自の引数を使用して別の構造体を定義します。メイン引数構造体には、すべてのサブコマンドに共通の引数が含まれ、次にサブコマンドが含まれます。

CLIを次のように構成します。

  • ログの詳細度とmax_depthパラメーターはメイン構造になります
  • countコマンドは、ファイル名を使用してカウントを検索して出力します
  • このprojectsコマンドは、オプションの開始パスを使用して検索を開始します
  • このprojectsコマンドは、指定されたディレクトリをスキップするオプションの除外パスリストを取ります

したがって、カウントとプロジェクトの列挙型を次のように追加します。

use clap::{Parser, Subcommand};
...
#[derive(Subcommand, Debug)]
enum SubCommand {
    /// Count how many times the package is used
    Count {
        #[clap(forbid_empty_values = true, validator = validate_package_name)]
        /// Name of the package to search
        package_name: String,
    },
    /// list all the projects
    Projects {
        #[clap(short, long, default_value_t = String::from("."),forbid_empty_values = true, validator = validate_package_name)]
        /// directory to start exploring from
        start_path: String,
        #[clap(short, long, multiple_values = true)]
        /// paths to exclude when searching
        exclude: Vec<String>,
    },
}

ここでは、をバリアントに移動package_nameし、バリアントにオプションCountを追加します。start_pathexcludeProjects

ここで、ヘルプを確認すると、これらのサブコマンドの両方が一覧表示され、各サブコマンドには独自のヘルプがあります。

次に、それらに対応するためにmain関数を更新できます。

let args = Arguments::parse();
match args.cmd {
    SubCommand::Count { package_name } => match count(&package_name, args.max_depth, &logger) {
        Ok(c) => println!("{} uses found", c),
        Err(e) => eprintln!("error in processing : {}", e),
    },
    SubCommand::Projects {
        start_path,
        exclude,
    } => {/* TODO */}
}

count以前のようにコマンドを使用して、使用回数をカウントすることもできます。

> cargo run -- -m 5 count test

max_depthメイン構造体で定義されているようArgumentsに、サブコマンドの前に指定する必要があります。

次に、必要に応じて、プロジェクトのコマンドの除外されたディレクトリに複数の値を指定できます。

> cargo run -- projects -e ./dir1 ./dir2
["./dir1", "./dir2"] # value of exclude vector

値をスペースで区切るのではなく、カスタム文字で区切る場合に備えて、カスタム区切り文字を設定することもできます。

#[clap(short, long, multiple_values = true, value_delimiter = ':')]
/// paths to exclude when searching
exclude: Vec<String>,

:これで、値を区切るために使用できます。

> cargo run -- projects -e ./dir1:./dir2
["./dir1", "./dir2"]

これで、アプリケーションのCLIが完成しました。プロジェクトリスト関数はここには示されていませんが、自分で作成するか、GitHubリポジトリでコードを確認することができます。

結論

Clapについて理解したので、プロジェクト用にクリーンでエレガントなCLIを作成できます。他にも多くの機能があり、プロジェクトでコマンドラインに特定の機能が必要な場合は、Clapにすでに機能がある可能性があります。

ClapのドキュメントClapGitHubページをチェックして、Clapライブラリが提供するオプションの詳細を確認できます。

このプロジェクトのコードはここから入手することもできます。読んでくれてありがとう!

ソース:https ://blog.logrocket.com/command-line-argument-parsing-rust-using-clap/

#rust 

What is GEEK

Buddha Community

Clapを使用したRustでのコマンドライン引数の解析
藤本  結衣

藤本 結衣

1650431123

Clapを使用したRustでのコマンドライン引数の解析

この記事では、Rustアプリケーションに渡されたコマンドライン引数を手動で解析する方法、手動解析が大規模なアプリに適していない理由、およびClapライブラリがこれらの問題の解決にどのように役立つかを説明します。

注意として、変数宣言、if-elseブロック、ループ、構造体などの基本的なRustの読み取りと書き込みに慣れている必要があります。

Rustアプリケーションの例を設定する

たとえば、ノードベースのプロジェクトが多数あるprojectsフォルダーがあり、「依存関係パッケージを含むすべてのパッケージのうち、どれを何回使用したか」を知りたいとします。

結局のところ、その合計1GBは、node_modulesすべて固有の依存関係になるわけではありませんね😰…?

プロジェクトでパッケージを使用する回数をカウントする素敵な小さなプログラムを作成したらどうなるでしょうか。

cargo new package-hunterこれを行うには、Rustでプロジェクトを設定しましょう。これで、src/main.rsファイルにデフォルトのメイン機能が追加されました。

fn main() {
    println!("Hello, world!");
}

次のステップは非常に単純なようです。アプリケーションに渡す引数を取得します。したがって、後で他の引数を抽出するための別の関数を記述します。

fn get_arguments() {
    let args: Vec<_> = std::env::args().collect(); // get all arguements passed to app
    println!("{:?}", args);
}
fn main() {
    get_arguments();
}

それを実行すると、エラーやパニックなしで、素晴らしい出力が得られます。

# anything after '--' is passed to your app, not to cargo
> cargo run -- svelte 
    Finished dev [unoptimized + debuginfo] target(s) in 0.01s
     Running `target/debug/package-hunter svelte`
["target/debug/package-hunter", "svelte"]

もちろん、最初の引数はアプリケーションを呼び出したコマンドであり、2番目の引数はアプリケーションに渡されたものです。かなり簡単なようです。

カウント関数を書く

これで、名前を取得するカウント関数を作成し、サブディレクトリでその名前のディレクトリをカウントすることができます。

use std::collections::VecDeque;
use std::fs;
use std::path::PathBuf;
/// Not the dracula
fn count(name: &str) -> std::io::Result<usize> {
    let mut count = 0;
    // queue to store next dirs to explore
    let mut queue = VecDeque::new();
    // start with current dir
    queue.push_back(PathBuf::from("."));
    loop {
        if queue.is_empty() {
            break;
        }
        let path = queue.pop_back().unwrap();
        for dir in fs::read_dir(path)? {
            // the for loop var 'dir' is actually a result, so we convert it
            // to the actual dir struct using ? here
            let dir = dir?;
            // consider it only if it is a directory
            if dir.file_type()?.is_dir() {
                if dir.file_name() == name {
                    // we have a match, so stop exploring further
                    count += 1;
                } else {
                    // not a match so check its sub-dirs
                    queue.push_back(dir.path());
                }
            }
        }
    }
    return Ok(count);
}

get_argumentsコマンドの後の最初の引数を返すようにを更新し、mainでは、その引数を使用して呼び出しcountます。

これをプロジェクトフォルダーの1つで実行すると、予期せず完全に機能し、1つのプロジェクトに依存関係が1回だけ含まれるため、カウントが1として返されます。

深度制限の作成

ここで、ディレクトリを上に移動して実行しようとすると、問題が発生します。通過するディレクトリが増えるため、少し時間がかかります。

理想的には、プロジェクトディレクトリのルートから実行して、その依存関係を持つすべてのプロジェクトを見つけることができますが、これにはさらに時間がかかります。

そのため、妥協して、特定の深さまでディレクトリを探索することにしました。ディレクトリの深さが指定された深さよりも大きい場合、それは無視されます。関数に別のパラメーターを追加し、それを更新して深さを考慮することができます。

/// Not the dracula
fn count(name: &str, max_depth: usize) -> std::io::Result<usize> {
...
queue.push_back((PathBuf::from("."), 0));
...
let (path, crr_depth) = queue.pop_back().unwrap();
if crr_depth > max_depth {
    continue;
}
...
// not a match so check its sub-dirs
queue.push_back((dir.path(), crr_depth + 1));
...   
}

これで、アプリケーションは2つのパラメーターを受け取ります。最初にパッケージ名、次に探索する最大深度です。

ただし、深さはオプションの引数にする必要があるため、指定しない場合はすべてのサブディレクトリを探索し、指定しない場合は指定した深さで停止します。

このために、get_arguments関数を更新して2番目の引数をオプションにすることができます。

fn get_arguments() {
    let args: Vec<_> = std::env::args().collect();
    let mdepth = if args.len() > 2 {
        args[2].parse().unwrap()
    } else {
        usize::MAX
    };
    println!("{:?}", count(&args[1], mdepth));
}

これにより、両方の方法で実行でき、次のように機能します。

> cargo run -- svelte
> cargo run -- svelte 5

残念ながら、これはあまり柔軟ではありません。のように引数を逆の順序で指定するとcargo run 5 package-name、アプリケーションは数値として解析しようとしてクラッシュしますpackage-name

フラグの追加

さて、引数に独自のフラグを持たせたい場合があります-f-dたとえば、任意の順序で引数を指定できます。(フラグのボーナスUnixポイントも!)

もう一度関数を更新し、get_arguments今回は引数に適切な構造体を追加するので、解析された引数を返すのが簡単になります。

#[derive(Default)]
struct Arguments {
    package_name: String,
    max_depth: usize,
}
fn get_arguments() -> Arguments {
    let args: Vec<_> = std::env::args().collect();
    // atleast 3 args should be there : the command name, the -f flag, 
    // and the actual file name
    if args.len() < 3 {
        eprintln!("filename is a required argument");
        std::process::exit(1);
    }
    let mut ret = Arguments::default();
    ret.max_depth = usize::MAX;
    if args[1] == "-f" {
        // it is file
        ret.package_name = args[2].clone();
    } else {
        // it is max depth
        ret.max_depth = args[2].parse().unwrap();
    }
    // now that one argument is parsed, time for seconds
    if args.len() > 4 {
        if args[3] == "-f" {
            ret.package_name = args[4].clone();
        } else {
            ret.max_depth = args[4].parse().unwrap();
        }
    }
    return ret;
}

fn count(name: &str, max_depth: usize) -> std::io::Result<usize> {
...
}

fn main() {
    let args = get_arguments();
    match count(&args.package_name, args.max_depth) {
        Ok(c) => println!("{} uses found", c),
        Err(e) => eprintln!("error in processing : {}", e),
    }
}

これで、またはのような派手な-フラグを使用して実行できます。cargo run -- -f sveltecargo run -- -d 5 -f svelte

引数とフラグに関する問題

ただし、これにはかなり深刻なバグがいくつかあります。同じ引数を2回指定して、ファイル引数を完全cargo run -- -d 5 -d 7にスキップするか、無効なフラグを指定すると、エラーメッセージなしで実行されます😭。

file_name上記の行でが空でないことを確認し、27誤った値が指定された場合に予想される内容を出力することで、これを修正できます。-dただし、を直接呼び出すため、に非数値を渡すと、これもクラッシュunwrapparseます。

また、このアプリケーションはヘルプ情報を提供しないため、新規ユーザーにとっては扱いにくい場合があります。ユーザーは、どの引数がどの順序で渡されるかわからない場合があり、アプリケーションには、-h従来のUnixプログラムのように、その情報を表示するためのフラグがありません。

これらはこの特定のアプリにとってはほんの少しの不便ですが、複雑さが増すにつれてオプションの数が増えるにつれて、これらすべてを手動で維持することがますます難しくなります。

これがクラップの出番です。

クラップとは何ですか?

-hClapは、引数の解析ロジックを生成する機能を提供するライブラリであり、引数の説明やヘルプコマンドなど、アプリケーション用のきちんとしたCLIを提供します。

Clapの使用は非常に簡単で、現在の設定にわずかな変更を加えるだけで済みます。

Clapには、多くのRustプロジェクトで使用される2つの一般的なバージョンがあります。V2とV3です。V2は主に、コマンドライン引数パーサーを構築するためのビルダーベースの実装を提供します。

V3は最近のリリース(執筆時点)であり、ビルダーの実装とともにderive proc-macrosが追加されているため、構造体に注釈を付けることができ、マクロは必要な関数を派生させます。

これらには両方とも独自の利点があり、より詳細な違いと機能のリストについては、ドキュメントとヘルプページを確認してください。これらのドキュメントとヘルプページには、例が示され、どの状況が派生し、ビルダーが適しているかが示されています。

この投稿では、proc-macroでClapV3を使用する方法を説明します。

プロジェクトにクラップを追加する

Clapをプロジェクトに組み込むには、以下をプロジェクトに追加しますCargo.toml

[dependencies]
clap = { version = "3.1.6", features = ["derive"] }

これにより、派生機能との依存関係としてClapが追加されます。

get_argumentsそれでは、関数とその呼び出しをmain:から削除しましょう。

use std::collections::VecDeque;
use std::fs;
use std::path::PathBuf;
#[derive(Default)]
struct Arguments {
    package_name: String,
    max_depth: usize,
}
/// Not the dracula
fn count(name: &str, max_depth: usize) -> std::io::Result<usize> {
    let mut count = 0;
    // queue to store next dirs to explore
    let mut queue = VecDeque::new();
    // start with current dir
    queue.push_back((PathBuf::from("."), 0));
    loop {
        if queue.is_empty() {
            break;
        }
        let (path, crr_depth) = queue.pop_back().unwrap();
        if crr_depth > max_depth {
            continue;
        }
        for dir in fs::read_dir(path)? {
            let dir = dir?;
            // we are concerned only if it is a directory
            if dir.file_type()?.is_dir() {
                if dir.file_name() == name {
                    // we have a match, so stop exploring further
                    count += 1;
                } else {
                    // not a match so check its sub-dirs
                    queue.push_back((dir.path(), crr_depth + 1));
                }
            }
        }
    }
    return Ok(count);
}
fn main() {}

次に、derive構造Arguments体に追加ParserしてDebug

use clap::Parser;
#[derive(Parser,Default,Debug)]
struct Arguments {...}

最後に、でmain、parseメソッドを呼び出します。

let args = Arguments::parse();
println!("{:?}", args);

cargo run引数なしでアプリケーションを実行すると、エラーメッセージが表示されます。

error: The following required arguments were not provided:
    <PACKAGE_NAME>
    <MAX_DEPTH>

USAGE:
    package-hunter <PACKAGE_NAME> <MAX_DEPTH>

For more information try --help

これは、手動バージョンよりも優れたエラー報告です。

また、ボーナスとして、-h引数とその順序を出力できるヘルプのフラグが自動的に提供されます。

package-hunter 

USAGE:
    package-hunter <PACKAGE_NAME> <MAX_DEPTH>

ARGS:
    <PACKAGE_NAME>    
    <MAX_DEPTH>       

OPTIONS:
    -h, --help    Print help information

そして今、に数字以外のものを提供するMAX_DEPTHと、提供された文字列が数字ではないというエラーが発生します。

> cargo run -- 5 test
error: Invalid value "test" for '<MAX_DEPTH>': invalid digit found in string

For more information try --help

それらを正しい順序で提供すると、次の出力が得られますprintln

> cargo run -- test 5
Arguments { package_name: "test", max_depth: 5 }

これらすべてに2行の新しい行があり、解析コードやエラー処理を記述する必要はありません。🎉

ヘルプメッセージの更新

現在、ヘルプメッセージは引数の名前と順序のみを示しているため、少し当たり障りのないものです。ユーザーが特定の引数の意味を理解できれば、エラーを報告したい場合はアプリケーションのバージョンでさえも役立つでしょう。

クラップは、このためのオプションも提供します。

#[derive(...)]
#[clap(author="Author Name", version, about="A Very simple Package Hunter")]
struct Arguments{...}

これで、-h出力にすべての詳細が表示-Vされ、バージョン番号を出力するためのフラグも提供されます。

package-hunter 0.1.0
Author Name
A Very simple Package Hunter

USAGE:
    package-hunter <PACKAGE_NAME> <MAX_DEPTH>

ARGS:
    <PACKAGE_NAME>    
    <MAX_DEPTH>       

OPTIONS:
    -h, --help       Print help information
    -V, --version    Print version information

マクロ自体に情報に関する複数の行を書き込むのは少し面倒な場合があるため、代わりに、構造体に使用するドキュメントコメントを追加する///と、マクロはそれを情報として使用します(両方が存在する場合は、1つマクロ内はドキュメントコメントよりも優先されます):

#[clap(author = "Author Name", version, about)]
/// A Very simple Package Hunter
struct Arguments {...}

これにより、以前と同じヘルプが提供されます。

引数に関する情報を追加するために、引数自体に同様のコメントを追加できます。

package-hunter 0.1.0
Author Name
A Very simple Package Hunter

USAGE:
    package-hunter <PACKAGE_NAME> <MAX_DEPTH>

ARGS:
    <PACKAGE_NAME>    Name of the package to search
    <MAX_DEPTH>       maximum depth to which sub-directories should be explored

OPTIONS:
    -h, --help       Print help information
    -V, --version    Print version information

これははるかに役立ちます!

-fここで、引数フラグ(および-d)やオプションのdepth引数の設定など、他の機能を復活させましょう。

クラップにフラグを追加する

Clapを使用すると、フラグ引数が途方もなく単純になります。構造体メンバーに。を使用して別のClapマクロアノテーションを追加するだけ#[clap(short, long)]です。

ここで、shortは、などのフラグの短縮バージョンを-f指しlong、はなどの完全なバージョンを指し--fileます。どちらかまたは両方を選択できます。この追加により、次のようになります。

package-hunter 0.1.0
Author Name
A Very simple Package Hunter

USAGE:
    package-hunter --package-name <PACKAGE_NAME> --max-depth <MAX_DEPTH>

OPTIONS:
    -h, --help                           Print help information
    -m, --max-depth <MAX_DEPTH>          maximum depth to which sub-directories should be explored
    -p, --package-name <PACKAGE_NAME>    Name of the package to search
    -V, --version                        Print version information

両方の引数にフラグが付いているため、位置引数はありません。cargo run -- test 5これは、Clapがフラグを探し、引数が提供されていないというエラーを出すため、実行できないことを意味します。

cargo run -- -p test -m 5 代わりに、またはを実行するcargo run -- -m 5 -p testと、両方が正しく解析され、次の出力が得られます。

Arguments { package_name: "test", max_depth: 5 }

パッケージ名は常に必要なので、位置引数にすることができ、-p毎回フラグを入力する必要はありません。

これを行うには、を削除し#[clap(short,long)]ます。これで、フラグのない最初の引数は次のように見なされpackage nameます。

> cargo run -- test -m 5
Arguments { package_name: "test", max_depth: 5 }
> cargo run -- -m 5 test
Arguments { package_name: "test", max_depth: 5 }

省略引数で注意すべきことの1つは、2つの引数が同じ文字で始まり(つまり、package-nameおよびpath)、両方で短いフラグが有効になっている場合、アプリケーションはデバッグビルドの実行時にクラッシュし、リリースビルドの混乱を招くエラーメッセージを表示することです。 。

したがって、次のいずれかを確認してください。

  • すべての引数は異なるアルファベットで始まります
  • 同じ開始アルファベットを持つ引数の1つだけにshortフラグがあります

次のステップは、max_depthオプションにすることです。

引数をオプションにする

引数をオプションとしてマークするには、その引数の型を作成します。Option<T>ここTで、は元の型引数です。したがって、この場合、次のようになります。

#[clap(short, long)]
/// maximum depth to which sub-directories should be explored
max_depth: Option<usize>,

これでうまくいくはずです。変更はヘルプにも反映され、必須の引数として最大深度がリストされていません。

package-hunter 0.1.0
Author Name
A Very simple Package Hunter

USAGE:
    package-hunter [OPTIONS] <PACKAGE_NAME>

ARGS:
    <PACKAGE_NAME>    Name of the package to search

OPTIONS:
    -h, --help                     Print help information
    -m, --max-depth <MAX_DEPTH>    maximum depth to which sub-directories should be explored
    -V, --version                  Print version information

そして、-mフラグを付けずに実行できます。

> cargo run -- test
Arguments { package_name: "test", max_depth: None }

しかし、これはまだ少し面倒です。ここで、を実行する必要があります。実行matchするmax_depth場合はNone、以前と同じように設定しusize::MAXます。

しかし、拍手はここでも私たちのために何かを持っています!作成する代わりにOption<T>、引数が指定されていない場合はデフォルト値を設定できます。

したがって、次のように変更した後:

#[clap(default_value_t=usize::MAX,short, long)]
/// maximum depth to which sub-directories should be explored
max_depth: usize,

の値を指定して、または指定せずにアプリケーションを実行できますmax_depth(の最大値はusizeシステム構成によって異なります)。

> cargo run -- test
Arguments { package_name: "test", max_depth: 18446744073709551615 }
> cargo run -- test -m 5
Arguments { package_name: "test", max_depth: 5 }

mainそれでは、前と同じようにカウント関数に接続しましょう。

fn main() {
    let args = Arguments::parse();
    match count(&args.package_name, args.max_depth) {
        Ok(c) => println!("{} uses found", c),
        Err(e) => eprintln!("error in processing : {}", e),
    }
}

これにより、元の機能が復活しましたが、コードが大幅に減り、いくつかの追加機能が追加されました。

空の文字列のバグを修正する

package-hunter期待どおりに機能していますが、残念ながら、手動の解析段階から存在し、Clapベースのバージョンに持ち込まれた微妙なバグがあります。あなたはそれが何であるかを推測できますか?

小さな小さなアプリにとってはそれほど危険なバグではありませんが、他のアプリケーションにとってはアキレス腱になる可能性があります。私たちの場合、エラーが発生したときに誤った結果が返されます。

次を実行してみてください:

> cargo run -- ""
0 uses found

ここではpackage_name、空のパッケージ名を許可しない場合に、が空の文字列として渡されます。これは、コマンドを実行するシェルが引数をアプリに渡す方法が原因で発生します。

通常、シェルはスペースを使用してプログラムに渡される引数リストを分割するため、、、、およびの3つのabc def hij個別の引数として指定されます。abcdefhij

引数にスペースを含めたい場合は、のように引用符で囲む必要があります"abc efg hij"。このようにして、シェルはこれが単一の引数であることを認識し、そのように渡します。

一方、これにより、空の文字列またはスペースのみの文字列をアプリに渡すこともできます。もう一度、拍手して救助してください!引数の空の値を拒否する方法を提供します。

#[clap(forbid_empty_values = true)]
/// Name of the package to search
package_name: String,

これで、引数として空の文字列を指定しようとすると、エラーが発生します。

> cargo run -- ""
error: The argument '<PACKAGE_NAME>' requires a value but none was supplied

ただし、これでもパッケージ名としてスペースが提供されます。つまり""、有効な引数です。これを修正するには、名前に先頭または末尾のスペースがあるかどうかを確認し、含まれている場合は拒否するカスタムバリデーターを提供する必要があります。

検証関数を次のように定義します。

fn validate_package_name(name: &str) -> Result<(), String> {
    if name.trim().len() != name.len() {
        Err(String::from(
            "package name cannot have leading and trailing space",
        ))
    } else {
        Ok(())
    }
}

次に、次のように設定しpackage_nameます。

#[clap(forbid_empty_values = true, validator = validate_package_name)]
/// Name of the package to search
package_name: String,

ここで、空の文字列またはスペースを含む文字列を渡そうとすると、次のようにエラーが発生します。

> cargo run -- "" 
error: The argument '<PACKAGE_NAME>' requires a value but none was supplied
> cargo run -- " "
error: Invalid value " " for '<PACKAGE_NAME>': package name cannot have leading and trailing space

このようにして、解析用のすべてのコードを記述せずに、カスタムロジックを使用して引数を検証できます。

クラップでロギング

アプリケーションは現在正常に動作していますが、動作しなかった場合に何が起こったかを確認する方法はありません。そのためには、アプリケーションがクラッシュしたときに何が起こったかを確認するために、アプリケーションが実行していることのログを保持する必要があります。

他のコマンドラインアプリケーションと同様に、ユーザーがログのレベルを簡単に設定できる方法が必要です。デフォルトでは、ログが乱雑にならないように、主要な詳細とエラーのみをログに記録する必要がありますが、アプリケーションがクラッシュした場合は、可能な限りすべてをログに記録するモードが必要です。

他のアプリケーションと同様に、-vフラグを使用してアプリに詳細レベルを取得させましょう。フラグなしは最小ロギング、-v中間ロギング、および-vv最大ロギングです。

これを行うために、Clapは、引数の値が発生する回数に設定されるようにする方法を提供します。これはまさにここで必要なことです。別のパラメーターを追加して、次のように設定できます。

#[clap(short、long、parse(from_occurrences))]冗長性:usize、

ここで、フラグを付けずに実行すると-v、値はゼロになります。それ以外の場合は、-vフラグが発生した回数をカウントします。

> cargo run -- test
Arguments { package_name: "test", max_depth: 18446744073709551615, verbosity: 0 }
> cargo run -- test -v
Arguments { package_name: "test", max_depth: 18446744073709551615, verbosity: 1 }
> cargo run -- test -vv
Arguments { package_name: "test", max_depth: 18446744073709551615, verbosity: 2 }
> cargo run -- -vv test -v
Arguments { package_name: "test", max_depth: 18446744073709551615, verbosity: 3 }

この値を使用すると、ロガーを簡単に初期化して、適切な量の詳細をログに記録させることができます。

この投稿は引数の解析に焦点を当てているため、ここではダミーのロガーコードを追加していませんが、最後のリポジトリにあります。

プロジェクトのカウントと検索

アプリケーションが正常に機能しているので、別の機能を追加します。それは、プロジェクトの一覧表示です。そうすれば、プロジェクトの素晴らしいリストが必要なときに、すぐにそれを取得できます。

Clapには、アプリに複数のサブコマンドを提供できる強力なサブコマンド機能があります。これを使用するには、サブコマンドとなる独自の引数を使用して別の構造体を定義します。メイン引数構造体には、すべてのサブコマンドに共通の引数が含まれ、次にサブコマンドが含まれます。

CLIを次のように構成します。

  • ログの詳細度とmax_depthパラメーターはメイン構造になります
  • countコマンドは、ファイル名を使用してカウントを検索して出力します
  • このprojectsコマンドは、オプションの開始パスを使用して検索を開始します
  • このprojectsコマンドは、指定されたディレクトリをスキップするオプションの除外パスリストを取ります

したがって、カウントとプロジェクトの列挙型を次のように追加します。

use clap::{Parser, Subcommand};
...
#[derive(Subcommand, Debug)]
enum SubCommand {
    /// Count how many times the package is used
    Count {
        #[clap(forbid_empty_values = true, validator = validate_package_name)]
        /// Name of the package to search
        package_name: String,
    },
    /// list all the projects
    Projects {
        #[clap(short, long, default_value_t = String::from("."),forbid_empty_values = true, validator = validate_package_name)]
        /// directory to start exploring from
        start_path: String,
        #[clap(short, long, multiple_values = true)]
        /// paths to exclude when searching
        exclude: Vec<String>,
    },
}

ここでは、をバリアントに移動package_nameし、バリアントにオプションCountを追加します。start_pathexcludeProjects

ここで、ヘルプを確認すると、これらのサブコマンドの両方が一覧表示され、各サブコマンドには独自のヘルプがあります。

次に、それらに対応するためにmain関数を更新できます。

let args = Arguments::parse();
match args.cmd {
    SubCommand::Count { package_name } => match count(&package_name, args.max_depth, &logger) {
        Ok(c) => println!("{} uses found", c),
        Err(e) => eprintln!("error in processing : {}", e),
    },
    SubCommand::Projects {
        start_path,
        exclude,
    } => {/* TODO */}
}

count以前のようにコマンドを使用して、使用回数をカウントすることもできます。

> cargo run -- -m 5 count test

max_depthメイン構造体で定義されているようArgumentsに、サブコマンドの前に指定する必要があります。

次に、必要に応じて、プロジェクトのコマンドの除外されたディレクトリに複数の値を指定できます。

> cargo run -- projects -e ./dir1 ./dir2
["./dir1", "./dir2"] # value of exclude vector

値をスペースで区切るのではなく、カスタム文字で区切る場合に備えて、カスタム区切り文字を設定することもできます。

#[clap(short, long, multiple_values = true, value_delimiter = ':')]
/// paths to exclude when searching
exclude: Vec<String>,

:これで、値を区切るために使用できます。

> cargo run -- projects -e ./dir1:./dir2
["./dir1", "./dir2"]

これで、アプリケーションのCLIが完成しました。プロジェクトリスト関数はここには示されていませんが、自分で作成するか、GitHubリポジトリでコードを確認することができます。

結論

Clapについて理解したので、プロジェクト用にクリーンでエレガントなCLIを作成できます。他にも多くの機能があり、プロジェクトでコマンドラインに特定の機能が必要な場合は、Clapにすでに機能がある可能性があります。

ClapのドキュメントClapGitHubページをチェックして、Clapライブラリが提供するオプションの詳細を確認できます。

このプロジェクトのコードはここから入手することもできます。読んでくれてありがとう!

ソース:https ://blog.logrocket.com/command-line-argument-parsing-rust-using-clap/

#rust