[Swift] CommandPlugin で クラス図を作る(CLI コマンドを作る)

SwiftPackageManagerEyeCatch

     
⌛️ 3 min.
Swift Package で Command Plugin を作っていきます。
最終的には、Swift のコードをパースして、mermaid の classDiagram を出力するのがゴールです。

環境&対象

以下の環境で動作確認を行なっています。

  • macOS14.3 Beta
  • Xcode 15.2
  • iOS 17.2
  • Swift 5.9

作りたい Plugin

指定した Package/Project のクラス構造を mermaid 形式で出力するプラグインを作ってみます。

全体の予定

全体のステップは、以下の予定です。

CommandPlugin の構造

前回までで、.swift ファイルをパースして、mermaid フォーマットの文字列を作る 関数の実装ができました。

次に、これらの関数を利用する コマンドを 作ります。

CommandPlugin の機能として使えるようにするためには、いろいろなやり方があると思いますが、今回は、CommandPlugin から CLIコマンドを キックするようにします。

今回は、その CLIコマンド を作成します。

product/target に Executable を設定する

前回までに、Package が提供する library と plugin を定義してきましたが、今回 executable も追加します。

products に 追加

まずは、Package.swift の products に追加します。


参考
executableApple Developer Documentation

名称は、コマンドライン名称と合わせて、”swiftymermaid” としました。

// swift-tools-version: 5.9
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
    name: "CommandPluginExample",
    platforms: [
        .macOS(.v14)
    ],
    products: [
        .library(name: "CommandPluginExampleLib", targets: ["CommandPluginExampleLib"]),
        .executable(name: "swiftymermaid", targets: ["swiftymermaid"]),      // NEW!!
        .plugin(
            name: "CommandPluginExample",
            targets: ["CommandPluginExample"]),
    ],
  ... snip ...

targets に追加

targets にも追加していきます。


参考
executableTargetApple Developer Documentation

// swift-tools-version: 5.9
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
    name: "CommandPluginExample",
    ... snip ...
    dependencies: [
        .package(url: "https://github.com/sdidla/Hatch", from: "509.0.2"),
        .package(url: "https://github.com/apple/swift-argument-parser", from: "1.2.3")
    ],
    targets: [
        .target(name: "CommandPluginExampleLib",
               dependencies: [ .product(name: "Hatch", package: "Hatch")]
                ),
        .executableTarget(name: "swiftymermaid",
                          dependencies: ["CommandPluginExampleLib",
                                         .product(name: "ArgumentParser", package: "swift-argument-parser")]
                         ),    ... snip ...

これまで作成してきた関数は、CommandPluginExampleLib に含まれていますので、そのライブラリに依存する形にしています。
同時に、コマンドラインの引数処理に便利な “swift-argument-parser” も dependencies に追加しています。

Package にフォルダを追加

新しい product である executable “swiftymermaid” を追加しましたので、Source フォルダ配下にフォルダを追加します。

addFolderForExecutable

フォルダが空のままだと エラーになるので、とりあえず File.swift を追加しています。

swift-argument-parser を使って、CLI コマンドを作る

CLIコマンドを作成します。

コマンド設計概要

以下のようなコマンドにします。

・(最初の)引数で、対象とするフォルダの URL を受け取ります。
・2番目に、出力先のファイルの URL を受け取ります。(省略されたら、標準出力に出力します)

実装

引数を処理するのに便利な swift-argument-parser を使って、作っていきます。

SwiftUI アプリの起動時を対象にしていますが、以下の記事で、swift-argument-parser を説明しています。
SwiftUI2021 [SwiftUI] swift-argument-parser を使って、SwiftUI 起動時動作を制御する

以下がコードです。

//
//  swiftymermaid.swift
//
//  Created by : Tomoaki Yagishita on 2024/01/26
//  © 2024  SmallDeskSoftware
//

import Foundation
import ArgumentParser
import CommandPluginExampleLib

@main
struct SwiftyMermaid: ParsableCommand {
    @Argument(help: "input folder")
    var folderURLString: String

    @Option(help: "output file(default: standard output)")
    var outputFile: String? = nil
    
    mutating func run() throws {
        let specifiedPath = (folderURLString as NSString).expandingTildeInPath
        guard FileManager.default.fileExists(atPath: specifiedPath) else { return }
        guard let folderURL = URL(string: specifiedPath) else { return }

        let extractedSymbols = try CommandPluginExampleLib.parseProject(folderURL)
        let extractedMermaid = CommandPluginExampleLib.mermaidString(extractedSymbols)

        if let outputFile = outputFile,
           let outputURL = URL(string: outputFile) {
            try extractedMermaid.data(using: .utf8)?.write(to: outputURL)
        } else {
            try FileHandle.standardOutput.write(contentsOf: extractedMermaid.data(using: .utf8)!)
        }
    }
}

コードの流れとしては、引数として与えられた URL に対して、swift-syntax/Hatch を使用してシンボルを抽出し、そのシンボル情報を mermaid 形式の文字列に変換し、指定された出力先に 出力しています。

上記をそのまま実装しています。

MEMO

当初、CLIコマンド に対しても テストを書こうとしていました。

書いてみるとわかるのですが、実質 parseProject と mermaidString を再度テストすることとなります。

ということで、CLIコマンド に対してのテストは書いていません。

使ってみる

せっかく作ったので、使ってみました。

プロジェクトのディレクトリに移動してから、Swift Package の実行コマンドを使います。

再帰的に全てのフォルダ中の swift ファイルを対象とするので、テストに使用しているファイル含めてシンボルが取得されて表示されています。

% swift run swiftymermaid .
Building for debugging...
[104/104] Linking swiftymermaid
Build complete! (37.52s)
classDiagram
%% Actor.swift
class Actor1 {
 >
}
%% Class.swift
class Class1 {
 >
}
%% CommandPluginExample.swift
class CommandPluginExample {
 >
}
class CommandPluginExample {
 >
}
%% CommandPluginExampleLib.swift
class CommandPluginExampleLib {
 >
}
class Hatch.Symbol {
 >
}
%% CommandPluginExampleLibParserTests.swift
class CommandPluginExampleLibParserTests {
 >
}
%% Enum.swift
class Enum1 {
 >
}
%% Package.swift
%% Protocol.swift
class Protocol1 {
 >
}
%% Struct.swift
class Struct1 {
 >
}
%% UnderstandingTests.swift
class UnderstandingTests {
 >
}
%% swiftymermaid.swift
class SwiftyMermaid {
 >
}

上記の文字列を実際に表示しようとすると問題があることがわかります。

シンボル名の1つとして、”Hatch.Symbol” 等名称がありますが、これが、mermaid 的に許容しません。エラーとなってしまいます。
以下は、Hatch.Symbol を Hatch_Symbol に置き換えた後 mermaid で表示したものです。

result

改善点

使ってみてわかったところは、以下です。

・extension が対応できていない
・Symbol名.Symbol名 というパターンを想定していなかった

とりあえず、課題として置いておきます。

まとめ

Swift Package/ swift-argument-parser を使って、CLIコマンド を作る

Swift Package/ swift-argument-parser を使って、CLIコマンド を作る
  • Swift Pacakge で CLIコマンド を作る時は、products に executable を定義する
  • Swift Pacakge で CLIコマンド を作る時は、targets に executableTarget を定義する
  • CLIコマンド の引数処理は、swift-argument-parser を使うと簡単に実装できる

説明は以上です。
不明な点やおかしな点ありましたら、こちらまで。

SwiftUI おすすめ本

SwiftUI を理解するには、以下の本がおすすめです。

SwiftUI ViewMatery

SwiftUI で開発していくときに、ViewやLayoutのための適切なmodifierを探すのが大変です。
英語での説明になってしまいますが、以下の”SwiftUI Views Mastery Bundle”という本がビジュアル的に確認して探せるので、便利です。

英語ではありますが、1ページに コードと画面が並んでいるので、非常にわかりやすいです。

View に適用できる modifier もわかりやすく説明されているので、ビューの理解だけではなく、どのような装飾ができるかも簡単にわかります。

超便利です

SwiftUIViewsMastery

販売元のページは、こちらです。

SwiftUI 徹底入門

# SwiftUI は、毎年大きく改善されていますので、少し古くなってしまいましたが、いまでも 定番本です。

Swift学習におすすめの本

詳解Swift

Swift の学習には、詳解 Swift という書籍が、おすすめです。

著者は、Swift の初期から書籍を出していますし、Swift の前に主力言語だった Objective-C という言語についても同様の書籍を出しています。

最新版を購入するのがおすすめです。

現時点では、上記の Swift 5 に対応した第5版が最新版です。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です