6
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Okinawa.go Advent Calendar 2016

Day 24

chzyer/readline で作る cowshell

Posted at

最近 go の github trendsgitql という sql 文を使ってローカルリポジトリの情報を確認することができるというツールが急上昇していました. そこで issue の中にインタラクティブなモードを実装するというのがあったので, これは PR チャンスだと思い今回インタラクティブモードを作成しました. そのインタラクティブモードを実装する上で使用したパッケージが github.com/chzyer/readline でした. 今回それが素晴らしかったので, みなさんにも是非使ってもらいたく紹介します!

使い方

サンプルコードが用意されていてこういう感じで使えばいいのかと分かりやすいです.
https://github.com/chzyer/readline/tree/master/example
しかし初挑戦という方は難しいかもしれないので, もう少し噛み砕いたサンプルを作ってみました.

cowshell.go
package main

import (
	"fmt"
	"io"
	"os"
	"strings"

	cowsay "github.com/Code-Hex/Neo-cowsay"
	"github.com/chzyer/readline"
)

func usage(w io.Writer) {
	io.WriteString(w, "commands:\n")
	io.WriteString(w, completer.Tree("    "))
}

// 補完機能の作成
var completer = readline.NewPrefixCompleter(
	readline.PcItem("help"),
	// » cow [tab とした時の補完候補
	readline.PcItem("cow",
		readline.PcItem("say",
			readline.PcItem("rainbow"),
			readline.PcItem("aurora"),
		),
		readline.PcItem("think"),
	),
)

func main() {
	// readline の初期化
	l, err := readline.NewEx(&readline.Config{
		Prompt:          "\033[31m»\033[0m ",
		HistoryFile:     "./cowsh.hist",
		AutoComplete:    completer,
		InterruptPrompt: "^C",
		EOFPrompt:       "exit",
	})
	if err != nil {
		panic(err)
	}
	defer l.Close()

	for {
		line, err := l.Readline()
		// ^D もしくは ^C でループを抜ける
		if err == io.EOF || err == readline.ErrInterrupt {
			break
		}

		// 両端の ' ', '\n', '\t', '\r' を削除
		// https://golang.org/pkg/strings/#TrimSpace
		line = strings.TrimSpace(line)

		cmds := strings.Split(line, " ")
		ln := len(cmds)

		if ln > 2 {
			mow := &cowsay.Cow{
				Type:        "default",
				BallonWidth: 40,
			}

			if strings.Contains(cmds[1], "think") {
				mow.Thinking = true
				mow.Phrase = cmds[2]
			} else {
				if ln > 3 {
					if strings.Contains(cmds[2], "rainbow") {
						mow.IsRainbow = true
					} else if strings.Contains(cmds[2], "aurora") {
						mow.IsAurora = true
					}
					mow.Phrase = cmds[3]
				} else {
					mow.Phrase = cmds[2]
				}
			}

			say, err := cowsay.Say(mow)
			if err != nil {
				fmt.Println(err)
			}
			fmt.Println(say)
		} else {
			usage(os.Stderr)
		}

	}
}

ちょこっと説明します.

readline.NewEx(&readline.Config{
        Prompt:          "\033[31m»\033[0m ",
        HistoryFile:     "./cowsh.hist",
        AutoComplete:    completer,
        InterruptPrompt: "^C",
        EOFPrompt:       "exit",
    })

を読んでみましょう.

  • Prompt enter キーを入力する度に一番左に現れる文字列を指定することができます.
  • HistoryFile 今まで実行したコマンドをファイルに書き出して保存することができます. 今回はカレントディレクトリに作成されるようになっています.
  • InterruptPrompt ctrl+c でシェルから抜け出した時に出力する文字列を指定できます.
  • EOFPrompt ctrl+d でシェルから抜け出した時に出力する文字列を指定できます.

ここで少し難しいのは補完機能です. 今回はこのように簡単に作りました.

var completer = readline.NewPrefixCompleter(
    readline.PcItem("help"),
    // » cow [tab とした時の補完候補
    readline.PcItem("cow",
        readline.PcItem("say",
            readline.PcItem("rainbow"),
            readline.PcItem("aurora"),
        ),
        readline.PcItem("think"),
    ),
)

例えば

readline.PcItem("cow",
        readline.PcItem("say",
            readline.PcItem("rainbow"),
            readline.PcItem("aurora"),
        ),
        readline.PcItem("think"),
    )

の場合だと cow を入力した後 tab キーを入力すると say, think が候補に現れ, cow say を入力した後 tab キーを入力すると rainbow, aurora を候補として出力することができます.

実行

実行するとこんな感じになります.
スクリーンショット 2016-12-24 23.29.58.png

一番下は tab キーを入力して補完を行っている様子です.
さらにこの cowshell を実行しているカレントディレクトリには cowsh.hist というファイルも作成されているはずです.

皆さんも是非試してみてください!

6
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?