Swift による CLI 構築 - アップル公式チュートリアル


:::note info

この記事では、[アップル公式ドキュメントのブートストラップ](https://www.swift.org/getting-started/cli-swiftpm/)を日本語で整理した
コマンドラインツールを構築すれば、例えばこんなものが作れる ↓

- 開発者向けツール： ソースコードのフォーマットや静的解析を行うツール
- 自動化スクリプト： 複数のコマンドをまとめて実行するスクリプト
- デプロイツール： サーバーへのアプリケーションのデプロイを自動化するツール

:::

## Swift でコマンドラインツールを構築する

以下のコマンドを実行し、SwiftPM による新規プロジェクトを作成する。
```terminal
$ mkdir MyCLI
$ cd MyCLI
$ swift package init --name MyCLI --type executable
```

すると、下記の構成で MyCLI ディレクトリができる。
```terminal
.
├── Package.swift
└── Sources
    └── main.swift
```

`Package.swift` はマニフェストファイルで、依存関係のようなプロジェクトのメタデータが記載される。`Sources/main.swift` は SwiftCLI のエントリーポイントとして実行コードが記載される。初期設定で "Hello, world" を出力する状態になっているので、以下のコマンドを実行して確認することができる。

```terminal
$ swift run MyCLI
Building for debugging...
[3/3] Linking MyCLI
Build complete! (0.68s)
Hello, world!
```

### 依存関係を追加する
`Package.swift` を拡張して、依存関係を追加する。ここでは、ASCII アートを作成してみることとする。

```swift: Package.swift
import PackageDescription

let package = Package(
    name: "MyCLI",
    dependencies: [
        .package(url: "https://github.com/apple/example-package-figlet", branch: "main"),
    ],
    targets: [
        // Targets are the basic building blocks of a package, defining a module or a test suite.
        // Targets can depend on other targets in this package and products from dependencies.
        .executableTarget(
             name: "MyCLI",
             dependencies: [
                 .product(name: "Figlet", package: "example-package-figlet"),
             ],
             path: "Sources"
        ),
    ]
)
```

以下のコマンドを実行すると、SwiftPM が立ち上がり、先ほどの依存関係を追加、並びにコードがビルドされる。
```terminal
$ swift build
```
すると、下記の構成でディレクトリが更新される。`Package.resolved` は、現在使用している依存関係のバージョンが載ったスナップショットである。
```terminal
.
├── Package.swift
├── Package.resolved
└── Sources
    └── main.swift
```

### ASCII アートを表示する

https://github.com/apple/example-package-figlet 

エントリーポイントの実行コードを編集し、先ほど追加した Figlet (↑)を使用する。ここで、`main.swift` あるいは `@main` の修飾子の記載されたファイルがエントリーポイントになることができる。試しに `main.swift` を消して、新たに `myCLI.swift` ファイルを作成し、以下のように編集する。

```swift: myCLI.swift
import Figlet

@main
struct FigletTool {
  static func main() {
    Figlet.say("Hello, Swift!")
  }
}
```

```terminal
$ swift run MyCLI
...
  _   _          _   _                 ____               _    __   _     _ 
 | | | |   ___  | | | |   ___         / ___|  __      __ (_)  / _| | |_  | |
 | |_| |  / _ \ | | | |  / _ \        \___ \  \ \ /\ / / | | | |_  | __| | |
 |  _  | |  __/ | | | | | (_) |  _     ___) |  \ V  V /  | | |  _| | |_  |_|
 |_| |_|  \___| |_| |_|  \___/  ( )   |____/    \_/\_/   |_| |_|    \__| (_)
                                |/                                          
```

### コマンドライン引数を取れるようにする

https://github.com/apple/swift-argument-parser

コマンドラインツールで引数をパースできるようにするために、新たな依存関係として Swift Argument Parser（↑）を追加する。Swift Argument Parser は、Swiftでのコマンドライン引数解析を型安全かつ簡単にする公式ライブラリで、自動ヘルプ生成や複数コマンドのサポートも提供されている。

```swift:Package.swift
import PackageDescription

let package = Package(
    name: "MyCLI",
    dependencies: [
        .package(url: "https://github.com/apple/example-package-figlet", branch: "main"),
        .package(url: "https://github.com/apple/swift-argument-parser", from: "1.0.0"),
    ],
    targets: [
        // Targets are the basic building blocks of a package, defining a module or a test suite.
        // Targets can depend on other targets in this package and products from dependencies.
        .executableTarget(
            name: "MyCLI",
            dependencies: [
                .product(name: "Figlet", package: "example-package-figlet"),
                .product(name: "ArgumentParser", package: "swift-argument-parser"),
            ],
            path: "Sources"
        ),
    ]
)
```

ParsableCommand に準拠させて、`@Option` などの修飾子をプロパティに付加することで引数を処理できる。例えば、`@Option(help:)` を使用すると、コマンドラインツールのオプションに対して説明文を付加できる。

```swift: myCLI.swift
import Figlet
import ArgumentParser

@main
struct FigletTool: ParsableCommand {
  @Option(help: "Specify the input")
  public var input: String

  public func run() throws {
    Figlet.say(self.input)
  }
}
```

```terminal
$ swift run MyCLI --input 'Hello, world!'

  _   _          _   _                                           _       _   _ 
 | | | |   ___  | | | |   ___         __      __   ___    _ __  | |   __| | | |
 | |_| |  / _ \ | | | |  / _ \        \ \ /\ / /  / _ \  | '__| | |  / _` | | |
 |  _  | |  __/ | | | | | (_) |  _     \ V  V /  | (_) | | |    | | | (_| | |_|
 |_| |_|  \___| |_| |_|  \___/  ( )     \_/\_/    \___/  |_|    |_|  \__,_| (_)
                                |/                                             
```

```terminal
$ MyCLI --help

USAGE: MyCLI [--verbose] [--filePath <filePath>]

OPTIONS:
  --input                 Specify the input
  -h, --help              Show help information.
```


---

https://www.swift.org/getting-started/cli-swiftpm/