on
使用Clap開發CLI工具
在工作中,我們常常會遇到一些需要重複執行的作業,專案時程安排不會特別開發複雜的界面,不需要精緻的使用者界面下往往會需要開發人員手動執行,這類的需求其實很適合寫成CLI來簡化流程。最近就因為類似的需求用Rust寫了一個簡單的小工具來增加工作效率。
Why Rust?
在開始之前我想先簡單分享一下感想,其實之前也試著用C#寫過CLI工具,那時候的DX其實不好,因為解析命令的部份其實不好處理,就算有框架的協助也需要撰寫非常多不好理解的程式碼。我覺得不方便的地方在於需要為每一個指令、參數、選項等等逐一設定,這些設定跟實際功能運作的連結其實不強。但Rust的Clap不太一樣,我覺得他的優點在於可使很直覺得使用資料結構來組織CLI工具中的指令與功能,這一點要歸功於Rust的型別系統具有強大的表達力。
Clap 介紹
Clap是rust中用來開發CLI的知名套件,查詢速度最快的ripgrep就是使用Clap開發的。下面是一個簡單的例子:
fn main() {
let matches = Command::new("echor")
.version("0.1.0")
.author("marvinhsu")
.about("Rust echo")
.arg(
Arg::new("text")
.value_name("TEXT")
.help("Input text")
.required(true)
.num_args(1..),
)
.arg(
Arg::new("omit_newline")
.short('n')
.help("Do not print newline")
.action(ArgAction::SetTrue),
)
.get_matches();
todo!();
}
詳細的程式碼可以參考我之前練習的程式,這個repo是參考Command-Line Rust的練習。這段程式碼是使用Clap的Builder API撰寫,這個風格其實也是大多數CLI框架的寫法,需要針對不同的命令逐一設定,再用大量的判斷式來解析接收的命令並且分派需要的實做。但Clap在後來的版本中加入了Derive API,大大增強了這部份的DX,可以簡單直覺的用資料結構來組織這些命令,並且用物件的思維來為這些命令實作對應的程式。官方文件的建議是通常情況下應該使用Derive API,除非有強烈的性能要求或是需要更精細的設定才考慮使用Builder API,因為Derive API真的相對容易理解。
Clap With Derive API
接下來就來介紹Derive API吧,我們以大家常用的Git為例,Git具有checkout、branch、push、commit等等指令,以checkout
為例後面需要加上一個分支名稱的參數,另外也有-f這個強制切換分之的flag,如果以derive的方式表達會是下面的程式碼:
use clap::{Parser, Args, Subcommand};
// Git的主要指令
#[derive(Parser)]
#[command(author, version, about, long_about = None)]
struct Git {
#[command(subcommand)]
subcmd: SubCommand,
}
// 使用enum來描述不同的subCommand
#[derive(Subcommand)]
enum SubCommand {
// Checkout指令會有自己的資料成員
Checkout(Checkout),
Branch,
Push,
Commit
}
// Checkout指令需要的資料
#[derive(Args)]
struct Checkout {
branch_name: String,
#[arg(long, short)]
force: bool,
}
// Checkout指令本身具有execute的關聯方法,呼叫後會取得該命令的參數並執行
impl Checkout {
fn execute(&self) {
println!("Checkout branch: {}", self.branch_name);
}
}
fn main() {
let git = Git::parse();
// 使用Pattern Match來解析應該要執行的指令
match git.subcmd {
SubCommand::Checkout(checkout) => {
checkout.execute();
}
SubCommand::Branch => todo!(),
SubCommand::Push => todo!(),
SubCommand::Commit => todo!(),
}
}
我覺得這個寫法比Builder API清晰易讀很多,實做起來也十分簡單。唯一的缺點大概就是難以客製化生成的文件吧!下面是第一層的文件,看起來沒有異常:
❯ cargo run -- -h
Usage: clitest <COMMAND>
Commands:
checkout
branch
push
commit
help Print this message or the help of the given subcommand(s)
Options:
-h, --help Print help
-V, --version Print version
接下來看看checkout
的文件:
❯ cargo run -- checkout -h
Usage: clitest checkout [OPTIONS] <BRANCH_NAME>
Arguments:
<BRANCH_NAME>
Options:
-f, --force
-h, --help Print help
基本上不妨礙使用,但在CLI的慣例中-f
這類true/false的設定應該叫做flag
而不是Option
(雖然Git本身的文件沒有這種區別),如果想要客製化設定文件,就只能透過Builder API進行,因為Derive本來就是為了簡單易用而提供的API。
小結
Clap提供了Derive API的方式來開發CLI工具,透過資料結構來描述指令功能方式真的讓開發簡單而直覺,我覺得這主要是因為Rust的型別系統讓開發人員更容易用資料結構去描述功能吧!