【Rust】构建自己的命令行工具,rust实现简单wget

【Rust】构建自己的命令行工具,rust实现简单wget

前言

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以执行参数解析。

扫描二维码关注公众号,回复: 15696206 查看本文章
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.tomldependencies

[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
}

实现wget的逻辑

下载功能还会在下载文件的每个块时更新进度条并打印出带有下载元数据的彩色文本。

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(())
}

猜你喜欢

转载自blog.csdn.net/qq_51173321/article/details/128934483