🖥

golangでインタラクティブなCLIツールをサクッと作る

2022/05/30に公開


チーム開発において、ちょっとした作業を自動化するためのツールを作ることが多々あります。エンジニアのみが使用する場合、ざっくり作成したCLIツールやshellscriptで問題ないですが、非エンジニアの方含めて使えるようなツールとなると、環境構築などが不要な実行ファイル形式であった方が良いですし、コマンドを打たなくても使える方が好ましいです。
しかし、GUIツールを作るとなると、工数やバグ発生率がぐんと上がってしまいます。そのため、クロスコンパイル可能なgo言語を使ってサクッと作れるようなインタラクティブなCLIツールを作成するライブラリがないかと探していました。その中で、promptuiというライブラリがシンプルで良さそうだったため紹介します。

本記事の内容

すること

  • goプロジェクト作成
  • promptuiで入力をstring型で取得する
  • promptuiで選択肢を矢印入力で選択する

扱わないこと

  • golangのコンパイル環境の作成

promptui

promptuiは、文字入力(Prompt)と矢印キーの選択(Select)による入力をインタラクティブに扱えるgolangライブラリです。できることがシンプルであり学習コストが小さいため、サクッとCLIツールを作ることができます。

https://github.com/manifoldco/promptui

プロジェクト作成からpromptui導入まで

まずはプロジェクトを作成し、promptuiを依存関係に追加します。

PROJECT_NAME=golang-promptui-example
mkdir $PROJECT_NAME
cd $PROJECT_NAME
go mod init $PROJECT_NAME
go get https://github.com/manifoldco/promptui

文字入力を扱う

以下は、文字入力を扱う場合の公式のサンプルです。
流れとしては以下の3ステップです。

  • validationを作成
  • promptオブジェクトを作成
  • prompt.Run()で入力を取得
package main

import (
	"errors"
	"fmt"
	"strconv"

	"github.com/manifoldco/promptui"
)

func main() {
	// 入力が不正な場合errorを返す関数を作成
	validate := func(input string) error {
		_, err := strconv.ParseFloat(input, 64)
		if err != nil {
			return errors.New("Invalid number")
		}
		return nil
	}

	// インタラクションの表示やバリデーションを設定
	prompt := promptui.Prompt{
		Label:    "Number", // 表示する文言
		Validate: validate, // validate
	}

	result, err := prompt.Run()

	if err != nil {
		fmt.Printf("Prompt failed %v\n", err)
		return
	}

	fmt.Printf("You choose %q\n", result)
}

以上の例は最小限構成ですが、promptui.Promptの設定値としては以下のようなものがあり、Templateでバリデーション状況によって色を変えたり、IsConfirmで y/N形式の入力を扱うこともできます。

key type description
Label interface{} promptに表示されるラベル(基本string)
Default string デフォルト値
AllowEdit bool falseの時、文字入力時にデフォルト値をクリアする
HideEnterd bool trueの時、入力完了後に入力値を隠す
Templates *promptui.PromptTemplate リッチな表現をするためのtemplate
Mask rune パスワードなど、値を隠したい時にマスクする
IsConfirm bool trueの時、y/N での入力になる
IsVimMode bool 左右移動がvim形式で可能になる(ぶっちゃけ不要)
Pointer promptui.Pointer カーソルをカスタマイズできる(DefaultCursor, BlockCursor, PipeCursorが用意されている)

Templateは以下のように、golangのテンプレート構文を利用して、validationの可否によって色を変えるなどができます。

templates := &promptui.PromptTemplates{
		Prompt:  "{{ . }} ",
		Valid:   "{{ . | green }} ",
		Invalid: "{{ . | red }} ",
		Success: "{{ . | bold }} ",
	}

矢印キーによる選択型の入力

選択では、バリデーションは不要であるため、promptインスタンスに、選択肢を配列で与えて、prompt.Run()を実行することで、選択した選択肢の順番(index)と文字列(result)が返ります。ここでは配列を文字列で与えていますが、任意オブジェクトの配列を渡すことができます。(resultにはfmt.Sprintf("%v", item)がかかってstringで帰ってくるため、indexを利用して値を取り出すのが良いと思われます。)

package man

import (
	"fmt"

	"github.com/manifoldco/promptui"
)

func main() {

	prompt := promptui.Select{
		// 選択肢のタイトル
		Label: "Select Day", 
		// 選択肢の配列
		Items: []string{"Monday", "Tuesday", "Wednesday", "Thursday", "Friday", 
			"Saturday", "Sunday"},
	}

	idx, result, err := prompt.Run() //入力を受け取る

	if err != nil {
		fmt.Printf("Prompt failed %v\n", err)
		return
	}

	fmt.Printf("You choose no.%d %q\n",idx, result)
}

promptui.Selectの設定値としては以下のようなものがあります。

key type description
Label interface{} promptに表示されるラベル 基本はstringだがTemplateを使うならstructでも良い
Items 配列 選択肢の配列
Size int スクロールなしで表示する選択肢の数(default=5)
CursorPos int 初期のカーソル位置
IsVimMode bool Vim形式で移動できる
HideHelp bool ヘルプ情報を隠すかどうか
HideSelected bool trueの場合選択後に選択した内容を隠す
Templates *promptui.SelectTemplate リッチな表現をするためのtemplate
Keys *promptui.SelectKey 選択時に操作するキーのカスタム設定
Searcher promptui.list.Searcher (input string,index int) ->bool型の関数を渡すことで、検索を実装できる(/を押すとSearchModeに入る)
StartInSearchMode bool SearchModeから始めるかSelectModeから始めるか
Pointer promptui.Pointer カーソルの種類

また、選択肢以外にユーザが入力して追加できるようにする場合はpromptui.SelectWithAddを使用します。
これらの基本的な書き方は公式の例を参照すると良さそうです。(冒頭のgifもこのサンプルをもとにしています)

https://github.com/manifoldco/promptui/tree/master/_examples

終わりに

本記事では、golangでpromptuiを使用することで、インタラクティブなCLI ツールを作成する方法についてまとめました。
golangはクロスコンパイルが可能かつ、rustほど型が厳しくない言語であるため、サクッとツールを作成する方法を知っておくと何かと便利だと思います。著者はgolangの扱いにはそこまで慣れていませんが、シンプルな言語なだけあり、短い時間でツールを作成できました。この記事が何らか役に立てば幸いです。

Discussion