このツールは、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によるシリアライズを統合しています。各モジュールは関心の分離を意識して設計されており、拡張性と保守性を重視しています。