yikegaya’s blog

仕事関連(Webエンジニア)と資産運用について書いてます

RustのclapクレートでcatみたいなCLI書いてみた

Rust試したくてclapクレート使ってcatみたいなCLIツール書いてみました。Rustではライブラリはクレートと呼ぶらしい。

Rustは「実践Rustプログラミング入門」という本を読んで勉強したんですがclapはそこで紹介されてて知りました。

www.shuwasystem.co.jp

clapについて

「実践Rustプログラミング入門」は2020年8月に出版されてるみたいなんですがその後clapの仕様が大分変わっており書籍のままだと動かんかった。

例えば以前は「App」という構造体でこのように作成するツーつを初期化して作者やバージョン情報記録していったのが今は「Command」に変わってたり。

    let app: App = App::new("rcat")
        .author("Yuki Ikegaya")
        .version("v1.0.0")
        .arg(filepath_arg)
        .arg(number_arg);

    let command: Command = Command::new("rcat")
        .author("Yuki Ikegaya")
        .version("v1.0.0")
        .arg(filepath_arg)
        .arg(number_arg);

ググった感じclapを紹介しているいろんなブログのコードも使えなくなってるパターンが多そう。

Rustはまだまだこれからの言語でOSSも発展途上なのか?アップデート対応は大変そうだけどOSSコントリビュートしたい人にはいいタイミングなのかも。

clapのdocs

公式ドキュメント読めば使い方書いてある。 docs.rs

ただソースコード読みにいっちゃった方が仕様把握するの手っ取り早いかも。 github.com

書いたコード

github.com

use clap::{Command, Arg};
use std::fs::File;
use std::io::{self, BufRead, BufReader};
use std::path::Path;

fn main() {
    let filepath_arg: Arg = Arg::new("filepath")
        .required(true);

    let number_arg: Arg = Arg::new("number")
        .short('n')
        .long("number")
        .help("Print line numbers")
        .num_args(0);

    let command: Command = Command::new("rcat")
        .author("Yuki Ikegaya")
        .version("v1.0.0")
        .arg(filepath_arg)
        .arg(number_arg);
    
    match command.try_get_matches() {
        Ok(m) => {
            if let Some(filepath) = m.get_one::<String>("filepath") {
                let print_numbers = m.contains_id("number");
    
                match read_file_contents(filepath, print_numbers) {
                    Ok(contents) => println!("{}", contents),
                    Err(e) => eprintln!("Error reading file: {}", e),
                }
            } else {
                println!("Filepath not provided");
            }
        },
        Err(e) => {
            println!("{}", e);
        }
    }
}

fn read_file_contents(filepath: &str, print_numbers: bool) -> io::Result<String> {
    let path = Path::new(filepath);
    let file = File::open(path)?;
    let reader = BufReader::new(file);
    let mut contents = String::new();

    for (i, line) in reader.lines().enumerate() {
        let line = line?;
        if print_numbers {
            contents.push_str(&format!("{:>6}: {}\n", i + 1, line));
        } else {
            contents.push_str(&format!("{}\n", line));
        }
    }

    Ok(contents)
}