0
0
Lập trình
Harry Tran
Harry Tran106580903228332612117

Tạo CLI dễ dàng trong Rust với clap và clap_mangen

Đăng vào 8 tháng trước

• 6 phút đọc

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 clapclap_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 --helpman 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:

Copy
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:

Copy
[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/*.1 hoặ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 Copy
// 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 Copy
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 Copy
// 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 Copy
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.

  1. 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ột match đơn giản điều phối đến subcommand đã chọn.

  2. Tài liệu vào thời điểm xây dựng (build.rs)
    - Bao gồm src/cli.rs để có cùng bố cục Command.
    - Gọi clap_mangen::generate_to trên lệnh gốc.
    - Đệ quy ghi các trang man cho lệnh gốc và từng subcommand vào target/man/.

  3. 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ửi target/man/*.1 hoặc cài đặt chúng vào /usr/share/man.

Thử nghiệm tại địa phương

Copy
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.rs chạ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ụng cargo xtask thay 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.

Các bước tiếp theo

  • Các trình cài đặt gói có thể đặt các trang dưới /usr/share/man/man1/.
  • Công thức Homebrew có thể cài đặt target/man/*.1 trong quá trình brew install.
  • Khám phá tài liệu: clap trên docs.rsclap_mangen trên docs.rs để biết thêm các tùy chọn nâng cao.
Gợi ý câu hỏi phỏng vấn
Không có dữ liệu

Không có dữ liệu

Bài viết được đề xuất
Bài viết cùng tác giả

Bình luận

Chưa có bình luận nào

Chưa có bình luận nào