前言本教程将带你从零开始构建一个功能完整的Rust命令行工具包,涵盖文件操作、系统监控、网络工具、文本处理和加密功能。我们将学习如何构建一个具有交互式界面、自动补全、命令历史等现代CLI特性的工具。项目概览我们要构建的工具名为RustToolkit(RTK),它具有以下特性:🚀
本教程将带你从零开始构建一个功能完整的Rust命令行工具包,涵盖文件操作、系统监控、网络工具、文本处理和加密功能。
我们将学习如何构建一个具有交互式界面、自动补全、命令历史等现代CLI特性的工具。
我们要构建的工具名为 Rust Toolkit (RTK),它具有以下特性:
cargo new toolkit-rs --bin
cd toolkit-rs
首先配置项目的基本信息和依赖:
[package]
name = "toolkit-rs"
version = "0.1.0"
edition = "2024"
authors = ["Your Name <your.email@example.com>"]
description = "A powerful and efficient CLI toolkit built with Rust"
license = "MIT"
[[bin]]
name = "rtk"
path = "src/main.rs"
[dependencies]
# 命令行参数解析
clap = { version = "4", features = ["derive"] }
# 异步运行时
tokio = { version = "1", features = ["full"] }
# 序列化/反序列化
serde = { version = "1", features = ["derive"] }
serde_json = "1.0"
# HTTP客户端
reqwest = { version = "0.12", features = ["json"] }
# 加密哈希
sha2 = "0.10"
hex = "0.4"
# 文件系统遍历
walkdir = "2.5"
# 正则表达式
regex = "1.11"
# 时间处理
chrono = { version = "0.4", features = ["serde"] }
# 彩色输出
colored = "3.0"
# 进度条
indicatif = "0.18"
# 错误处理
anyhow = "1.0"
thiserror = "2.0"
# 系统信息
sysinfo = "0.37"
# 终端控制
crossterm = "0.29"
# 随机数生成
rand = "0.9"
# TUI框架
ratatui = { version = "0.29", features = ["crossterm"] }
# 交互式输入
inquire = "0.7.5"
# 枚举工具
strum = { version = "0.26", features = ["derive"] }
mkdir -p src/{commands,interactive,utils}
最终的项目结构:
src/
├── main.rs # 主入口点
├── commands/ # 命令模块
│ ├── mod.rs # 命令注册和自动补全
│ ├── file.rs # 文件操作
│ ├── system.rs # 系统信息
│ ├── network.rs # 网络工具
│ ├── text.rs # 文本处理
│ └── crypto.rs # 加密工具
├── interactive/ # 交互式Shell
│ ├── mod.rs
│ └── session.rs # 会话管理
└── utils/ # 工具函数
├── mod.rs
├── tui.rs # 终端UI组件
├── progress.rs # 进度显示
└── output.rs # 输出格式化
use anyhow::Result;
use colored::*;
mod commands;
mod interactive;
mod utils;
use interactive::InteractiveSession;
use utils::output;
#[tokio::main]
async fn main() -> Result<()> {
print_welcome();
let mut session = InteractiveSession::new();
session.run().await?;
Ok(())
}
fn print_welcome() {
// 清屏
print!("\x1B[2J\x1B[1;1H");
output::print_header("🦀 Welcome to Rust Toolkit");
println!();
output::print_info("A powerful collection of CLI utilities.");
println!();
println!(
"{} {} {} {}",
"Type".bright_black(),
"help".bright_yellow().bold(),
"for a list of commands, or".bright_black(),
"exit".bright_red().bold()
);
println!();
}
use colored::{Color, Colorize};
pub fn print_header(title: &str) {
println!("{}", title.cyan().bold());
println!("{}", "=".repeat(title.len()).blue());
}
pub fn print_success(msg: &str) {
println!("{} {}", "✅".green(), msg.green());
}
pub fn print_error(msg: &str) {
println!("{} {}", "❌".red(), msg.red());
}
pub fn print_warning(msg: &str) {
println!("{} {}", "⚠️".yellow(), msg.yellow());
}
pub fn print_info(msg: &str) {
println!("{} {}", "ℹ️".blue(), msg.blue());
}
pub fn print_normal(msg: &str) {
println!("{}", msg);
}
pub fn print_colored(msg: &str, color: Color) {
match color {
Color::Red => println!("{}", msg.red()),
Color::Green => println!("{}", msg.green()),
Color::Blue => println!("{}", msg.blue()),
Color::Yellow => println!("{}", msg.yellow()),
Color::Cyan => println!("{}", msg.cyan()),
Color::Magenta => println!("{}", msg.magenta()),
Color::White => println!("{}", msg.white()),
Color::Black => println!("{}", msg.black()),
_ => print_normal(msg),
}
}
pub mod output;
pub mod progress;
pub mod tui;
这是整个命令系统的核心,负责管理所有命令并提供自动补全功能:
pub mod file;
pub mod system;
pub mod network;
pub mod text;
pub mod crypto;
pub use file::FileCommands;
use strum::IntoEnumIterator;
pub use system::SystemCommands;
pub use network::NetworkCommands;
pub use text::TextCommands;
pub use crypto::CryptoCommands;
/// 将驼峰命名转换为短横线命名
///
/// 示例:
/// - LogAnalyze -> log-analyze
/// - Info -> info
/// - HttpRequest -> http-request
fn to_kebab_case(s: &str) -> String {
let mut result = String::new();
let mut chars = s.chars().peekable();
while let Some(c) = chars.next() {
if c.is_uppercase() && !result.is_empty() {
result.push('-');
}
result.push(c.to_lowercase().next().unwrap());
}
result
}
/// 获取所有可用命令用于自动补全建议
///
/// 这个函数使用CommandNames trait实现来自动生成完整的可用命令列表。
/// 当添加新的命令变体时,只需更新相应的CommandNames实现。
pub fn get_all_commands() -> Vec<String> {
let mut commands = Vec::new();
// 自动从每个命令类型中提取命令
for cmd in FileCommands::iter() {
commands.push(format!("file {}", to_kebab_case(&cmd.to_string())));
}
for cmd in SystemCommands::iter() {
commands.push(format!("system {}", to_kebab_case(&cmd.to_string())));
}
for cmd in NetworkCommands::iter() {
commands.push(format!("network {}", to_kebab_case(&cmd.to_string())));
}
for cmd in TextCommands::iter() {
commands.push(format!("text {}", to_kebab_case(&cmd.to_string())));
}
for cmd in CryptoCommands::iter() {
commands.push(format!("crypto {}", to_kebab_case(&cmd.to_string())));
}
// 全局命令 (在session.rs中处理)
commands.extend([
"help",
"clear",
"exit",
].iter().map(|s| s.to_string()));
commands
}
让我们实现一个完整的文件操作模块:
use crate::utils::{output, progress};
use anyhow::Result;
use clap::Subcommand;
use colored::Color;
use regex::Regex;
use sha2::{Digest, Sha256};
use std::collections::HashMap;
use std::fs;
use strum::{Display, EnumIter};
use walkdir::WalkDir;
#[derive(Subcommand, Clone, EnumIter, Display)]
pub enum FileCommands {
/// 按名称模式搜索文件
Search {
/// 搜索目录
#[arg(short, long, default_value = ".")]
dir: String,
/// 搜索模式 (支持正则表达式)
pattern: String,
/// 不区分大小写搜索
#[arg(short, long)]
ignore_case: bool,
},
/// 获取文件统计信息
Stats {
/// 分析目录
#[arg(short, long, default_value = ".")]
dir: String,
},
/// 批量重命名文件
Rename {
/// 包含文件的目录
#[arg(short, long, default_value = ".")]
dir: String,
/// 匹配模式 (正则表达式)
pattern: String,
/// 替换字符串
replacement: String,
/// 预览模式 (不实际重命名)
#[arg(short, long)]
preview: bool,
},
/// 查找重复文件
Duplicates {
/// 扫描目录
#[arg(short, long, default_value = ".")]
dir: String,
},
}
pub async fn handle_file_command(command: FileCommands) -> Result<()> {
match command {
FileCommands::Search {
dir,
pattern,
ignore_case,
} => {
search_files(&dir, &pattern, ignore_case).await?;
}
FileCommands::Stats { dir } => {
show_file_stats(&dir).await?;
}
FileCommands::Rename {
dir,
pattern,
replacement,
preview,
} => {
batch_rename(&dir, &pattern, &replacement, preview).await?;
}
FileCommands::Duplicates { dir } => {
find_duplicates(&dir).await?;
}
}
Ok(())
}
async fn search_files(dir: &str, pattern: &str, ignore_case: bool) -> Result<()> {
output::print_header("🔍 Searching files...");
let regex = if ignore_case {
Regex::new(&format!("(?i){}", pattern))?
} else {
Regex::new(pattern)?
};
let mut found_count = 0;
for entry in WalkDir::new(dir).into_iter().filter_map(|e| e.ok()) {
if let Some(filename) = entry.file_name().to_str() {
if regex.is_match(filename) {
let path = entry.path().display();
output::print_colored(&format!(" ✓ {}", path), Color::Green);
found_count += 1;
}
}
}
output::print_success(&format!("Found {} files", found_count));
Ok(())
}
async fn show_file_stats(dir: &str) -> Result<()> {
output::print_header("📊 Analyzing directory...");
let mut total_files = 0;
let mut total_dirs = 0;
let mut total_size = 0u64;
let mut extensions: HashMap<String, (u32, u64)> = HashMap::new();
let entries: Vec<_> = WalkDir::new(dir)
.into_iter()
.filter_map(|e| e.ok())
.collect();
let pb = progress::create_progress_bar(entries.len() as u64);
for entry in entries {
pb.inc(1);
if entry.file_type().is_dir() {
total_dirs += 1;
} else {
total_files += 1;
if let Ok(metadata) = entry.metadata() {
let size = metadata.len();
total_size += size;
if let Some(ext) = entry.path().extension().and_then(|s| s.to_str()) {
let ext = ext.to_lowercase();
let entry = extensions.entry(ext).or_insert((0, 0));
entry.0 += 1;
entry.1 += size;
}
}
}
}
pb.finish_with_message("Analysis complete!");
output::print_normal("");
output::print_colored("📈 Statistics:", Color::Green);
output::print_normal(&format!(" Total files: {}", total_files));
output::print_normal(&format!(" Total directories: {}", total_dirs));
output::print_normal(&format!(" Total size: {}", format_size(total_size)));
if !extensions.is_empty() {
output::print_normal("");
output::print_colored("📋 File type distribution:", Color::Green);
let mut ext_vec: Vec<_> = extensions.into_iter().collect();
ext_vec.sort_by(|a, b| b.1.1.cmp(&a.1.1));
for (ext, (count, size)) in ext_vec.iter().take(10) {
output::print_normal(&format!(
" .{}: {} files, {}",
ext,
count,
format_size(*size)
));
}
}
Ok(())
}
// ... 其他函数实现
fn format_size(size: u64) -> String {
const UNITS: &[&str] = &["B", "KB", "MB", "GB", "TB"];
let mut size = size as f64;
let mut unit_index = 0;
while size >= 1024.0 && unit_index < UNITS.len() - 1 {
size /= 1024.0;
unit_index += 1;
}
format!("{:.2} {}", size, UNITS[unit_index])
}
这是项目的亮点之一,实现了一个功能完整的交互式终端界面:
use anyhow::Result;
use crossterm::{
event::{self, Event, KeyCode, KeyEventKind},
terminal::{disable_raw_mode, enable_raw_mode},
};
use std::io::{self, Write};
pub struct TuiApp {
input: String,
all_suggestions: Vec<String>,
filtered_suggestions: Vec<String>,
show_suggestions: bool,
selected_index: usize,
history: Vec<String>,
history_index: Option<usize>,
current_input: String, // 保存当前输入内容
scroll_offset: usize, // 建议列表的滚动偏移
}
impl TuiApp {
pub fn with_history(commands: Vec<String>, history: Vec<String>) -> Self {
Self {
input: String::new(),
all_suggestions: commands,
filtered_suggestions: Vec::new(),
show_suggestions: false,
selected_index: 0,
history,
history_index: None,
current_input: String::new(),
scroll_offset: 0,
}
}
pub fn run(&mut self) -> Result<Option<String>> {
enable_raw_mode()?;
print!("❯ ");
io::stdout().flush()?;
let result = self.run_input_loop();
disable_raw_mode()?;
println!();
result
}
fn run_input_loop(&mut self) -> Result<Option<String>> {
loop {
if let Event::Key(key) = event::read()? {
if key.kind == KeyEventKind::Press {
match key.code {
KeyCode::Enter => {
if self.show_suggestions && !self.filtered_suggestions.is_empty() {
if let Some(selected) =
self.filtered_suggestions.get(self.selected_index).cloned()
{
self.clear_suggestions_display()?;
return Ok(Some(selected));
}
}
if self.show_suggestions {
self.clear_suggestions_display()?;
}
return Ok(Some(self.input.clone()));
}
KeyCode::Esc => {
if self.show_suggestions {
self.hide_suggestions()?;
} else {
return Ok(None);
}
}
KeyCode::Char('/') => {
if !self.show_suggestions {
self.show_suggestions()?;
} else {
self.input.push('/');
self.update_suggestions_with_filter()?;
}
}
KeyCode::Tab => {
if !self.show_suggestions {
self.show_suggestions()?;
} else if !self.filtered_suggestions.is_empty() {
if let Some(selected) =
self.filtered_suggestions.get(self.selected_index)
{
self.input = selected.clone();
self.hide_suggestions()?;
self.redraw_input_line()?;
}
}
}
KeyCode::Char(c) => {
// 如果正在浏览历史记录,重置历史索引
if self.history_index.is_some() {
self.history_index = None;
}
self.input.push(c);
if self.show_suggestions {
self.update_suggestions_with_filter()?;
} else {
print!("{}", c);
io::stdout().flush()?;
}
}
KeyCode::Backspace => {
if !self.input.is_empty() {
// 如果正在浏览历史记录,重置历史索引
if self.history_index.is_some() {
self.history_index = None;
}
self.input.pop();
if self.show_suggestions {
if self.input.is_empty() {
self.hide_suggestions()?;
} else {
self.update_suggestions_with_filter()?;
}
} else {
print!("\x08 \x08"); // 退格
io::stdout().flush()?;
}
}
}
KeyCode::Up => {
if self.show_suggestions {
self.previous_suggestion()?;
} else {
self.previous_history()?;
}
}
KeyCode::Down => {
if self.show_suggestions {
self.next_suggestion()?;
} else {
self.next_history()?;
}
}
_ => {}
}
}
}
}
}
// ... 其他方法实现
}
use anyhow::Result;
use colored::*;
use crate::commands;
use crate::utils::{output, tui::TuiApp};
use clap::{Parser, Subcommand};
pub struct InteractiveSession {
history: Vec<String>,
}
#[derive(Parser)]
#[command(name = "rtk", disable_help_flag = true)]
#[command(about = "Rust Toolkit - A powerful CLI utility collection")]
#[command(version = "0.1.0")]
struct Cli {
#[command(subcommand)]
command: Option<Commands>,
}
#[derive(Subcommand, Clone)]
enum Commands {
/// 文件操作
File {
#[command(subcommand)]
action: commands::FileCommands,
},
/// 系统信息
System {
#[command(subcommand)]
action: commands::SystemCommands,
},
/// 网络工具
Network {
#[command(subcommand)]
action: commands::NetworkCommands,
},
/// 文本处理工具
Text {
#[command(subcommand)]
action: commands::TextCommands,
},
/// 加密工具
Crypto {
#[command(subcommand)]
action: commands::CryptoCommands,
},
}
impl InteractiveSession {
pub fn new() -> Self {
Self {
history: Vec::new(),
}
}
pub async fn run(&mut self) -> Result<()> {
loop {
// 从commands模块获取动态命令列表
let command_list = commands::get_all_commands();
let mut app = TuiApp::with_history(
command_list,
self.history.clone()
);
let user_input_result = app.run();
match user_input_result {
Ok(Some(input)) => {
self.history.push(input.clone());
let trimmed_input = input.trim();
if trimmed_input.is_empty() {
continue;
}
match trimmed_input {
"exit" | "quit" => {
output::print_success("👋 Goodbye!");
break;
}
"clear" => {
// 清屏并重新开始循环
print!("\x1B[2J\x1B[1;1H");
continue;
}
"help" => {
self.show_help();
println!();
continue;
}
_ => {}
}
let args = std::iter::once("rtk".to_string())
.chain(trimmed_input.split_whitespace().map(|s| s.to_string()));
match Cli::try_parse_from(args) {
Ok(cli) => {
if let Some(command) = cli.command {
if let Err(e) = self.handle_command(command.clone()).await {
output::print_error(&e.to_string());
}
println!(); // 添加换行符用于间距
} else {
self.show_help();
}
}
Err(e) => {
output::print_error(&e.to_string());
}
}
}
Ok(None) => {
// 用户按了Esc,退出
output::print_success("👋 Goodbye!");
break;
}
Err(e) => {
// TUI循环本身失败,打印错误并退出
output::print_error(&format!("TUI Error: {}", e));
break;
}
}
}
Ok(())
}
async fn handle_command(&self, command: Commands) -> Result<()> {
match command {
Commands::File { action } => {
commands::file::handle_file_command(action).await?;
}
Commands::System { action } => {
commands::system::handle_system_command(action).await?;
}
Commands::Network { action } => {
commands::network::handle_network_command(action).await?;
}
Commands::Text { action } => {
commands::text::handle_text_command(action).await?;
}
Commands::Crypto { action } => {
commands::crypto::handle_crypto_command(action).await?;
}
}
Ok(())
}
fn show_help(&self) {
output::print_header("🦀 Rust Toolkit Help");
println!();
output::print_info("A powerful collection of CLI utilities.");
println!();
println!("{}", "Available Commands:".bright_white().bold());
println!(" {} File operations (stats, search, rename, duplicates)", "file".bright_green());
println!(" {} System information (info, processes, monitor)", "system".bright_green());
println!(" {} Network utilities (ping, scan, http)", "network".bright_green());
println!(" {} Text processing (count, log, replace)", "text".bright_green());
println!(" {} Cryptographic tools (hash, password, base64, caesar)", "crypto".bright_green());
println!();
println!("{}", "Global Commands:".bright_white().bold());
println!(" {} Show this help message", "help".bright_yellow());
println!(" {} Clear the screen", "clear".bright_yellow());
println!(" {} Exit the application", "exit".bright_red());
println!();
println!("{}", "Usage:".bright_black());
println!("{}", " <command> <subcommand> [options]".bright_black());
println!("{}", " Example: file stats --dir .".bright_black());
println!("{}", " For help on a specific command, type: <command> --help".bright_black());
}
}
use indicatif::{ProgressBar, ProgressStyle};
pub fn create_progress_bar(len: u64) -> ProgressBar {
let pb = ProgressBar::new(len);
pb.set_style(
ProgressStyle::default_bar()
.template("{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {pos}/{len} ({eta})")
.unwrap()
.progress_chars("#>-"),
);
pb
}
pub fn create_spinner(msg: &str) -> ProgressBar {
let pb = ProgressBar::new_spinner();
pb.set_style(
ProgressStyle::default_spinner()
.template("{spinner:.green} {msg}")
.unwrap(),
);
pb.set_message(msg.to_string());
pb
}
use crate::utils::output;
use anyhow::Result;
use clap::Subcommand;
use colored::*;
use rand::Rng;
use sha2::{Digest, Sha256};
use std::fs;
use strum::{Display, EnumIter};
#[derive(Subcommand, Clone, EnumIter, Display)]
pub enum CryptoCommands {
/// 计算文件哈希
Hash {
/// 要哈希的文件
#[arg(short, long)]
file: String,
/// 哈希算法 (md5, sha1, sha256, sha512)
#[arg(short, long, default_value = "sha256")]
algorithm: String,
},
/// 生成安全密码
Password {
/// 密码长度
#[arg(short, long, default_value = "16")]
length: usize,
/// 包含大写字母
#[arg(long, default_value = "true")]
uppercase: bool,
/// 包含小写字母
#[arg(long, default_value = "true")]
lowercase: bool,
/// 包含数字
#[arg(long, default_value = "true")]
numbers: bool,
/// 包含符号
#[arg(long)]
symbols: bool,
},
/// Base64编码/解码
Base64 {
/// 输入文本或文件
input: String,
/// 解码而不是编码
#[arg(short, long)]
decode: bool,
/// 将输入视为文件路径
#[arg(short, long)]
file: bool,
},
/// 简单文本加密 (凯撒密码)
Caesar {
/// 要加密/解密的文本
text: String,
/// 偏移量
#[arg(short, long, default_value = "3")]
shift: i32,
/// 解密而不是加密
#[arg(short, long)]
decrypt: bool,
},
}
pub async fn handle_crypto_command(command: CryptoCommands) -> Result<()> {
match command {
CryptoCommands::Hash { file, algorithm } => {
calculate_hash(&file, &algorithm).await?;
}
CryptoCommands::Password {
length,
uppercase,
lowercase,
numbers,
symbols,
} => {
generate_password(length, uppercase, lowercase, numbers, symbols).await?;
}
CryptoCommands::Base64 {
input,
decode,
file,
} => {
handle_base64(&input, decode, file).await?;
}
CryptoCommands::Caesar {
text,
shift,
decrypt,
} => {
caesar_cipher(&text, shift, decrypt).await?;
}
}
Ok(())
}
async fn generate_password(
length: usize,
uppercase: bool,
lowercase: bool,
numbers: bool,
symbols: bool,
) -> Result<()> {
output::print_header("🔑 Generate Secure Password");
let mut charset = String::new();
if uppercase {
charset.push_str("ABCDEFGHIJKLMNOPQRSTUVWXYZ");
}
if lowercase {
charset.push_str("abcdefghijklmnopqrstuvwxyz");
}
if numbers {
charset.push_str("0123456789");
}
if symbols {
charset.push_str("!@#$%^&*()_+-=[]{}|;:,.<>?");
}
if charset.is_empty() {
output::print_error("At least one character type must be selected");
return Err(anyhow::anyhow!(
"At least one character type must be selected"
));
}
let charset_chars: Vec<char> = charset.chars().collect();
let mut rng = rand::rng();
let password: String = (0..length)
.map(|_| charset_chars[rng.random_range(0..charset_chars.len())])
.collect();
output::print_normal("");
output::print_colored("🔐 Generated Password:", Color::Green);
output::print_normal(&format!("Length: {} characters", length));
output::print_colored(&format!("Password: {}", password), Color::Yellow);
let strength = evaluate_password_strength(&password);
output::print_normal(&format!("Strength: {}", strength));
if length < 12 {
output::print_warning(
"Consider using a longer password (12+ characters) for better security",
);
}
Ok(())
}
fn evaluate_password_strength(password: &str) -> String {
let mut score = 0;
if password.len() >= 8 {
score += 1;
}
if password.len() >= 12 {
score += 1;
}
if password.chars().any(|c| c.is_ascii_lowercase()) {
score += 1;
}
if password.chars().any(|c| c.is_ascii_uppercase()) {
score += 1;
}
if password.chars().any(|c| c.is_ascii_digit()) {
score += 1;
}
if password.chars().any(|c| !c.is_ascii_alphanumeric()) {
score += 1;
}
match score {
0..=2 => "Weak".red().to_string(),
3..=4 => "Medium".yellow().to_string(),
5..=6 => "Strong".green().to_string(),
_ => "Very Strong".green().bold().to_string(),
}
}
// ... 其他函数实现
# 开发构建
cargo build
# 发布构建
cargo build --release
# 直接运行
cargo run
# 或者运行构建后的二进制文件
./target/release/rtk
启动程序后,你可以尝试以下命令:
# 文件操作
file stats --dir .
file search --pattern "*.rs"
file duplicates --dir .
# 加密工具
crypto password --length 20 --symbols
crypto hash --file Cargo.toml
crypto base64 "Hello World"
# 系统信息
system info
system processes
# 使用自动补全
# 按 '/' 或 Tab 键显示可用命令
# 使用上下箭头键浏览建议
# 使用上下箭头键浏览命令历史
使用 anyhow
和 thiserror
进行优雅的错误处理:
use thiserror::Error;
#[derive(Error, Debug)]
pub enum ToolkitError {
#[error("File not found: {path}")]
FileNotFound { path: String },
#[error("Invalid pattern: {pattern}")]
InvalidPattern { pattern: String },
#[error("Network error: {0}")]
Network(#[from] reqwest::Error),
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
}
添加配置文件支持:
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug)]
pub struct Config {
pub default_dir: String,
pub max_results: usize,
pub theme: String,
}
impl Default for Config {
fn default() -> Self {
Self {
default_dir: ".".to_string(),
max_results: 100,
theme: "default".to_string(),
}
}
}
为了支持扩展,可以设计一个简单的插件系统:
pub trait Plugin {
fn name(&self) -> &str;
fn description(&self) -> &str;
fn execute(&self, args: &[String]) -> Result<()>;
}
pub struct PluginManager {
plugins: Vec<Box<dyn Plugin>>,
}
impl PluginManager {
pub fn new() -> Self {
Self {
plugins: Vec::new(),
}
}
pub fn register_plugin(&mut self, plugin: Box<dyn Plugin>) {
self.plugins.push(plugin);
}
pub fn execute_plugin(&self, name: &str, args: &[String]) -> Result<()> {
for plugin in &self.plugins {
if plugin.name() == name {
return plugin.execute(args);
}
}
Err(anyhow::anyhow!("Plugin not found: {}", name))
}
}
充分利用Tokio的异步特性:
use tokio::fs;
use tokio::task;
async fn process_files_concurrently(files: Vec<String>) -> Result<Vec<String>> {
let tasks: Vec<_> = files
.into_iter()
.map(|file| {
task::spawn(async move {
// 异步处理每个文件
process_single_file(file).await
})
})
.collect();
let mut results = Vec::new();
for task in tasks {
results.push(task.await??);
}
Ok(results)
}
使用流式处理大文件:
use tokio::io::{AsyncBufReadExt, BufReader};
async fn process_large_file(path: &str) -> Result<()> {
let file = fs::File::open(path).await?;
let reader = BufReader::new(file);
let mut lines = reader.lines();
while let Some(line) = lines.next_line().await? {
// 逐行处理,避免将整个文件加载到内存
process_line(&line).await?;
}
Ok(())
}
为不同平台构建:
# 安装目标平台
rustup target add x86_64-pc-windows-gnu
rustup target add x86_64-apple-darwin
rustup target add x86_64-unknown-linux-gnu
# 交叉编译
cargo build --release --target x86_64-pc-windows-gnu
cargo build --release --target x86_64-apple-darwin
cargo build --release --target x86_64-unknown-linux-gnu
#!/bin/bash
# install.sh
set -e
REPO="your-username/toolkit-rs"
BINARY_NAME="rtk"
# 检测操作系统和架构
OS=$(uname -s | tr '[:upper:]' '[:lower:]')
ARCH=$(uname -m)
case $ARCH in
x86_64) ARCH="x86_64" ;;
arm64|aarch64) ARCH="aarch64" ;;
*) echo "Unsupported architecture: $ARCH"; exit 1 ;;
esac
# 下载并安装
DOWNLOAD_URL="https://github.com/$REPO/releases/latest/download/$BINARY_NAME-$OS-$ARCH"
INSTALL_DIR="$HOME/.local/bin"
mkdir -p "$INSTALL_DIR"
curl -L "$DOWNLOAD_URL" -o "$INSTALL_DIR/$BINARY_NAME"
chmod +x "$INSTALL_DIR/$BINARY_NAME"
echo "✅ $BINARY_NAME installed successfully to $INSTALL_DIR"
echo "Make sure $INSTALL_DIR is in your PATH"
通过这个教程,我们从零开始构建了一个功能完整的Rust命令行工具,涵盖了:
这个项目展示了如何使用Rust构建一个生产级别的命令行工具,结合了现代CLI工具的最佳实践和Rust语言的优势。你可以基于这个框架继续添加更多功能,或者将其作为其他CLI项目的起点。
希望这个教程对你学习Rust和CLI开发有所帮助!🦀
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!