#japan #command-line-tool #cli #rust

app dirsearch

🌾 Rust CLI Template using clap v4 🌾

1 unstable release

0.1.0 Dec 16, 2022

#2545 in Command line utilities

MIT/Apache

13KB
55 lines

dirsearch

Usage

$ dirsearch --number [num]

Article

こんにづは、@ekusiadadusです。 CLI ăƒ„ăƒŒăƒ«äœœăŁăŠă„ăŸă™ă‹ïŒŸ CLI ăƒ„ăƒŒăƒ«ă‚’ Rust ă§äœœă‚‹ăšăă«ă€æŻŽć›žç’°ćąƒă‚’æ•Žăˆă‚‹ăźăŒéąć€’ă ăŁăŸăźă§ă€ăƒ†ăƒłăƒ—ăƒŹă‚’äœœă‚ŠăŸă—ăŸă€‚ ä»Šć›žăŻăăźăƒ†ăƒłăƒ—ăƒŹă‚’äœżăŁăŠă€ç°Ąæ˜“çš„ăȘ CLI ăƒ„ăƒŒăƒ«ă‚’ Rust ă§çˆ†é€Ÿă§äœœăŁăŠăżăŸă™ă€‚

ăƒ†ăƒłăƒ—ăƒŹăƒŒăƒˆăŻă“ăĄă‚‰ă§ă™ă€‚ https://github.com/ekusiadadus/rust-cli-template

ä»Šć›žäœœă‚‹ă‚łăƒžăƒłăƒ‰ăƒ©ă‚€ăƒłăƒ„ăƒŒăƒ«ăŻă“ăĄă‚‰ă§ă™ă€‚ https://github.com/ekusiadadus/dirsearch

ăƒ†ăƒłăƒ—ăƒŹă‚’äœżăŁăŠ CLI ăƒ„ăƒŒăƒ«ă‚’äœœă‚‹

ä»Šć›žăŻă€ă‚ˆăă‚ă‚‹ăƒ‡ă‚ŁăƒŹă‚ŻăƒˆăƒȘé…äž‹ă«ć­˜ćœšă™ă‚‹ăƒ•ă‚©ăƒ«ăƒ€ă€ăƒ•ă‚Ąă‚€ăƒ«ăźæ•°ăšăăźć€§ăă•ă‚’èĄšç€șする CLI ăƒ„ăƒŒăƒ«ă‚’çˆ†é€Ÿă§äœœăŁăŠăżăŸă™ă€‚

ăƒ†ăƒłăƒ—ăƒŹă‚’ă‚Żăƒ­ăƒŒăƒłă™ă‚‹

ăŸăšăŻă€ăƒ†ăƒłăƒ—ăƒŹă‚’ă‚Żăƒ­ăƒŒăƒłă—ăŸă™ă€‚

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

cargo run ă§ćźŸèĄŒă—ăŠăżă‚‹

ăƒ†ăƒłăƒ—ăƒŹă‚’äœżă†ă«ăŻă€ăƒ†ăƒłăƒ—ăƒŹăźăƒ‡ă‚ŁăƒŹă‚ŻăƒˆăƒȘに移拕しお、cargo rună‚’ćźŸèĄŒă—ăŸă™ă€‚

cd rust-cli-template
cargo run

ă†ăŸăă„ă‘ă°ă€ă“ă‚“ăȘæ„Ÿă˜ă§ă€rust-cli-templateべいう損才ぼ CLI ăƒ„ăƒŒăƒ«ăŒćźŸèĄŒă•ă‚ŒăŸă™ă€‚

æ—ąă«ă“ăźæź”éšŽă§ă€cargo run で CLI ăƒ„ăƒŒăƒ«ăŒćźŸèĄŒă§ăă‚‹ç’°ćąƒăŒæ•ŽăŁăŠă„ăŸă™ă€‚

image.png

(䜙談) mold + cargo watch ă‚’äœżă†

mold + cargo watch ăŻäœżă‚ăȘăăŠă‚‚ă„ă„ă§ă™ăŒă€ä»„äž‹ăźç‚čă§äŸżćˆ©ă§ă™ă€‚

  • ホットăƒȘăƒ­ăƒŒăƒ‰ă•ă‚Œă‚‹é–‹ç™ș環汃
  • ăƒ“ăƒ«ăƒ‰ăŒé€ŸăăȘる

ここらèŸșăŻă€ć‚è€ƒèš˜äș‹ă‚’èČŒăŁăŠăŠăăźă§ă‚‚ă—ă‚ˆă‹ăŁăŸă‚‰äœżăŁăŠăżăŠăă ă•ă„ă€‚

https://keens.github.io/blog/2021/12/20/moldwotsukautorustnobirudogahayakunaru/

https://qiita.com/kyamamoto9120/items/2081bc44c6c987b9ec29

ä»Šć›žăźć Žćˆă€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 = "rust-cli-template"
version = "0.1.0"
edition = "2021"
license = "MIT"
description = "🌾 Rust CLI Template using clap v4 🌾"
readme = "README.md"
homepage = "https://github.com/ekusiadadus/rust-cli-template"
repository = "https://github.com/ekusiadadus/rust-cli-template"
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"

Dependencies

~1.2–8.5MB
~70K SLoC