Rustで実装するカラフルなファイルリスト表示ツール

このツールは、Unix系のlsコマンドをRustで再実装したもので、カラー付きテーブル表示とJSON出力機能を備えています。実行例では、ディレクトリ内容を視覚的に整理し、ファイル種別や最終更新日時を直感的に把握できます。

プロジェクト初期化

cargo new rust-ls && cd rust-ls

依存パッケージの追加

Cargo.tomlに以下のセクションを追加します:

[package]
name = "rust-ls"
version = "0.2.0"
edition = "2021"

[dependencies]
clap = { version = "4.5", features = ["derive"] }
owo-colors = "4.2"
strum = { version = "0.27", features = ["derive"] }
tabled = "0.20"
chrono = { version = "0.4", features = ["serde"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

基本的なCLI構造の実装

src/main.rsを以下のように書き換えます:

use std::fs;
use std::path::{Path, PathBuf};
use clap::Parser;
use owo_colors::OwoColorize;

#[derive(Parser, Debug)]
#[command(
    name = "rust-ls",
    version = "0.2.0",
    about = "Modern file listing utility powered by Rust"
)]
struct Args {
    #[arg(value_name = "PATH", default_value = ".")]
    target: PathBuf,

    #[arg(short = 'j', long = "json", help = "Output as JSON instead of table")]
    output_json: bool,
}

fn main() {
    let args = Args::parse();
    
    if !args.target.exists() {
        eprintln!("{}", format!("Error: '{}' not found", args.target.display()).red());
        std::process::exit(1);
    }

    if args.output_json {
        let entries = collect_entries(&args.target);
        println!("{}", serde_json::to_string_pretty(&entries).unwrap_or_else(|_| "[]".to_string()));
    } else {
        render_table(&args.target);
    }
}

データモデルとファイル走査ロジック

以下をmain.rsの末尾に追加します:

use serde::Serialize;
use strum::Display;
use tabled::{Table, Tabled};
use tabled::settings::{Style, Color, object::{Columns, Rows}};
use chrono::{DateTime, Utc};

#[derive(Debug, Display, Serialize)]
enum ItemType {
    Directory,
    RegularFile,
}

#[derive(Debug, Tabled, Serialize)]
struct FileInfo {
    #[tabled(rename = "Item")]
    item_name: String,
    #[tabled(rename = "Kind")]
    kind: ItemType,
    #[tabled(rename = "Size (B)")]
    size_bytes: u64,
    #[tabled(rename = "Modified")]
    last_modified: String,
}

fn collect_entries(dir: &Path) -> Vec<FileInfo> {
    let mut items = Vec::new();
    
    if let Ok(entries) = fs::read_dir(dir) {
        for entry in entries.filter_map(Result::ok) {
            if let Ok(metadata) = fs::metadata(&entry.path()) {
                let name = entry.file_name().to_string_lossy().into_owned();
                let kind = if metadata.is_dir() {
                    ItemType::Directory
                } else {
                    ItemType::RegularFile
                };
                
                let mtime = metadata.modified()
                    .ok()
                    .and_then(|t| t.into())
                    .map(|dt: DateTime<Utc>| dt.format("%b %d %Y %H:%M").to_string())
                    .unwrap_or_default();

                items.push(FileInfo {
                    item_name: name,
                    kind,
                    size_bytes: metadata.len(),
                    last_modified: mtime,
                });
            }
        }
    }
    
    items
}

fn render_table(dir: &Path) {
    let items = collect_entries(dir);
    let mut table = Table::new(items);
    
    table.with(Style::rounded());
    table.modify(Columns::first(), Color::FG_GREEN);
    table.modify(Columns::one(1), Color::FG_BLUE);
    table.modify(Columns::one(2), Color::FG_YELLOW);
    table.modify(Columns::one(3), Color::FG_CYAN);
    
    println!("{}", table);
}

ビルドと検証

cargo build --release

動作確認:

# ヘルプ表示
./target/release/rust-ls --help

# バージョン確認
./target/release/rust-ls --version

# 現在ディレクトリのリスト表示
./target/release/rust-ls

# JSON形式で出力
./target/release/rust-ls --json

# 特定ディレクトリを対象に
./target/release/rust-ls ./src

この実装では、clapによる引数解析、tabledによる整形された表形式出力、owo-colorsによるターミナル色付け、およびserde_jsonによるシリアライズを統合しています。各モジュールは関心の分離を意識して設計されており、拡張性と保守性を重視しています。

タグ: rust clap tabled owo-colors serde

6月9日 17:42 投稿