はじめに
みなさま、コマンドラインツールは何で作っているでしょうか。Rustではclapやstructoptが有名ですよね。今回は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されてからの方が良いでしょう。
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がマクロによる定義に対応しそうだよ!という紹介をしました。マクロって、使い手側からすると便利ですけど、謎めいてますよね。。