1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Rustのコマンドラインパーサ『clap』でマクロが使えるようになりそう。

Posted at

はじめに

みなさま、コマンドラインツールは何で作っているでしょうか。Rustではclapstructoptが有名ですよね。今回はclapがマクロでの記述に対応しそうなので、それについて解説したいと思います。

clapとは

clapはRustでコマンドラインツールを作るためのクレートです。コマンドラインツールを作るときに、オプションの解析やヘルプメッセージの準備など面倒ですよね。clapはそんな面倒事を引き受けてくれます。百聞は一見に如かずということでサンプルを見てみましょう。

    let matches = App::new("My Super Program")
        .version("1.0")
        .author("Kevin K. <kbknapp@gmail.com>")
        .about("Does awesome things")
        .arg(Arg::new("config")
            .short('c')
            .long("config")
            .value_name("FILE")
            .about("Sets a custom config file")
            .takes_value(true))
        .arg(Arg::new("INPUT")
            .about("Sets the input file to use")
            .required(true)
            .index(1))
        .arg(Arg::new("v")
            .short('v')
            .multiple_occurrences(true)
            .takes_value(true)
            .about("Sets the level of verbosity"))
        .subcommand(App::new("test")
            .about("controls testing features")
            .version("1.3")
            .author("Someone E. <someone_else@other.com>")
            .arg(Arg::new("debug")
                .short('d')
                .about("print debug information verbosely")))
        .get_matches();

(https://github.com/clap-rs/clap のREADMEから引用)

なんとなく雰囲気はわかるのではないでしょうか。ビルダーパターンを用いて、引数を解析した構造体matchを生成しています。けどなんか長ったらしいですね。

structoptとは

structoptも同様なコマンドラインパーサーなのですが、マクロを使って引数を解析した構造体を生成できます。

# [derive(StructOpt, Debug)]
# [structopt(name = "basic")]
struct Opt {
    // A flag, true if used in the command line. Note doc comment will
    // be used for the help message of the flag. The name of the
    // argument will be, by default, based on the name of the field.
    /// Activate debug mode
    #[structopt(short, long)]
    debug: bool,

    // The number of occurrences of the `v/verbose` flag
    /// Verbose mode (-v, -vv, -vvv, etc.)
    #[structopt(short, long, parse(from_occurrences))]
    verbose: u8,

    /// Set speed
    #[structopt(short, long, default_value = "42")]
    speed: f64,

    /// Output file
    #[structopt(short, long, parse(from_os_str))]
    output: PathBuf,

    // the long option will be translated by default to kebab case,
    // i.e. `--nb-cars`.
    /// Number of cars
    #[structopt(short = "c", long)]
    nb_cars: Option<i32>,

    /// admin_level to consider
    #[structopt(short, long)]
    level: Vec<String>,

    /// Files to process
    #[structopt(name = "FILE", parse(from_os_str))]
    files: Vec<PathBuf>,
}

(https://github.com/TeXitoi/structopt のREADMEから引用)

こっちの方がスッキリしています。

clapでもマクロでの定義に対応しそう

さて本題です。clapではv2.x.xまでは(多分)マクロによる定義はできなかったのですが、clap v3からはマクロによる定義ができるようになりそうです。内部ではstrcutoptと同じコードが動いているそうです。そこでclapのマクロ定義を使ってlinuxのheadコマンドの簡易的な実装を記述してみました。

このプログラムはclap = "3.0.0-beta.4"を使って実装しています。beta版なのでAPIはまだ安定していないことにご注意ください。使うのは3.0がreleaseされてからの方が良いでしょう。

myhead.rs
use clap::{AppSettings, Clap};
use std::path::PathBuf;
use std::fs::File;
use std::path::Path;
use std::io::{self, BufReader};
use std::io::prelude::*;

# [derive(Clap)]
# [clap(version="1.0", author= "yukichi hukuzawa")]
# [clap(setting = AppSettings::ColoredHelp)]
struct Opts {
    // nオプションにのみ対応
    #[clap(short = 'n', long)]
    lines: Option<u64>,
    #[clap(name="FILE", parse(from_os_str))]
    files: Vec<PathBuf>,
}

fn main() {
    let opts = Opts::parse();
    // headはデフォルトで頭10行を表示 
    let mut n = 10;
    if let Some(_n) = opts.lines  {
        // nオプションが指定されていれば上書き
        n = _n;
    }

    // ファイルが一個なら普通にプリント
    if opts.files.len() == 1 {
        print(&opts.files[0], n).expect("Can't print!");
        return;
    }

    // ファイルが複数なら、それぞれのヘッダーのファイル名もプリントする
    for file in &opts.files {
        println!("==> {:?} <==", file);
        print(file, n).expect("Can't print!");
    }


}

fn print(filepath: impl AsRef<Path>,n: u64) -> io::Result<()> 
{

     let file = File::open(filepath)?;
     let file = BufReader::new(file);
     let mut count = 0;
     for line in file.lines() {
        if count >= n {
            break;
        }
        count += 1;
        println!("{}", line?);
     }
     Ok(())
}

実行例

helpオプション

PS C:\Users\yukichi> myhead -h
実行結果

yukichi hukuzawa

    myhead.exe [OPTIONS] [FILE]...
ARGS:
    <FILE>...

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

OPTIONS:
    -n, --lines <LINES>

単ファイルを実行

PS C:\Users\yukichi> myhead .\myhead.rs
実行結果
use clap::{AppSettings, Clap};
use std::path::PathBuf;
use std::fs::File;
use std::path::Path;
use std::io::{self, BufReader};
use std::io::prelude::*;

# [derive(Clap)]
# [clap(version="1.0", author= "yukichi hukuzawa")]
# [clap(setting = AppSettings::ColoredHelp)]

nオプションつきで、複数ファイルに実行

PS C:\Users\yukichi> myhead -n 3 .\myhead.rs .\myuniq.rs
実行結果
==> ".\\myhead.rs" <==
use clap::{AppSettings, Clap};
use std::path::PathBuf;
use std::fs::File;
==> ".\\myuniq.rs" <==
use std::env;
use std::fs::{File, OpenOptions};
use std::collections::HashSet;

終わりに

この記事ではclapがマクロによる定義に対応しそうだよ!という紹介をしました。マクロって、使い手側からすると便利ですけど、謎めいてますよね。。

参考文献

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?