🎀
Rustでコマンドラインツールを爆速で作る環境を構築しよう2022年版

ekusiadadus

ekusiadadus

2022年12月17日
Rustcliclap

こんにちは、@ekusiadadusです。 CLI ツール作っていますか? CLI ツールを Rust で作るときに、毎回環境を整えるのが面倒だったので、テンプレを作りました。 今回はそのテンプレを使って、簡易的な CLI ツールを Rust で爆速で作ってみます。

テンプレートはこちらです。

今回作るコマンドラインツールはこちらです。

https://crates.io/ に登録して配布した CLI ツールです。

https://crates.io/crates/dirsearch

cargo install dirsearchでインストールできます。

image.png

# テンプレを使って CLI ツールを作る

今回は、よくあるディレクトリ配下に存在するフォルダ、ファイルの数とその大きさを表示する CLI ツールを爆速で作ってみます。

# テンプレをクローンする

まずは、テンプレをクローンします。

git clone https://github.com/ekusiadadus/rust-cli-template.git

# [object Object], で実行してみる

テンプレを使うには、テンプレのディレクトリに移動して、cargo runを実行します。

cd rust-cli-template
cargo run

うまくいけば、こんな感じで、rust-cli-templateという名前の CLI ツールが実行されます。

既にこの段階で、cargo run で CLI ツールが実行できる環境が整っています。

image.png

# (余談) mold + cargo watch を使う

mold + cargo watch は使わなくてもいいですが、以下の点で便利です。

  • ホットリロードされる開発環境
  • ビルドが速くなる

ここら辺は、参考記事を貼っておくのでもしよかったら使ってみてください。

今回の場合、cargo watch -s 'mold -run cargo run' でホットリロードできる環境にしています。 Makefile も載せてあるので、make watch で動きます。

保存すると自動的にビルドされて、実行されます。

build-with-mold.gif

# ディレクトリ配下のファイル、フォルダの数と大きさを表示する

walkDir を使って、ディレクトリ配下のファイル、フォルダの数と大きさを表示するようにします。

walkDir をインストールする

walkdirをつかいます。

cargo add walkdir

walkDir を使うには、use walkdir::WalkDir; を追加します。

ディレクトリ配下のファイル、フォルダを取得する

use walkdir::WalkDir;

fn main() {
    for entry in WalkDir::new(".") {
        let entry = entry.unwrap();
        println!("{}", entry.path().display());
    }
}

cargo run で実行すると、ディレクトリ配下のファイル、フォルダが表示されます。

image.png

ディレクトリ配下のファイル、フォルダの数と大きさを表示する

ディレクトリ配下のファイルと、フォルダを走査して、ファイルの数と大きさを表示するようにします。 walkdir を使うと非常に簡単にファイルとフォルダを走査できます。

use walkdir::WalkDir;
const DIR: &str = "./";

fn main() {
    let mut size: u64 = 0;
    let mut count: u64 = 0;

    for entry in WalkDir::new(DIR).into_iter().filter_map(|e| e.ok()) {
        let path = entry.path();
        if path.is_file() {
            size += path.metadata().unwrap().len();
            count += 1;
        }
        println!("{}", entry.path().display());
    }

    println!("{} files, {} bytes", count, size);
}

実際にcargo run で走らせてみるとこんな感じ。

image.png

現在のディレクトリ配下には、626 個のファイルが存在して、総合で304742935 bytesの大きさになることがわかります。

(余談 2) ファイルサイズを Rust でいい感じに表示するには...

ファイルサイズを Rust でいい感じに表示するには、file_sizeを使います。

use file_size::fit_4;

assert_eq!(&fit_4(999), "999");
assert_eq!(&fit_4(12345), "12K");
assert_eq!(&fit_4(999_999), "1.0M");
assert_eq!(&fit_4(7_155_456_789_012), "7.2T");

こんな感じで、いい感じにファイルサイズを表示してくれるクレートです。

println!("{} files, {} bytes", count, fit_4(size));

使ってみるとこんな感じ。

image.png

ええやん。

ディレクトリ配下のファイルで上位 N 件を持ってくる

ディレクトリ配下のファイルで上位 N 件を持ってくるようにします。 あと main が大きくなってきたので、関数に切り出します。

fn get_dir_size(dir: &str) -> Result<(), Box<dyn Error>> {
    let mut size: u64 = 0;
    let mut count: u64 = 0;
    let mut tops: Vec<Entry> = Vec::with_capacity(NUM + 1);
    let mut min_tops: u64 = 0;

    for entry in WalkDir::new(dir).into_iter().filter_map(|e| e.ok()) {
        let path = entry.path();
        if path.is_file() {
            let t = path.metadata().unwrap().len();
            if t > min_tops {
                tops.push(Entry {
                    path: path.to_str().unwrap().to_string(),
                    size: t,
                });
                tops.sort_by(|a, b| b.size.cmp(&a.size));
                tops.truncate(NUM);
                min_tops = tops.last().unwrap().size;
            }
            size += path.metadata().unwrap().len();
            count += 1;
        }
    }

    println!("{} files, {} bytes", count, fit_4(size));
    println!("{} largest files:", NUM);
    println!("{} | {}", "Size", "Path");
    for t in tops {
        println!("{} | {}", fit_4(t.size), t.path);
    }

    Ok(())
}

実行するとこんな感じ。

image.png

ディレクトリ配下のファイルで上位 N 件を持ってくる (並列処理)

Clap を使って、コマンドラインツールにする

clap v4 を使って、コマンドラインツールにします。 v4 は、v3 とはかなり違うので、clap v4 のドキュメントを見ながら進めましょう。

use clap::Parser;

#[derive(Parser)]
#[command(author, version, about, long_about = None)] // Read from `Cargo.toml`
struct Cli {
    #[arg(long)]
    number: usize,
}

fn main() {
    let cli = Cli::parse();
    let num = cli.number;
    let dir = DIR;

    if num == 0 {
        println!("Number of files to show must be greater than 0");
        return;
    }

    get_dir_size(dir, num).unwrap();
}

実際に実行するとこんな感じになります。

image.png

--number argument を忘れると怒られます。

image.png

例えば、上位 100 件を表示するには、--number 100とします。

image.png

デフォルトで --help が使えるようになっています。

image.png

Cargo.toml に書いた情報が、--help で表示されます。

[package]
name = "dirsearch"
version = "0.1.0"
edition = "2021"
license = "MIT OR Apache-2.0"
description = "🌸 Rust CLI Template using clap v4 🌸"
readme = "README.md"
homepage = "https://github.com/ekusiadadus/dirsearch"
repository = "https://github.com/ekusiadadus/dirsearch"
keywords = ["cli", "Japan", "Rust"]
categories = ["command-line-utilities"]

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
clap = { version = "4.0.29", features = ["derive"] }
file-size = "1.0.3"
walkdir = "2.3.2"

dirsearch を cretes.io に登録して配布する

https://crates.io/

image.png

コマンドラインツールができたので、実際に cargo install dirsearch できるようにhttps://crates.io/に登録します。

cargo release --dry-run で、リリースの準備をします。

image.png

成功したら、実際にローカルでインストールして動かしてみましょう。

cargo install --path . で、ローカルにインストールできます。 dirsearch で、コマンドラインツールが使えるようになります。

image.png

各コマンドが実際に動く確認します。

image.png

ちゃんと動いていそう。

image.png

実際に動いているので、配布します。 cargo publish で、https://crates.io/に登録できます。

image.png

登録できたことが確認できました。

image.png

これで、どこからでもcargo install dirsearchできますね!

image.png

# まとめ

Rust でコマンドラインツールを爆速で作る環境を構築しよう 2022 年版ということで、いかがだったでしょうか?

https://crates.io/crates/dirsearch

テンプレを使ってを配布するまでに 1 時間かからず行えました!

上が今回実際に配布したコマンドラインツールのリポジトリです。 めちゃくちゃかんたんですよね!

他にも、Rust で作ったコマンドラインツールをテンプレから作っています。

ここら辺は、Twitter API を使っているので完全に自分用ですが、Rust でコマンドラインツールを作るのはとても楽しいですね! clap v4 からかなり仕様が変わったので、結構 clap になれるのが大変かもしれませんが、テンプレを使って爆速で作れるので、ぜひ試してみてください!