[Rust] 独自のコマンドラインツールを構築します。Rust はシンプルな wget を実装します。

[Rust] 独自のコマンドラインツールを構築します。Rust はシンプルな wget を実装します。

序文

build-your-own-x プロジェクト: https://github.com/codecrafters-io/build-your-own-x

上記のプロジェクトの 1 つ: https://mattgathu.github.io/2017/08/29/writing-cli-app-rust.html

新しいバージョンの Rust を使用してツールを再実装する

この演習では、ファイルをダウンロードするための簡単な一般的なツールを作成することにより、Rust でコマンドライン ツールを作成する方法を示しますwget

ターゲット関数

wgetこの単純な実装には、コマンド ライン ツールに必要な次の関数が含まれます。

  • パラメータ分析
  • カラー出力
  • プログレスバー

新しいプロジェクト

Cargoプロジェクトのフレームワークをセットアップするには、Rust のビルド ツール (およびパッケージ マネージャー) を使用します。

cargo new myget

これにより、myget という新しいプロジェクトが作成されます。

cd myget
tree .
.
├── Cargo.toml
└── src
    └── main.rs

1 directory, 2 files

パラメータ分析

cargo add clap --features derive

clapクレートを使用してコマンドライン引数を解析します。Cargo のマニフェスト ファイルの依存関係セクションを更新して、プロジェクトに追加します。

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

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