【1日目】graqt開発日誌 ~GolangでリッチなCLIを作る~
最近ブログ書いてないので、graqtの進捗を1mmでも進んだら書くことにする。
graqtについては、以下の記事を参考にされたし。 一言で言うと、バカでも使えるGolang用のWebアプリのボトルネックを調査するソフトウェアです。主にISUCONをターゲットにしています。
serinuntius.hatenablog.jp github.com
進捗
現在、CLI部分を作っていて、見た目だけ少し動くようになった。 まだ、ログ部分とつなぎこみはしていない。
別日だけど、sketchでワイヤー作ったりもした。
めっちゃ雑に今作ってるISUCON用の解析ツールのCLIのデザインをsketchで書いてみた!
— serinuntius (@_serinuntius) August 7, 2018
CLIツールでもGUIの場合ワイヤー引いとかないと、いろいろ要件漏れちゃいそうだからな〜。 pic.twitter.com/d79CjFq5DO
graqtの進捗 #golang #isucon pic.twitter.com/F35efDpx9w
— serinuntius (@_serinuntius) August 16, 2018
golangでリッチなCLIを作るには
jroimartin/gocuiというOSSがすごく便利です。
_exampleに参考になるコードが結構ある。 github.com
今日のコードを貼っておきます。 汚いのは自覚してるし、graqtに持っていくときは清書しますw
package main import ( "log" "github.com/jroimartin/gocui" "fmt" "text/tabwriter" "github.com/pkg/errors" ) type RequestWidget struct { name string x, y int w, h int counter int v *gocui.View tw *tabwriter.Writer } func NewRequestWidget(name string, x, y, w, h int) *RequestWidget { // add initialize return &RequestWidget{name: name, x: x, y: y, w: w, h: h, counter: 0} } func (w *RequestWidget) Layout(g *gocui.Gui) error { // _ => vgit v, err := g.SetView(w.name, w.x, w.y, w.x+w.w, w.y+w.h) if err != nil { if err != gocui.ErrUnknownView { return err } v.Title = "Request Index" v.Highlight = true v.SelBgColor = gocui.ColorGreen v.SelFgColor = gocui.ColorBlack v.SetCursor(0, 1) if err := w.KeyBindings(g); err != nil { return errors.Wrap(err, "Failed to Set KeyBindings") } if _, err := g.SetCurrentView("request"); err != nil { log.Panicln(err) } w.InitTabWriter(v) if _, err := w.PrintHeader(); err != nil { return err } // TODO テスト用 for i := 0; i < 10; i++ { fmt.Fprintln(w.tw, "\t300\tGET\t/hoge\t10\t0.3\t5\t3000\t3\t8\t9\t8\t300mb\t50mb\t30mb\t30000mb") } w.tw.Flush() fmt.Fprintln(w.v) } return nil } func (w *RequestWidget) InitTabWriter(v *gocui.View) error { w.v = v w.tw = tabwriter.NewWriter(v, 0, 8, 2, ' ', 0) return nil } func (w *RequestWidget) PrintHeader() (int, error) { return fmt.Fprintln(w.tw, "\tCount\tMethod\tPath\tMax\tMin\tAvg\tSum\tP1\tP50\tP99\tStddev\tMaxBody\tMinBody\tAvgBody\tSumBody") } func (w *RequestWidget) enter(g *gocui.Gui, v *gocui.View) error { w.v.Clear() w.PrintHeader() w.counter++ for i := 0; i < w.counter; i++ { fmt.Fprintln(w.tw, "\taaaaaaaaaa\tbbb") } w.tw.Flush() fmt.Fprintln(w.v) return nil } func (w *RequestWidget) cursorUp(g *gocui.Gui, v *gocui.View) error { if v != nil { ox, oy := v.Origin() cx, cy := v.Cursor() // cy == 0 is header if cy == 1 { return nil } if err := v.SetCursor(cx, cy-1); err != nil && oy > 0 { if err := v.SetOrigin(ox, oy-1); err != nil { return err } } } return nil } func (w *RequestWidget) cursorDown(g *gocui.Gui, v *gocui.View) error { if v != nil { cx, cy := v.Cursor() if err := v.SetCursor(cx, cy+1); err != nil { ox, oy := v.Origin() if err := v.SetOrigin(ox, oy+1); err != nil { return err } } } return nil } func (w *RequestWidget) Printf(format string, a ...interface{}) { fmt.Fprintf(w.tw, format, a) w.tw.Flush() fmt.Fprintln(w.v) } type HelpWidget struct { name string x, y int w, h int body string } func NewHelpWidget(name string, x, y, w, h int) *HelpWidget { // add initialize return &HelpWidget{name: name, x: x, y: y, w: w, h: h} } func (w *HelpWidget) Layout(g *gocui.Gui) error { // _ => v v, err := g.SetView(w.name, w.x, w.y, w.x+w.w, w.y+w.h) if err != nil { if err != gocui.ErrUnknownView { return err } v.Title = "Help" fmt.Fprintf(v, "%d:%d:%d:%d", w.x, w.y, w.x+w.w, w.y+w.h) } return nil } type QueryWidget struct { name string x, y int w, h int } func NewQueryWidget(name string, x, y, w, h int) *QueryWidget { // add initialize return &QueryWidget{name: name, x: x, y: y, w: w, h: h} } func (w *QueryWidget) Layout(g *gocui.Gui) error { // _ => v v, err := g.SetView(w.name, w.x, w.y, w.x+w.w, w.y+w.h) if err != nil { if err != gocui.ErrUnknownView { return err } v.Title = "Query Index" } return nil } type ParameterWidget struct { name string x, y int w, h int } func NewParameterWidget(name string, x, y, w, h int) *ParameterWidget { // add initialize return &ParameterWidget{name: name, x: x, y: y, w: w, h: h} } func (w *ParameterWidget) Layout(g *gocui.Gui) error { // _ => v v, err := g.SetView(w.name, w.x, w.y, w.x+w.w, w.y+w.h) if err != nil { if err != gocui.ErrUnknownView { return err } v.Title = "Query Parameter" } return nil } func quit(g *gocui.Gui, v *gocui.View) error { return gocui.ErrQuit } func (w *RequestWidget) KeyBindings(g *gocui.Gui) error { if err := g.SetKeybinding("request", gocui.KeyEnter, gocui.ModNone, w.enter); err != nil { return err } if err := g.SetKeybinding("request", gocui.KeyArrowDown, gocui.ModNone, w.cursorDown); err != nil { return err } if err := g.SetKeybinding("request", gocui.KeyArrowUp, gocui.ModNone, w.cursorUp); err != nil { return err } if err := g.SetKeybinding("request", gocui.KeyCtrlN, gocui.ModNone, w.cursorDown); err != nil { return err } if err := g.SetKeybinding("request", gocui.KeyCtrlP, gocui.ModNone, w.cursorUp); err != nil { return err } return nil } func main() { g, err := gocui.NewGui(gocui.OutputNormal) if err != nil { log.Panicln(err) } defer g.Close() g.Highlight = true g.SelFgColor = gocui.ColorBlue winX, winY := g.Size() fmt.Println(winX, winY) request := NewRequestWidget("request", 0, 0, winX-1, winY-3) help := NewHelpWidget("help", 0, winY-3, winX-1, 2) //query := NewQueryWidget("query", winX/2, 0, winX/2-1, winY-3) //parameter := NewParameterWidget("parameter", winX/2, winY/2, winX/2-1, winY/2-2) g.SetManager(help, request) //, query, parameter) if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil { log.Panicln(err) } if err := g.MainLoop(); err != nil && err != gocui.ErrQuit { log.Panicln(err) } }