【Golang】go:embedを用いて格言表示コマンド(subaru)を開発
前書き:go:embedを試したかった
Golangでは、go:embedがVersion1.16から組み込まれています。
go:embedの利点は、「バイナリインストール(設定ファイルや関連ファイル含む)がより簡単になる事」でしょうか。go:embedを用いる事によって、バイナリの中に設定ファイルやスクリプトなどを埋め込めます。そのため、バイナリをシステムに置くだけで、インストール作業が完了となります。
この手法は、他言語でも一般的です。例えば、シェルスクリプトにバイナリを埋め込んで、インストーラを作る手法があります。
馴染み深いがゆえに、「go:embedを使ったコマンドを作ってみたいな」と2021年から考えていました。2022年の2月に重い腰を上げて、go:embedの勉強用コマンド(subaru)を作ることにしました。
subaru:格言を出力するfortuneライクコマンド
subaruコマンドは、fortuneコマンドをインスパイアしています。
fortuneとは、Unix系OSのコマンドの一つ。フォーチュン・クッキーを模したプログラムであり、歴史上の偉人の名言や有名人の発言などを一部引用したメッセージを無作為に表示する。
subaruコマンドをそのまま実行すると、ランダムで格言(および格言に関係する人、会社、ツールの名前)が表示されます。
1 2 3 4 |
$ subaru [atari] Business is a good game. Lots of competition and a minimum of rules. You keep score with money. |
サブコマンドが用意されており、特定の格言を出力できます。例えば、以下の例ではUnix哲学を出力します。
1 2 3 4 5 6 7 8 9 10 11 |
$ subaru unix [unix] 1. Small is beautiful. 2. Make each program do one thing well. 3. Build a prototype as soon as possible. 4. Choose portability over efficiency. 5. Store data in flat text files. 6. Use software leverage to your advantage. 7. Use shell scripts to increase leverage and portability. 8. Avoid captive user interfaces. 9. Make every program a Filter.。 |
設計:格言追加時にコードを修正しない
subaruコマンドを開発する時、避けたかった設計が一つあります。それは、subaruコマンドのコード内部に格言(テキスト)をベタ書きする方法です。コードはなるべく触りたくありません。
そのため、subaruコマンドは以下の設計となっています。
- 格言は外部ファイル(拡張子”.subaru”)に定義する
- 全ての*.subaruファイルをgo:embedでバイナリに組み込む
- *.subaruファイルからサブコマンドを実行時に定義する
- *.subaruファイルのファイル名をサブコマンド名とする
これらの仕様をどのように実装したかを後述します。
*.subaruファイルの埋め込み
subaruコマンドのディレクトリ構成(大事な部分)は、以下の構成です。
1 2 3 4 5 6 7 8 9 10 |
subaruプロジェクトルートディレクトリ ├── cmd │ ├── root.go ★ 動的にサブコマンドを定義する処理が存在 │ └── version.go ├── fortune ★ 格言を保存するディレクトリ │ ├── atari.subaru │ ├── nietzsche.subaru │ ├── python.subaru │ └── unix.subaru └── main.go ★ここにgo:emdedの定義が存在 |
fortune/*以下に存在するファイルを全てバイナリに埋め込みます。そのため、プロジェクトルートディレクトリに存在するmain.goに、go:embedの処理を書いています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
package main import ( "embed" "github.com/nao1215/subaru/cmd" ) //go:embed fortune/*.subaru (注釈:埋め込み処理) var fortune embed.FS func main() { cmd.AddCommands(fortune) cmd.Execute() } |
埋め込みでは、初期化前の変数(上記の例ではfortune)の上に”//go:embed”を記載します。変数の型には、string、[]byte、embed.FSが使用できます。
“//go:embed “の後に、埋め込み対象のファイル名を書きます。ファイル名はスペース区切りで複数記載でき、上記の例のようにワイルドカード指定もできます。
注意点は、「”//”と”go:embed”の間にスペースは不要」および「埋め込めないファイルが存在する事」です。後者の具体例ですが、”go:embed”を記載したファイルより上の階層のファイル」は埋め込めません。つまり、”//go:embed ../sample.txt”のような記載はビルドエラーとなります。
サブコマンドを実行時に定義
この方法は、エキスパートたちのGo言語の1.2章(依存関係のある処理を並行して実行できるタスクランナー)を真似して実装しました。
subaruコマンドはspf13/cobraを利用しています。サブコマンド定義は、cobraのAPI “ (*cobra.Command).AddCommand()”を呼び出せば終わります。
subaruコマンドの場合、上記のAddCommandに渡す引数は、サブコマンド名、サブコマンドの説明、サブコマンドのエントリーポイント関数の3つです。
- サブコマンド名は、*.subaruのファイル名と同一
- サブコマンドの説明は、”Print phrase related to $(サブコマンド名)”
- エントリーポイント関数は、*.subaruの内容を標準出力に表示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
func AddCommands(fs embed.FS) { // fortune/*.subaruの内容がバイナリに埋め込まれているので、 // fortuneディレクトリ以下のエントリを取得 entries, err := fs.ReadDir("fortune") if err != nil { exitError(err) } // エントリ(ファイル)単位でサブコマンドを追加 for _, entry := range entries { // エントリ名(= ファイル名)から拡張子を除外 cmdName := removerExt(entry.Name()) // 格言を定義したファイルの内容を読み込み b, err := fs.ReadFile(filepath.Join("fortune", entry.Name())) if err != nil { exitError(err) } // サブコマンドの追加 rootCmd.AddCommand(&cobra.Command{ Use: cmdName, Short: "Print phrase related to " + cmdName, Run: func(cmd *cobra.Command, args []string) { os.Exit(printPhrase(cmd.Name(), string(b))) }, }) } } } |
上記の処理があるおかげで*.subaruファイルが増えても、自動でサブコマンドが定義されます(再ビルドは必須)。そのため、ファイル追加だけでドンドン格言が増やせます。コントリビュートしたい方、募集中!
最後に:subaruの由来
subaru(昴)は、子供に付けようと思った名前です。
昴は意味も良いし、車のSUBARUは技術大好きな人が多いし、名字との合わせも良くて「完璧だ」と思っていました。が、諸事情でNGとなりました。
一度気に入った名前なので「コマンドの名前にでもするか」と考え、今回subaruを実装しました。subaruは、集団(何かが集まるイメージ)の意味があるので「格言”集”」となっています。
おまけ:2022年に作成したGolang製コマンド一覧
【Golang】2022年に開発した自作CLIコマンド/ライブラリに対する所感と宣伝【OSS】
ロシア人と国際結婚した地方エンジニア。
小学〜大学院、就職の全てが新潟。
大学の専攻は福祉工学だったのに、エンジニアとして就職。新卒入社した会社ではOS開発や半導体露光装置ソフトを開発。現在はサーバーサイドエンジニアとして修行中。HR/HM(メタル)とロシア妻が好き。サイトに関するお問い合わせやTwitterフォローは、お気軽にどうぞ。
1件の返信
[…] 【golang】go:embedを用いて格言表示コマンド(subaru)を開発 […]