`flutter create`で生成されるいらないコメントを消すツールを作った

こんにちは！

今回はFlutterプロジェクト生成時によく使う`flutter create`コマンドについて、勝手に生成されるTips系のコメントを消した状態で実行する拡張機能のようなものを作成したので、ご紹介します。

▼ GitHubでプログラムと実行ファイルは公開しているため、忙しい方はそちらをご覧ください！

https://github.com/wassy310/flutter_cleaner

:::note info
記事中に所々「邪魔」「不要」という言葉を使用していますが、
決してデフォルトの`flutter create`の仕様を貶すために作ったわけではありません。
:::

# 0.0 まずFlutterとは

**Flutter** は クロスプラットフォームのオープンソースなフレームワークで、基本的に単一コードで複数プラットフォーム対応可能なアプリケーションを作成できます。

https://flutter.dev/

言語として **Dart** というものを用い、JavaScriptやTypeScriptに似た構造で書くことができます。

従来であれば、Androidアプリの開発にはJava・Kotlin・C#、iOSであればSwift・Objective-Cあたりを使うのが主流ですが、 **Flutterを使えば1つのコードでAndroid/iOS対応の画面、アプリを実装できます** 。

また、ボタンやアイコンといった基本的なデザインはもちろん、位置情報取得やBluetooth通信といったネイティブAPIに関するパッケージも用意されており、それぞれに対応するドキュメントもしっかり書かれているため、作りたいものをうまいこと実現するのに向いていると思います。

https://pub.dev/

# 0.1 `flutter create`コマンドとは

Flutterプロジェクトの **雛型を自動生成** してくれるコマンドです。
`flutter create project_name`で、任意の名前のプロジェクトを生成してくれます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1472510/91031142-161e-5168-056e-f496265870ba.png)

ためしに`flutter create hoge`としてみました。
`lib`フォルダの中にある`main.dart`というファイルを編集してビルドするだけで、自動的にクロスコンパイルしてくれます。
コマンドひとつでこれだけしっかりした雛型を作ってくれるあたり、かなり便利です。

https://docs.flutter.dev/reference/flutter-cli

# 1.1 `flutter create`の不便な点

ここからが本題です。

先ほどの便利な`flutter create`コマンドですが、これで生成された`main.dart`や`pubspec.yaml`ファイルを見てみます。長いので一部のみ抜粋します。

```main.dart
class MyApp extends StatelessWidget {
  const MyApp({super.key});

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        // This is the theme of your application.
        //
        // TRY THIS: Try running your application with "flutter run". You'll see
        // the application has a blue toolbar. Then, without quitting the app,
        // try changing the seedColor in the colorScheme below to Colors.green
        // and then invoke "hot reload" (save your changes or press the "hot
        // reload" button in a Flutter-supported IDE, or press "r" if you used
        // the command line to start the app).
        //
        // Notice that the counter didn't reset back to zero; the application
        // state is not lost during the reload. To reset the state, use hot
        // restart instead.
        //
        // This works for code too, not just values: Most code changes can be
        // tested with just a hot reload.
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}
```
```pubspec.yaml
name: hoge
description: A new Flutter project.
# The following line prevents the package from being accidentally published to
# pub.dev using `flutter pub publish`. This is preferred for private packages.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev

# The following defines the version and build number for your application.
# A version number is three numbers separated by dots, like 1.2.43
# followed by an optional build number separated by a +.
# Both the version and the builder number may be overridden in flutter
# build by specifying --build-name and --build-number, respectively.
# In Android, build-name is used as versionName while build-number used as versionCode.
# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix.
version: 1.0.0+1

environment:
  sdk: '>=3.0.5 <4.0.0'

# Dependencies specify other packages that your package needs in order to work.
# To automatically upgrade your package dependencies to the latest versions
# consider running `flutter pub upgrade --major-versions`. Alternatively,
# dependencies can be manually updated by changing the version numbers below to
# the latest version available on pub.dev. To see which dependencies have newer
# versions available, run `flutter pub outdated`.
dependencies:
  flutter:
    sdk: flutter
```

これ、コード上にコメントアウトを活用してTipsを書いてくれているようですが、さすがにこの量は少し邪魔だと感じてしまいます。

ということで、`flutter create`コマンドの欠点は **「不要なコメントが自動生成される」** で決定です！
他はめちゃくちゃ素晴らしいものだと思います。

## 1.2 環境
使用した環境は次の通りです。

### デバイス
- **OS**： Windows 10 Pro

### Flutter環境
```
Flutter 3.10.5 • channel stable • https://github.com/flutter/flutter.git
Framework • revision 796c8ef792 (4 months ago) • 2023-06-13 15:51:02 -0700
Engine • revision 45f6e00911
Tools • Dart 3.0.5 • DevTools 2.23.1
```

## 1.3 <実装編>

「不要なコメントが自動生成されるのなら自動削除すれば良い」ということで、**Rust** で以下を実装していきます。

だいたいの流れはこのような感じです。
- プロセスの実行とファイルシステム操作に必要なライブラリをインポート
- コマンドライン引数からプロジェクト名を取得
- flutterコマンドを実行するためのコマンドを生成
- 指定されたファイルの内容を読み込み
- コメント行を削除
    - 各行を処理し、`//`や`#`で始まる行をフィルタリング
    - フィルタリングされた行を改行で結合し、元のファイルに書き込み

実装したコードがこちらです。

```rust
use std::process::Command;
use std::fs;

fn main() {
    let project_name = std::env::args().nth(1).expect("Please provide a project name.");

    let output = Command::new("flutter.bat")
        .arg("create")
        .arg(&project_name)
        .output()
        .expect("Failed to create Flutter project");

    if !output.status.success() {
        panic!("Failed to create Flutter project");
    }

    remove_comments(&format!("{}/lib/main.dart", project_name));
    remove_comments(&format!("{}/pubspec.yaml", project_name));
}

fn remove_comments(file_path: &str) {
    let contents = fs::read_to_string(&file_path).expect("Failed to read file.");

    let filtered: Vec<&str> = contents.lines()
        .filter(|line| !line.trim_start().starts_with("//") && !line.trim_start().starts_with("#"))
        .collect();

    fs::write(&file_path, filtered.join("\n")).expect("Failed to write file.");
}
```

## 1.4 <解説編>

部分ごとに解説していきます。

### 1.4.1 プロセスの実行とファイルシステム操作をインポート
今回は、**プロセスの実行(Command)** と **ファイルシステム操作(fs)** をインポートしています。
```rust
use std::process::Command;
use std::fs;
```

### 1.4.2 コマンドライン引数からプロジェクト名を取得
プロジェクト名はプログラムを実行する際に指定する必要があります。
```rust
let project_name = std::env::args().nth(1).expect("Please provide a project name.");
```

### 1.4.3 flutterコマンドを実行するためのコマンドを生成
`Command::new("flutter.bat")`は、「"flutter.bat" というコマンドを実行するためのコマンド」を生成しています。ややこしい日本語ですみません。`.arg("create").arg(&project_name)`は、コマンドに引数を追加し、指定されたプロジェクト名でFlutterプロジェクトを作成します。作成したプロジェクトの実行結果 (成功 or 失敗) は output変数に格納されます。
```rust
let output = Command::new("flutter.bat")
    .arg("create")
    .arg(&project_name)
    .output()
    .expect("Failed to create Flutter project");
```
また、プロジェクトの作成が成功しなかった場合、エラーメッセージを表示して終了します。
```rust
if !output.status.success() {
    panic!("Failed to create Flutter project");
}
```

### 1.4.4 任意のファイルからコメント行を削除
`remove_comments`関数が呼び出され、`main.dart`および`pubspec.yaml`ファイルからコメント行を削除します。また、`format!`マクロは、ファイルのパスを構築するために使用されます。
```rust
remove_comments(&format!("{}/lib/main.dart", project_name));
remove_comments(&format("{}/pubspec.yaml", project_name));
```

### 1.4.5 指定されたファイルの内容を読み込み
指定されたファイルの内容を読み込みます。ファイルが存在しない場合や読み込みに失敗した場合、エラーメッセージを表示して終了します。
```rust
let contents = fs::read_to_string(&file_path).expect("Failed to read file.");
```

### 1.4.6 コメント行を削除
ファイルの内容からコメント行を削除するために、各行を処理し、`//`や`#`で始まる行をフィルタリングします。
```rust
let filtered: Vec<&str> = contents.lines()
    .filter(|line| !line.trim_start().starts_with("//") && !line.trim_start().starts_with("#"))
    .collect();
```

### 1.4.7 ファイルへの書き込み
フィルタリングされた行を改行で結合し、元のファイルに書き込みます。書き込みに失敗した場合、エラーメッセージを表示して終了します。
```rust
fs::write(&file_path, filtered.join("\n")).expect("Failed to write file.");
```

## 2.1 使い方
[GitHub](https://github.com/wassy310/flutter_cleaner)にも書いてありますが、以下の手順で使用することが可能です。
また、僕のデバイスがWindowsなのもあり、現時点(2023/10/26)では`.exe`形式の実行ファイルしか作れていません。

1. [flutter_cleaner.exe](https://github.com/wassy310/flutter_cleaner/releases/download/v0.1.0/flutter_cleaner.exe)をコピーまたはダウンロードします。
2. このプログラム (flutter_cleaner) は flutterコマンドを呼び出すため、Flutterを実行するPCにFlutterがインストールされており、PATHに適切に追加されていることを確認してください。
3. PC上で実行可能ファイルを実行します。Windowsでは、実行時に現在のディレクトリ内のファイルを明示的に指定する必要があります。
例: `.\flutter_cleaner.exe your_project_name`

:::note warn
PC、ファイアウォール、またはウイルス対策ソフトウェアのセキュリティ設定によっては、実行可能ファイルがブロックされる場合があります。
:::

## まとめ
今回は、不要なコメント行を自動的に削除することで、`flutter create`コマンドをより効率的に利用できるツールを開発・解説しました。このツールを使用することで、不要なコメントの手動削除をする必要がなくなり、イライラすることなく、よりスムーズな開発ができたら本望です。

※ 決してデフォルトの`flutter create`の仕様を貶すために作ったわけではありません。