Goで処理途中でvimを開いてその結果を受け取って後続の処理をする

タイトルをうまい感じにまとめられなくてそのままなタイトルになってしまったw

最近 Go のスキルをあげるのとちょっとほしい CLI があって作ってます。
その中でユーザーからの入力を echo --message hogehoge のように実行時に引数として文字列を受け取るコマンドを作ってました。
ただこれはある程度の文字数と複数行が想定される引数です。
さすがにコマンドラインの引数として複数行わたしていくのは面倒そうだなーと思い良い案がないか考えてました。

自分が使っている身近なツールとかで解決していないかなと探した時にそういえば tig がやりたいことに近いことをしていたのでソースを見てみました。

github.com

tig は Text-mode interface for git と公式には書いてあってターミナルで良い感じに git を操作できるツールで、 commit する時に Editor が開くのですが、これがやりたいことに近かったです。

サーと読んだ感じと挙動をみた感じ EDITOR とか VISUAL のような環境変数からどのeditor で起動するかを判定してこれを使って .git/COMMIT_EDITMSG 開いている。 エディタで保存し終わると再度このファイルを読み込んで後続の処理をしている感じだった。
https://github.com/jonas/tig/blob/89667b4afb17b0535c64aab86fa0770451b076fe/src/display.c#L125

まぁーなるほどねーという感じ。
とりあえず Go でサンプルなコードを書いた。

package main

import (
    "fmt"
    "io/ioutil"
    "log"
    "os"
    "os/exec"
)

const TEMPLATE = `## Title1

## Title2

`

func main() {
    tmpfile, err := ioutil.TempFile("", "hogehoge")
    if err != nil {
        log.Fatal(err)
    }
    defer os.Remove(tmpfile.Name())
    defer tmpfile.Close()

    if _, err := tmpfile.Write([]byte(TEMPLATE)); err != nil {
        log.Fatal(err)
    }
    if err := openEditor(tmpfile.Name()); err != nil {
        log.Fatal(err)
    }

    content, err := ioutil.ReadFile(tmpfile.Name())
    if err != nil {
        log.Fatal(err)
    }

    fmt.Printf("%s", content)
}

func openEditor(filepath string) error {
    cmdName := "vi"
    if e := os.Getenv("EDITOR"); e != "" {
        cmdName = e
    }

    c := exec.Command(cmdName, filepath)
    c.Stdin = os.Stdin
    c.Stdout = os.Stdout
    c.Stderr = os.Stderr
    return c.Run()
}

処理として tmp file を作成してそれにあらかじめ決めておいたテンプレートを流し込む。

その後に指定された editor で開いて書き込んだ後に保存したりして editor のプロセスを終了するとその後の処理で再度ファイルを読み込んで中身を出力する。

実際に実行すると↓のようにテンプレートを流した状態のファイルを編集するためのエディタが立ち上がる。
f:id:hatappi1225:20191012111048p:plain

編集して保存すれば内容が出力される。

f:id:hatappi1225:20191012111246p:plain