Tạo CLI dễ dàng trong Rust với clap và clap_mangen
Giới thiệu
Trong bài viết này, chúng ta sẽ tìm hiểu cách tạo một Command Line Interface (CLI) trong Rust bằng cách sử dụng thư viện clap và clap_mangen. Việc định nghĩa CLI một lần duy nhất với clap derive, chạy nó tại thời điểm thực thi và tự động tạo các trang man vào thời điểm xây dựng sẽ giúp bạn tiết kiệm thời gian và công sức. Hãy cùng khám phá cách thức hoạt động và những lợi ích mà nó mang lại cho các nhà phát triển.
Trang man là gì và tại sao bạn cần nó?
"man" là viết tắt của manual. Trên các hệ thống giống Unix, khi bạn sử dụng lệnh man <chủ đề>, tài liệu sẽ được mở trong terminal (ví dụ: man ls). Việc cung cấp các trang man cùng với CLI của bạn có nghĩa là:
- Trợ giúp tích hợp offline: Người dùng có thể đọc tài liệu mà không cần kết nối internet.
- Trải nghiệm người dùng nhất quán: Các lệnh như
mycli,mycli --helpvàman mycliđều cung cấp thông tin giống nhau. - Khám phá dễ dàng: Sử dụng
man -k mycli(apropos) cho phép người dùng tìm kiếm các lệnh liên quan.
Cài đặt nhanh
Trước tiên, hãy cài đặt các phụ thuộc cần thiết, sau đó bạn có thể theo dõi:
cargo add clap --features derive
cargo add clap --build --features derive
cargo add clap_mangen --build
Kết quả sẽ là một tệp Cargo.toml như sau:
[package]
name = "mycli"
version = "0.1.0"
edition = "2021"
build = "build.rs"
[dependencies]
clap = { version = "4", features = ["derive"] }
[build-dependencies]
clap = { version = "4", features = ["derive"] }
clap_mangen = "0.2"
Tại sao điều này lại hữu ích?
- Nguồn thông tin duy nhất: Các loại derive điều khiển việc phân tích tại thời điểm thực thi và tài liệu.
- Không có tài liệu lỗi thời: Các trang man được tái tạo mỗi khi xây dựng khi CLI có thay đổi.
- Tốt cho việc đóng gói: Bạn có thể gửi
target/man/*.1hoặc cài đặt chúng vào/usr/share/man. - Hỗ trợ các subcommand lồng nhau ngay từ đầu.
Định nghĩa CLI (derive)
Chúng ta sẽ định nghĩa toàn bộ CLI trong tệp src/cli.rs bằng cách sử dụng clap derive. Cả tệp nhị phân và tệp xây dựng đều sử dụng các loại này.
rust
// trong src/cli.rs
use clap::{Command, CommandFactory, Parser};
const LONG_ABOUT: &str = r#"
mycli là một ví dụ nhỏ về CLI thể hiện việc tự động tạo các trang man với clap và clap_mangen.
Nó thể hiện:
- Các subcommands lồng nhau (ví dụ: `config get`, `config set`)
- Tài liệu/help phong phú được lấy từ một nguồn thông tin duy nhất
- Tạo trang man vào thời điểm xây dựng đến `target/man/`
Các lệnh cấp cao:
- config: quản lý các giá trị cấu hình (get/set)
- server: chạy một server demo (địa chỉ/cổng/độ chi tiết)
- remote: thêm hoặc xóa một remote theo tên
"#;
#[derive(Debug, Parser)]
#[command(
name = "mycli",
about = "CLI ví dụ với các subcommands lồng nhau và tạo trang man",
long_about = LONG_ABOUT,
version
)]
pub struct Cli {
/// Subcommand cấp cao để thực thi
#[command(subcommand)]
pub command: Commands,
}
Chạy thời gian tối thiểu: main.rs
Tại thời điểm thực thi, bạn chỉ cần phân tích một lần và điều phối đến subcommand đã chọn. Đây là phần chính từ src/main.rs:
rust
use clap::Parser;
mod cli;
fn main() {
let opts = cli::Cli::parse();
match opts.command {
cli::Commands::Server(s) => {
println!("server bắt đầu trên {}:{} (độ chi tiết: {})", s.addr, s.port, s.verbose);
}
cli::Commands::Remote(r) => {
if r.remove {
println!("remote đã xóa: {}", r.name);
} else if let Some(url) = r.url {
println!("remote đã thêm: {} -> {}", r.name, url);
} else {
println!("thông tin remote đã yêu cầu: {}", r.name);
}
}
cli::Commands::Config(cfg) => match cfg.action {
cli::ConfigAction::Get(g) => println!("config get {} (định dạng: {:?})", g.key, g.format),
cli::ConfigAction::Set(s) => println!("config set {}={} (toàn cục: {})", s.key, s.value, s.global),
},
}
}
Chia sẻ CLI với build.rs
build.rs biên dịch như một crate riêng, vì vậy chúng ta bao gồm cùng một src/cli.rs (và đảm bảo clap có sẵn trong [build-dependencies] với features=["derive"]).
rust
// trong build.rs
mod cli {
include!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/cli.rs"));
}
let mut cmd = <cli::Cli as clap::CommandFactory>::command();
Tạo trang man một cách đệ quy
Thư viện clap_mangen có thể tạo các trang man cho lệnh gốc và tất cả các subcommand lồng nhau chỉ với một lời gọi:
rust
let out_dir = manifest_dir.join("target").join("man");
std::fs::create_dir_all(&out_dir)?;
let mut cmd = <cli::Cli as clap::CommandFactory>::command();
clap_mangen::generate_to(&mut cmd, &out_dir)?; // ghi mycli.1, mycli-config.1, mycli-config-get.1, v.v.
Cách các phần kết hợp với nhau
Nguồn thông tin duy nhất: src/cli.rs chứa tất cả các loại derive clap cho CLI của bạn. Cả thời gian thực thi và tệp xây dựng đều bao gồm tệp này để văn bản trợ giúp và cấu trúc không bị lệch.
-
Phân tích thời gian thực (
src/main.rs)
-Cli::parse()xây dựng các giá trị từ dòng lệnh.
- Mộtmatchđơn giản điều phối đến subcommand đã chọn. -
Tài liệu vào thời điểm xây dựng (
build.rs)
- Bao gồmsrc/cli.rsđể có cùng bố cụcCommand.
- Gọiclap_mangen::generate_totrên lệnh gốc.
- Đệ quy ghi các trang man cho lệnh gốc và từng subcommand vàotarget/man/. -
Lợi ích
- Không có thông số trùng lặp cho trợ giúp và tài liệu.
- Các trang man được tái tạo tự động khi xây dựng khi CLI thay đổi.
- Tốt cho việc đóng gói: gửitarget/man/*.1hoặc cài đặt chúng vào/usr/share/man.
Thử nghiệm tại địa phương
cargo build
ls target/man
man -l target/man/mycli.1 # lệnh gốc
man -l target/man/mycli-config-get.1 # subcommand lồng nhau
Những điều cần lưu ý và đánh đổi
build.rschạy trên mỗi lần xây dựng; việc tạo tài liệu nặng có thể làm chậm chu trình lặp lại. Đối với các pipeline tài liệu lớn hơn, hãy xem xét sử dụngcargo xtaskthay thế và chạy nó theo yêu cầu hoặc trong CI.- Một số máy chủ tài liệu (ví dụ: docs.rs) hạn chế hoặc bỏ qua tác động phụ từ tệp xây dựng. Giữ cho nó nhẹ nhàng.
- Nếu bạn cần tên/tên mục tùy chỉnh, hãy sử dụng
clap_mangen::Man::new(cmd).render(&mut writer)và tự đệ quy.