インタラクティブにテキスト処理を実行できるツールを作った - txtmanip
外部コマンドを利用したテキスト処理をインタラクティブに実行することができます。
ログ集計などで試行錯誤したり、ワンライナーを作る練習として使えると思います。
デモ
実行している様子は次のとおりです。
cat
で標準出力に出力したcombined形式のアクセスログの内容をパイプで渡し、インタラクティブモード内でawk '{print $7}'
→ sort
→ uniq -c
→ sort -nr
→ head -5
を実行してリクエスト数上位5件のURLを出力しています。
インタラクティブモードを終了すると、インタラクティブモード中に実行したコマンドをパイプで繋げたワンライナーが出力されます。これを実行することで最終的な結果と同じ内容のものを表示することができます。
設定ファイル
TOML形式の設定ファイルに設定を記述します。
現状は、enable_commands
という項目にインタラクティブモード中に実行するコマンドリストを設定することができます。リストにないコマンドは実行することができません。
enable_commands = [ "awk", "cut", "grep", "head", "sed", "sort", "tail", "uniq", "wc", ]
実装について
termbox-go
テキストベースのユーザインタフェースを実現するために、GitHub - nsf/termbox-go を使っています。
termbox-goではSetCell
で位置と文字(rune)を指定してどこになにを描画するかを定義します。これを手続き的に書いていくと非常に分かりづらいコードになってしまうため、そうならないように表示する内容と描画する処理を分けて書くようにしました。
MainView
とそれに含まれるInputArea
(ユーザの入力が表示される部分)とTextArea
(テキスト操作の結果が表示される部分)をそれぞれ構造体として定義しています。表示する内容はそれぞれのフィールドの値として保持します。
// MainView represent main view type MainView struct { textArea TextArea inputArea InputArea height int width int } ... // InputArea represent input area type InputArea struct { text []byte error []byte cursorPos int cursorInitialPos int prompt []byte history []string historyPos int } ... // TextArea represent text area type TextArea struct { text []byte history []string }
描画は各構造体に実装した描画用のメソッドで実行するようにしました。描画用のメソッドはフィールドに保持している値をもとにSetCell
を実行していきます。
以下はTextAreaの描画用メソッドです。
func (v *MainView) DrawTextArea() { y := TextAreaPos x := 0 for _, t := range v.textArea.text { if t == byte('\n') { y++ x = 0 continue } termbox.SetCell(x, y, rune(t), ColFg, ColBg) x++ } }
また、MainView
のFlush()
メソッドをで、描画用メソッドをまとめて実行するようにしています。
これをイベント(キーの入力)ごとに実行することで、都度画面の表示内容を更新しています。
func (v *MainView) Flush() error { if err := termbox.Clear(ColBg, ColBg); err != nil { return err } termbox.SetCursor(v.inputArea.cursorPos, InputAreaPos) v.DrawBorderLine() v.DrawInputArea() v.DrawInputError() v.DrawTextArea() return termbox.Flush() }
課題
元となるテキストやヒストリーをすべて構造体のフィールドの値として保持しており、メモリ効率がよくないと感じています。
また、マルチバイト文字に対応していないので、今後このあたりを改善していこうと思っています。
まとめ
何かありましたらIssue、Pull Requestをいただけると嬉しいです。