📌

TODO検索するCLIツールを作ってHomebrewで配布した備忘録

に公開

はじめに

この記事は自分がTodoコメント行を抽出するCLIツールを作成した話を、配布時の備忘録的に書くものです。
以下の内容が含まれます。

  • Rustのちょっとした解説
  • Homebrewで配布する方法

制作したもの

https://github.com/I-Dieod/TodoScanner

対象読者

  • Homebrewで自作のツールを配布してみたい人
  • Rustでファイル読み取り、コメント抽出の方法が知りたい人

開発背景

Zedエディターの拡張機能としてVSCodeにあるTodoTreeのようなものを作りたいと思い、"TODO:"コメント行を抽出するロジックをRustでコーディングしていました。
しかしやりたいことが予想よりも高難易度だったことや似たような拡張機能がすでに存在したこと、zed_extension_apiがアプリケーションのUIに干渉できないなどの理由によりプロジェクトを無期限凍結にし、副産物としてできたロジックをどうせだしHomebrewで配布してみようと思いついたのが発端です。
それすらも先行プロジェクトが存在しますが、Rustで高速に動くかもしれないとか、4年前で更新が停止しているとか、まあとにかくやってみました。

ツールの内容

やってることはシンプルです。
argsでコマンドに対する引数を受け取ると同時に、実行時のカレントディレクトリをenvライブラリで取得、引数が空であればカレントディレクトリからスキャンすることを宣言します。

let args: Vec<String> = env::args().collect();
let cdu: String = env::current_dir().unwrap().to_string_lossy().to_string();
let root_path = if args.len() > 1 { &args[1] } else { &cdu };

println!("🔍 Scanning for COMMENT items in: {}", root_path);

ディレクトリのスキャンはPathオブジェクトにあるis_dir()メソッドでディレクトリか否か判定し、再帰的に引数以下のファイルを探索します。
path.extension()は拡張子の判定です。

if path.is_dir() {
            scan_directory(&path, todos)?;
        } else if let Some(ext) = path.extension().and_then(|e| e.to_str()) {
            // Check if it's a supported file type
            match ext {
                "rs" | "js" | "ts" | "jsx" | "tsx" | "py" | "java" | "go" | "c" | "cpp" | "h"
                | "hpp" | "php" | "rb" | "swift" | "kt" | "scala" | "html" | "css" | "vue"
                | "md" | "txt" | "yaml" | "yml" | "json" | "toml" | "xml" | "sh" | "bash"
                | "zsh" => {
                    scan_file(&path, todos)?;
                }
                _ => {}
            }
        }

合致する拡張子を持つファイルを発見したら、scan_file関数でファイルの中身を読み取り、//や#などで始まるコメント行の内、TODO:やFIX:, NOTE:で始まる行の内容を抽出し、構造体に加えます。
ディレクトリのスキャンが終われば、構造体からコメントの種類ごとにアイテムを並べるためのBTreeMapオブジェクトを宣言し、アイテムを一つずつ取り出してプッシュします。
最後に、特定の順序でコメントを表示するために用意した順序配列からfor文で要素を取り出し、合致するコメントの種類ごとにそれぞれのアイテムの内容をprintln!します。

for comment_type in &type_order {
            if let Some(files) = by_type.get(*comment_type) {
                println!("🏷️  {} Items:", comment_type);
                println!("   {}", "=".repeat(comment_type.len() + 7));

                for (file, file_todos) in files {
                    println!("    📁 {}", file);
                    for todo in file_todos {
                        println!("        📌 Line {}: {}", todo.line + 1, todo.text);
                    }
                    println!();
                }
            }
        }

Homebrewでの配布

作成したツールをHomebrewで配布するために、新しくhomebrew-tapというリポジトリを作成し、そこにRubyで記述されるFormulaファイルを配置します。
https://github.com/I-Dieod/homebrew-tap
この時、Cargo.tomlで定義したパッケージ名と、作成するFormulaファイル名と記述するクラス名は同じになるように気をつけてください。
一致していないと後述するbrew tapのタイミングでエラーが出ます。
作成するFormulaファイルはClaude Sonnet 4.5に任せました。

# !ここのクラス名!
class Tds < Formula
  desc "Fast TODO comment scanner for your codebase"
  homepage "https://github.com/I-Dieod/TodoScanner"
  url "https://github.com/I-Dieod/TodoScanner/archive/refs/tags/v0.2.0.tar.gz"
  sha256 "f2afe11e469036e8af3344e546d85df70fbe6340241c156309b6b904f520befa"
  license "MIT"

  depends_on "rust" => :build

  def install
    system "cargo", "install", *std_cargo_args
  end

  test do
    # Create a test file with a TODO comment
    (testpath/"test.rs").write <<~EOS
      // TODO: Test comment
      fn main() {}
    EOS

    output = shell_output("#{bin}/tds #{testpath}")
    assert_match "Found 1 TODO items", output
  end
end

具体的な記述内容について、urlにはリポジトリ上でリリースした際に作成されるソースコードのtarballのリンク、sha256にはtarballリンクから得られるSHA256ハッシュを記述してください。
ハッシュについてはNushell, Bashであれば以下のコマンドで取得できると思います。

curl -L [tarball_url] | shasum -a 256

作成したFormulaファイルをリモートリポジトリにPushしたら、お手元のCLIで以下のコマンドを実行してください。

brew tap [your_user_name]/tap
brew install [your_user_name]/[package_name_you_want]
# または
brew install [your_user_name]/tap/[package_name_you_want]

これで自作したツールがbrew経由でお手元のCLI上で使えるようになると思います。

今回配布したツールが動作する様子

おわりに

ほんとはZedの拡張機能を作りたかったんですが、すでに存在したことが悔しいです。アイデア負け。
これを機に色々、ツール制作とかOSSコントリビューションとかやっていけたらなと思いながら、まぁとにかくやってみました。
この記事がHomebrewで何か配布してみたい人の一助になってくれると嬉しいです。
ツールに対する文句とかPRとか、何か間違った箇所等あればコメントで教えてくださると助かります。

GitHubで編集を提案

Discussion