S3のオブジェクトを毎回ブラウザでマネジメントコンソールにログインしてS3選んで…ってやるのが面倒になってきたので、ターミナルからドリルダウンで検索してダウンロードできるツール作った。
How it works
操作しているのを見てもらうのが早いと思う。
結構サクサク動いていい感じ。
termbox-go
見ての通りtermbox-goを使ってる。
一番最初はpecoを使ってたんだけど、一度選択する度にtermboxが終了して画面が切り替わるのが嫌で、termbox-goの勉強も兼ねてドリルダウンしたりFilter Queryしたりするのを自分で実装した。(補足:もちろんpecoはシンプルに選択するツールなので今回の用途に合わなかっただけです)
あんまりこういうターミナルアプリケーションを作る機会がなかったのでいい経験になったし実務でも役に立っている。
ちなみに未リファクタリングなのでコードベースは汚いので後でやる。
仕様とか詰まったところなど
いろいろ試行錯誤することになった。
temboxの描画
x, yの座標に一つ一つruneを置いていく感じの描画になってるので直感的にはCanvasみたいな感じ。ちょっとミスると文字上書きしちゃったりでステータスとか表示を切り替える処理に手間取った。
termbox-goのKeyRepeat問題
キーボードのリピートをめちゃ早くしてて、カーソルキーを押しっぱなしにするとtermbox側でsliceのout of rangeエラーが出た。(ここ)
環境要因なのかよく分からなかったので、termbox.PollEvent()の戻り値をバッファサイズ1のチャンネルに入れて確実に一個ずつ処理するようにしたら上手く行った。具体的には、
for {
switch evt := termbox.PollEvent(); evt.Type {
case termbox.EventKey:
// do something...
}
}
ってググった記事では出てくるけど、これだとキーリピートが連続するとエラーを吐くようで、
queue := make(chan termbox.Eventundefined 1)
go func() {
for {
select {
case evt := <- queue:
switch evt.Type {
case termbox.EventKey:
// dom something...
}
}
}
}()
go func() {
for {
queue <- termbox.PollEvent()
}
}()
という感じでgoroutine内でなんやかんや処理してあげると落ちなくなった。channel便利。一応キーイベントのハンドラでもmutexしてるけどいらないのかもしれない。
参考にしたのはこれ。完全に解決したわけじゃないみたいだけど少なくとも自分の環境では問題なくなった。
マルチバイト関連
mattnさんのgo-runewidthを使ってあっさり解決。
ログとかデバッグとか
termboxを起動すると標準出力とかが奪われてfmt.Println()とかがtermbox上で出たりでデバッグしづらいので、ログ周りはカレントディレクトリにログファイルを作ってそこに流し込む感じにして開発してた。 panicの時はそうも行かないので、recoverでエラーを拾ってそれもファイルに流す感じ。スタックトレースとれないけど。。。あとmain()のdeferでtermbox.Close()してもシェルに制御が戻った時に挙動がおかしくなったりしたので、そこかしこでCloseしてる。要調査。
func main() {
// do something
if err := termbox.Init(); err != nil {
panic(err)
}
defer func() {
if err := recover(); err != nil {
fp.WriteString(fmt.Sprintf("%v\n"undefined err))
}
termbox.Close()
}()
}
recover()で拾うerrってinterface{}なの知らなかった、、、
ターミナルリサイズで再描画
これが一番しんどかった気がする。でもリサイズイベントで色々処理できるので、ターミナルアプリもレスポンシブにできて面白いのでは(フロントエンド脳)。
まとめ
Githubはこちらです。go getで入るのでよろしければ使ってみてください。Windowsは未検証ですが…
現場からは以上です。