build-your-own-x项目:https://github.com/codecrafters-io/build-your-own-x
以上项目的其中之一:https://mattgathu.github.io/2017/08/29/writing-cli-app-rust.html
以rust新版本重新实现该工具
此练习将介绍如何使用 Rust 编写命令行工具,方法是编写一个简单的用于文件下载的流行工具wget
。
我们的简单实现将具有命令行工具wget
中所需的以下功能:
我们使用 rust 的构建工具(和包管理器)Cargo
来设置我们的项目框架。
cargo new myget
这将创建一个名为myget的新项目。
cd myget
tree .
.
├── Cargo.toml
└── src
└── main.rs
1 directory, 2 files
cargo add clap --features derive
我们将使用 clap crate 来解析命令行参数。我们 通过更新货物的舱单文件依赖项部分添加到我们的项目中。
[package]
name = "myget"
version = "0.1.0"
edition = "2021"
authors = ["Star-tears"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
clap = { version = "4.1.4", features = ["derive"] }
然后,我们更新main.rs以执行参数解析。
use clap::Parser;
#[derive(Parser, Debug)]
#[command(author="Star-tears", version="0.1.0", about="myget clone written in Rust", long_about = None)]
struct Cli {
/// Download url
#[arg(short, long)]
url: String,
}
fn main() {
let cli = Cli::parse();
println!("Value of url: {}", cli.url);
}
我们现在可以使用 Cargo 测试我们的参数解析器。
cargo run
Finished dev [unoptimized + debuginfo] target(s) in 0.73s
Running `target\debug\myget.exe`
error: the following required arguments were not provided:
--url <URL>
Usage: myget.exe --url <URL>
For more information, try '--help'.
error: process didn't exit successfully: `target\debug\myget.exe` (exit code:
我们可以通过在调用时添加--
来将参数传递给我们的程序
cargo run -- -h
Finished dev [unoptimized + debuginfo] target(s) in 0.05s
Running `target\debug\myget.exe -h`
myget clone written in Rust
Usage: myget.exe --url <URL>
Options:
-u, --url <URL> Download url
-h, --help Print help
Indicatif 库,用于指示命令行应用程序中的进度。我们使用它为我们的 wget 克隆实现进度条和微调器。
Indicatif依赖于另一个板条箱,console,并将其用于彩色输出。 我们用它实现控制台打印彩色文本。
我们使用 reqwest 库来实现文件下载功能 接收 URL 并将其下载到本地文件中。
cargo add [email protected]
cargo add [email protected]
cargo add reqwest --features blocking
最终Cargo.toml
的dependencies
:
[dependencies]
clap = { version = "4.1.4", features = ["derive"] }
console = "0.15.5"
indicatif = "0.17.3"
reqwest = { version = "0.11.14", features = ["blocking"] }
以下是用于创建进度条的函数:
fn create_progress_bar(quiet_mode: bool, msg: String, length: Option<u64>) -> ProgressBar {
let bar = match quiet_mode {
true => ProgressBar::hidden(),
false => match length {
Some(len) => ProgressBar::new(len),
None => ProgressBar::new_spinner(),
},
};
bar.set_message(msg);
match length.is_some() {
true => bar
.set_style(ProgressStyle::default_bar()
.template("{msg} {spinner:.green} [{elapsed_precise}] [{wide_bar:.cyan/blue}] {bytes}/{total_bytes} eta: {eta}").unwrap()
.progress_chars("=> ")),
false => bar.set_style(ProgressStyle::default_spinner()),
};
bar
}
下载功能还会在下载文件的每个块时更新进度条并打印出带有下载元数据的彩色文本。
fn download(target: String, quiet_mode: bool) -> Result<(), Box<dyn std::error::Error>> {
let url = Url::parse(&target)?;
let client = Client::new();
let mut res = client.get(url).send().unwrap();
println!(
"HTTP request sent... {}",
console::style(format!("{}", res.status())).green()
);
if res.status().is_success() {
let headers = res.headers().clone();
let ct_len = res.content_length();
let ct_type = headers.get("content-type").unwrap();
match ct_len {
Some(len) => {
println!(
"Length: {} ({})",
console::style(len).green(),
console::style(format!("{}", HumanBytes(len))).red()
);
}
None => {
println!("Length: {}", console::style("unknown").red());
}
}
println!(
"Type: {}",
console::style(ct_type.to_str().unwrap()).green()
);
let fname = target.split("/").last().unwrap();
println!("Saving to: {}", console::style(fname).green());
let chunk_size = match ct_len {
Some(x) => x as usize / 99,
None => 1024usize, // default chunk size
};
let mut buf: Vec<u8> = Vec::new();
let bar = create_progress_bar(quiet_mode, fname.to_string(), ct_len);
loop {
let mut buffer = vec![0; chunk_size];
let bcount = res.read(&mut buffer[..]).unwrap();
buffer.truncate(bcount);
if buffer.is_empty() {
break;
}
buf.extend(buffer.into_boxed_slice().into_vec().iter().clone());
bar.inc(bcount as u64);
}
bar.finish();
let mut file = match File::create(Path::new(fname)) {
Ok(file) => file,
Err(why) => panic!("couldn't create {}: {}", fname, why),
};
file.write_all(buf.as_slice()).unwrap();
}
Ok(())
}