1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

テキスト一括置換アプリ開発。「ツールは探す時代から作る時代に」を実感したAIとの問答

1
Posted at

SpeedReplace.png

複数ファイルのテキストを一括置換できるアプリとして、長年使用してきたspeeeeedを今風にしようかなという思いつきで始まりました。AI への最初の指示が3つ。使用したモデルは、GPT-5.6 Sol 中です。

最初に ChatGPT のチャットモードで AI に投げたのがこれ

https://freesoft.tvbok.com/freesoft/web/speeeeed.html
まずはこのページを理解して

返事がこれ

指定されたページを確認し、内容を理解しました。

speeeeed:複数ファイルの文字列を一括置換するフリーソフト
1. speeeeedとは?
speeeeedは、複数のテキストファイルに含まれる文字列をまとめて検索・置換できるWindows用フリーソフトです。
・
・

次に投げたのがこれ

このアプリはとても古いのですが、これと同様以上の機能を持つ現代に適したアプリをクロスプラットフォームで作成したい

返事がこれ

可能です。しかも、speeeeedのシンプルな操作性を維持しながら、Windows・macOS・Linuxで動作する、高速かつ安全な一括文字列置換アプリとして開発する価値は十分にあります。
・
・
次に決めたい3つの仕様
開発を具体化するにあたり、次の3点を確認したいです。

Q1. アプリの名前
- A. ReplaceFlow(仮称を採用)
- B. speeeeedに近い名前
- C. 別の名前を考える(候補を提案できます)
Q2. GUIの方向性
- A. speeeeedのようなシンプルなGUI(初心者向け)
- B. VS Codeのような高機能GUI(開発者向け)
- C. 両方を切り替えられるGUI(シンプルモード/詳細モード)
Q3. 開発の優先順位
- A. まず基本機能を完成させ、順次機能追加
- B. 最初から安全機能を含む実用的な完成度を目指す
- C. 最初からすべての機能を実装する
私の推奨は、Q2=C、Q3=Bです。

次がこれです

Q2=C、Q3=B
にする

この後、アプリ名を決めて、Codex に移動して、これを投げました

speeeeed代替アプリ開発 
このスレッドの全体を理解して

そして、これをなげてから寝ました。

進めて

翌朝、これらを投げて完成でした

GitHubにプライベートリポジトリとしてプッシュして
実装を開始して
v1.0の完成まで完了させて、テストもお願い

最終的に README の調整と多言語化、公開前の最終チェックを AI にさせて、ここから下も生成してくれました。睡眠中や食事などの時間を除けば正味1時間はかかっていないと思います。

まさにツールは探す時代から作る時代にを実感する体験でした

はじめに

複数ファイルの文字列を一括置換したい。

やりたいことはシンプルですが、実際の現場では次のような心配があります。

  • Shift_JISのファイルをUTF-8で上書きしてしまわないか
  • BOMやCRLF/LFが勝手に変わらないか
  • 正規表現のミスで想定外のファイルまで変更しないか
  • プレビュー後に別プロセスが更新したファイルを上書きしないか
  • 何かあったときに元に戻せるか

長年使われてきたspeeeeedのような手軽さは残したい。一方で、今新しく作るなら、置換に成功するだけでなく、データを壊さないことを最優先にしたい。

そこで作ったのが、クロスプラットフォームの一括テキスト置換アプリ SpeedReplace です。

最初に決めた4つの原則

実装の前に、プロダクトとしての判断基準を決めました。

  1. 単発の置換でも迷わず使える
  2. 大規模なフォルダでも安全に使える
  3. プレビューを省略したくならない速度にする
  4. アカウントもネットワークも必要としない

この原則があったおかげで、「速さと安全性のどちらを優先するか」と迷ったときは、安全性を選べました。

なぜTauri + Rust + Reactにしたか

構成は次の通りです。

React + TypeScript UI
        ↓ Tauri command
Rust command layer
        ↓
speedreplace-core
  ├─ scan / diff
  ├─ encoding preservation
  ├─ apply / backup
  └─ undo / conflict detection

Tauriを選んだ主な理由は、ネイティブのファイル操作をRust側に寄せながら、UIはReact/TypeScriptで素早く作れることでした。Electronより小さな構成にしやすく、Windows、macOS、Linuxを同じコードベースで狙えます。

また、置換エンジンをTauriから独立したRust crateにしました。これにより、GUIを起動せずに文字コード、差分、バックアップ、Undoをテストできます。

難所1:「読めた」ではなく「同じバイト列に戻せる」

文字コードの自動判定は100%正確ではありません。そのためSpeedReplaceでは、デコードできたかどうかだけでなく、置換前に往復変換を行います。

let text = encoding
    .decode_without_bom_handling_and_without_replacement(body)
    .ok_or_else(|| Error::Encoding("invalid byte sequence".into()))?
    .into_owned();

let round_trip = encode_body(&text, encoding)?;
if round_trip != body {
    return Err(Error::Encoding(
        "exact round-trip validation failed".into(),
    ));
}

元のバイト列に完全に戻らないファイルは、安全な置換対象とみなしません。置換後の文字列が元の文字コードで表現できない場合も書き込みを拒否します。

現在は次の文字コードを対象にしています。

  • UTF-8(BOMあり/なし)
  • UTF-16 LE/BE
  • Shift_JIS
  • EUC-JP
  • ISO-2022-JP
  • Windows-1252

BOMとLF/CRLF/CRも記録し、できるだけ元の形式を保ちます。

難所2:プレビュー後の上書きを防ぐ

プレビューが正しくても、「プレビューした後にファイルが変更された」なら、その結果を適用してはいけません。

SpeedReplaceはスキャン時に置換前後のBLAKE3ハッシュを持ちます。適用時にファイルを読み直し、次を確認します。

  1. 現在のハッシュがプレビュー時の入力ハッシュと一致する
  2. ルールを再実行した結果がプレビュー時の出力ハッシュと一致する
  3. バックアップが元のハッシュと一致する
  4. 書き込み後のファイルが出力ハッシュと一致する

どこか一つでも異なれば競合として止めます。

書き込みは同じディレクトリの一時ファイルを経由し、フラッシュ後に置き換えます。単にfs::writeするより実装量は増えますが、中途半端な状態を残しにくくなります。

難所3:Undoは「バックアップがある」だけでは足りない

Undoも無条件の上書きにすると危険です。置換後にユーザーがファイルを編集していたら、Undoはその新しい変更を消してしまいます。

そこでUndo時にも現在のハッシュと、操作マニフェストに記録した置換後ハッシュを比較します。外部変更があるファイルは競合としてスキップします。

公開前監査では、この周辺をさらに強化しました。

  • シンボリックリンクは検索時と書き込み直前の両方で拒否
  • Undoの操作IDは正規形UUIDに限定
  • マニフェスト内のバックアップパスを信用せず、アプリ管理下のオブジェクトディレクトリから解決
  • スキーマとマニフェストIDを検証

「正常系で元に戻る」だけでなく、悪意的または想定外の状態で、どこまで保守的に止まれるかをテストしました。

UIはシンプルに、但し安全情報は隠さない

画面は3ステップです。

  1. ファイルまたはフォルダを選ぶ
  2. 置換ルールを入力する
  3. 差分をプレビューして適用する

シンプルモードでは必要最小限の項目だけを表示し、詳細モードでは複数ルール、正規表現、include/exclude glob、サイズ上限などを表示します。

プレビュー後にルールや対象が変わると、そのプレビューはstaleとみなし、再スキャンするまで適用ボタンを無効化します。

また、バイナリや文字コード不明などの非対応ファイルはデフォルトで非表示にし、「非対応ファイルを表示」で確認できるようにしました。エラーの存在は残しつつ、通常の置換対象に集中できるための調整です。

後から言語を追加できる多言語化

UIの初期言語は英語で、日本語へ切り替えられます。言語ファイルを追加するだけで選択肢に出るよう、Viteのimport.meta.globを使いました。

const localeModules = import.meta.glob(
  "../locales/*.ts",
  { eager: true },
);

英語メッセージのキーを基準にして、日本語側にRecord<TranslationKey, string>を満たさせることで、翻訳キーの追加漏れをTypeScriptのビルドで検出します。

「対応ファイル形式」を拡張子で決めなかった

SpeedReplaceは.txt、.csv、.md、.json、.php、ソースコードや設定ファイルなどを扱えます。ただし、これは拡張子の許可リストではありません。

判定基準は、「対応文字コードのプレーンテキストとして安全に往復変換できるか」です。そのため、拡張子のないDockerfileやMakefile、独自拡張子も処理できます。

逆に.docx、.xlsx、.pdf、圧縮ファイル、画像などのバイナリ/コンテナ形式は対象外です。CSVやJSONも構文を解釈するのではなく、あくまでテキストとして置換します。

3 OSのCIと配布パッケージ

GitHub ActionsでmacOS、Ubuntu、WindowsのマトリクスCIを回しています。

  • cargo fmt --check
  • cargo clippy -D warnings
  • Rustテスト
  • TypeScript/Viteビルド
  • Tauriデスクトップコンパイル
  • npm/RustSec監査

配布ワークフローでは次を生成します。

  • Windows x64: MSI、NSIS .exe
  • Linux x64: .deb、AppImage

ここで意外にはまったのが、Tauriの--bundles引数でした。カンマ区切りの値をGitHub ActionsのマトリクスからPowerShellに渡すと、Windowsで想定した引数にならずビルドが失敗しました。

最終的に、マトリクス値を次のようなスペース区切りにすることで、WindowsとLinuxの両方で動く形になりました。

- name: Windows
  bundles: msi nsis
- name: Linux
  bundles: deb appimage

ローカルだけでは見つけにくい「シェルによる引数解釈の違い」で、実機CIを通す重要性を再確認しました。

v1.0.0の完成後に、もう一度「公開できるか」を見直した

v1.0.0が動いた時点で、すぐPublicにすることもできました。しかし、動作完了と公開準備完了は別だと考え、v1.0.1で公開前監査を行いました。

確認・改善したのは、例えば次の項目です。

  • 秘密情報、個人パス、履歴全体のスキャン
  • 依存関係とライセンスの確認
  • npmとRustSecの自動監査
  • Dependabotの設定
  • GitHub Actionsを不変のコミットSHAに固定
  • SECURITY.md、CONTRIBUTING.md、Issue/PRテンプレート
  • 専用アイコンと各OS用アセット
  • リリース資産のSHA-256チェックサム

この過程で、正常系の機能追加よりも、シンボリックリンクやUndoマニフェストの信頼境界といった点に修正が入りました。

安定版v1.0.1ではRustテスト16件、3 OSでのビルド、Windows/Linux配布物の生成を確認しています。

AIとの開発で、人間が決めたこと

この開発は、AIコーディングエージェントと対話しながら進めました。設計案、実装、テスト、CI、README、リリースノート、公開前監査まで、一連の作業を同じ文脈で繰り返しました。

ただし、「どんなアプリにするか」と「どこまでを安全とするか」は、自動的に決まるものではありませんでした。

人間側で特に重要だったのは、次のような判断です。

  • speeeeedの「簡単さ」は残す
  • 速度と安全性が衝突したら安全性を優先する
  • 文字コードを勝手に変換しない
  • プレビューとUndoを「付加機能」ではなく中心設計にする
  • WindowsとLinuxの配布物まで作る
  • Public化前にもう一度セキュリティと配布品質を見直す

AIは実装速度と確認範囲を大きく広げてくれましたが、製品の性格を決めたのは、こうした優先順位でした。

できたことと、まだやらないこと

現在できることは次の通りです。

  • 個別ファイルとフォルダの再帰スキャン
  • 通常文字列、正規表現、複数行置換
  • 複数ルールの順番適用
  • 統一形式の差分プレビュー
  • 文字コード、BOM、改行コードの保持
  • 検証付きバックアップと競合検知付きUndo
  • 英語/日本語UIと言語ファイル追加による拡張

一方、次の項目は今後の候補です。

  • ルールプリセット
  • CLI
  • 進捗表示とキャンセル
  • 文字コードの明示的な上書き
  • バックアップ保持期間の設定
  • 差分ハンク単位の選択
  • 各OSのコード署名とmacOS Notarization

特に署名は、バイナリを一般配布する上で重要な残作業です。現在の配布物は未署名のため、OSがセキュリティ警告を表示する場合があります。

作ってみて分かったこと

一括置換アプリの中心は、文字列のreplace処理ではありませんでした。

難しかったのは、

  • 今見せたプレビューと、実際に書く内容が同じであること
  • 元の文字コードやBOMを保持すること
  • 外部更新を無視しないこと
  • バックアップとUndo自体も安全であること
  • 3つのOSで同じ品質ゲートを通すこと

です。

使う人から見れば、「検索して置換する」アプリです。その単純な操作を安心して実行できるようにするために、見えないところにたくさんの検証を置く。それがSpeedReplaceの開発で一番大きなテーマになりました。

リポジトリとリリースは以下から確認できます。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?