ishell:建立互動式cli應用程式庫
ishell是一個用於建立互動式cli應用程式的互動式shell庫。

最近在研究supervisor的原始碼,參考supervisor的架構,做公司的專案。我後面會給出supervisor的開源學習的總結。github上有一個gopher寫了一個golang版的supervisor, 原始碼 ,原理和python版的都類似,但是 ctl是執行命令的方式,不是很優雅。
今天這篇文章介紹一個go的包,實現互動式的CLI工具的包。
常見的cli包有:flag、cli、os...都可以實現
但是上面有一個問題,就是執行完以後,就會給出結果,並退出,不是進入一個shell中,執行所有結果都是不同的。
互動式的cli如下:

今天要介紹的庫是 ishell
類似上面的gif圖中效果,很容易實現
程式碼示例
import "strings" import "github.com/abiosoft/ishell" func main(){ // create new shell. // by default, new shell includes 'exit', 'help' and 'clear' commands. shell := ishell.New() // display welcome info. shell.Println("Sample Interactive Shell") // register a function for "greet" command. shell.AddCmd(&ishell.Cmd{ Name: "greet", Help: "greet user", Func: func(c *ishell.Context) { c.Println("Hello", strings.Join(c.Args, " ")) }, }) // run shell shell.Run() }
上面程式碼很簡單就是先例項化 ishell.New()
一個 Shell
物件,使用方法 AddCmd
新增命令
看一下原始碼:
// New creates a new shell with default settings. Uses standard output and default prompt ">> ". func New() *Shell { return NewWithConfig(&readline.Config{Prompt: defaultPrompt}) } // NewWithConfig creates a new shell with custom readline config. func NewWithConfig(conf *readline.Config) *Shell { rl, err := readline.NewEx(conf) if err != nil { log.Println("Shell or operating system not supported.") log.Fatal(err) } return NewWithReadline(rl) } // NewWithReadline creates a new shell with a custom readline instance. func NewWithReadline(rl *readline.Instance) *Shell { shell := &Shell{ rootCmd: &Cmd{}, reader: &shellReader{ scanner:rl, prompt:rl.Config.Prompt, multiPrompt: defaultMultiPrompt, showPrompt:true, buf:&bytes.Buffer{}, completer:readline.NewPrefixCompleter(), }, writer:rl.Config.Stdout, autoHelp: true, } shell.Actions = &shellActionsImpl{Shell: shell} shell.progressBar = newProgressBar(shell) addDefaultFuncs(shell) return shell } func (s *Shell) AddCmd(cmd *Cmd) { s.rootCmd.AddCmd(cmd) } // AddCmd adds cmd as a subcommand. func (c *Cmd) AddCmd(cmd *Cmd) { if c.children == nil { c.children = make(map[string]*Cmd) } c.children[cmd.Name] = cmd }
再看一下shell的結構體:
type Shell struct { rootCmd*Cmd genericfunc(*Context) interruptfunc(*Context, int, string) interruptCountint eoffunc(*Context) reader*shellReader writerio.Writer activebool activeMutexsync.RWMutex ignoreCasebool customCompleterbool multiChoiceActive bool haltChanchan struct{} historyFilestring autoHelpbool rawArgs[]string progressBarProgressBar pagerstring pagerArgs[]string contextValues Actions }
執行的結果:
Sample Interactive Shell >>> help Commands: clearclear the screen greetgreet user exitexit the program helpdisplay help >>> greet Someone Somewhere Hello Someone Somewhere >>> exit $
常用的屬性
1. 輸入資料或密碼
shell.AddCmd(&ishell.Cmd{ Name: "login", Func: func(c *ishell.Context) { c.ShowPrompt(false) defer c.ShowPrompt(true) c.Println("Let's simulate login") // prompt for input c.Print("Username: ") username := c.ReadLine() c.Print("Password: ") password := c.ReadPassword() // do something with username and password c.Println("Your inputs were", username, "and", password+".") }, Help: "simulate a login", })
2. 輸入可以換行
// read multiple lines with "multi" command shell.AddCmd(&ishell.Cmd{ Name: "multi", Help: "input in multiple lines", Func: func(c *ishell.Context) { c.Println("Input multiple lines and end with semicolon ';'.") // 設定結束符 lines := c.ReadMultiLines(";") c.Println("Done reading. You wrote:") c.Println(lines) }, })
3. 單選
// choice shell.AddCmd(&ishell.Cmd{ Name: "choice", Help: "multiple choice prompt", Func: func(c *ishell.Context) { choice := c.MultiChoice([]string{ "Golangers", "Go programmers", "Gophers", "Goers", }, "What are Go programmers called ?") if choice == 2 { c.Println("You got it!") } else { c.Println("Sorry, you're wrong.") } }, })
4. 多選
// multiple choice shell.AddCmd(&ishell.Cmd{ Name: "checklist", Help: "checklist prompt", Func: func(c *ishell.Context) { languages := []string{"Python", "Go", "Haskell", "Rust"} choices := c.Checklist(languages, "What are your favourite programming languages ?", nil) out := func() (c []string) { for _, v := range choices { c = append(c, languages[v]) } return } c.Println("Your choices are", strings.Join(out(), ", ")) }, })
5. 顏色
cyan := color.New(color.FgCyan).SprintFunc() yellow := color.New(color.FgYellow).SprintFunc() boldRed := color.New(color.FgRed, color.Bold).SprintFunc() shell.AddCmd(&ishell.Cmd{ Name: "color", Help: "color print", Func: func(c *ishell.Context) { c.Print(cyan("cyan\n")) c.Println(yellow("yellow")) c.Printf("%s\n", boldRed("bold red")) }, })
6. 進度條
// progress bars { // determinate shell.AddCmd(&ishell.Cmd{ Name: "det", Help: "determinate progress bar", Func: func(c *ishell.Context) { c.ProgressBar().Start() for i := 0; i < 101; i++ { c.ProgressBar().Suffix(fmt.Sprint(" ", i, "%")) c.ProgressBar().Progress(i) time.Sleep(time.Millisecond * 100) } c.ProgressBar().Stop() }, }) // indeterminate shell.AddCmd(&ishell.Cmd{ Name: "ind", Help: "indeterminate progress bar", Func: func(c *ishell.Context) { c.ProgressBar().Indeterminate(true) c.ProgressBar().Start() time.Sleep(time.Second * 10) c.ProgressBar().Stop() }, }) }
分析一下上面的原始碼
上面介紹了一些常用的命令,下面我們直接看原始碼:
shell.AddCmd(&ishell.Cmd{ Name: "det", Help: "determinate progress bar", Func: func(c *ishell.Context) { c.ProgressBar().Start() for i := 0; i < 101; i++ { c.ProgressBar().Suffix(fmt.Sprint(" ", i, "%")) c.ProgressBar().Progress(i) time.Sleep(time.Millisecond * 100) } c.ProgressBar().Stop() }, })
上面很多操作都是在 func(c *ishell.Context)
裡面操作的
type Context struct { contextValues progressBar ProgressBar errerror // Args is command arguments. Args []string // RawArgs is unprocessed command arguments. RawArgs []string // Cmd is the currently executing command. This is empty for NotFound and Interrupt. Cmd Cmd Actions }
重要 內容都在Actions中
// Actions are actions that can be performed by a shell. type Actions interface { // ReadLine reads a line from standard input. ReadLine() string // ReadLineErr is ReadLine but returns error as well ReadLineErr() (string, error) // ReadLineWithDefault reads a line from standard input with default value. ReadLineWithDefault(string) string // ReadPassword reads password from standard input without echoing the characters. // Note that this only works as expected when the standard input is a terminal. ReadPassword() string // ReadPasswordErr is ReadPassword but returns error as well ReadPasswordErr() (string, error) // ReadMultiLinesFunc reads multiple lines from standard input. It passes each read line to // f and stops reading when f returns false. ReadMultiLinesFunc(f func(string) bool) string // ReadMultiLines reads multiple lines from standard input. It stops reading when terminator // is encountered at the end of the line. It returns the lines read including terminator. // For more control, use ReadMultiLinesFunc. ReadMultiLines(terminator string) string // Println prints to output and ends with newline character. Println(val ...interface{}) // Print prints to output. Print(val ...interface{}) // Printf prints to output using string format. Printf(format string, val ...interface{}) // ShowPaged shows a paged text that is scrollable. // This leverages on "less" for unix and "more" for windows. ShowPaged(text string) error // ShowPagedReader shows a paged text that is scrollable, from a reader source. // This leverages on "less" for unix and "more" for windows. ShowPagedReader(r io.Reader) error // MultiChoice presents options to the user. // returns the index of the selection or -1 if nothing is // selected. // text is displayed before the options. MultiChoice(options []string, text string) int // Checklist is similar to MultiChoice but user can choose multiple variants using Space. // init is initially selected options. Checklist(options []string, text string, init []int) []int // SetPrompt sets the prompt string. The string to be displayed before the cursor. SetPrompt(prompt string) // SetMultiPrompt sets the prompt string used for multiple lines. The string to be displayed before // the cursor; starting from the second line of input. SetMultiPrompt(prompt string) // SetMultiChoicePrompt sets the prompt strings used for MultiChoice(). SetMultiChoicePrompt(prompt, spacer string) // SetChecklistOptions sets the strings representing the options of Checklist(). // The generated string depends on SetMultiChoicePrompt() also. SetChecklistOptions(open, selected string) // ShowPrompt sets whether prompt should show when requesting input for ReadLine and ReadPassword. // Defaults to true. ShowPrompt(show bool) // Cmds returns all the commands added to the shell. Cmds() []*Cmd // HelpText returns the computed help of top level commands. HelpText() string // ClearScreen clears the screen. Same behaviour as running 'clear' in unix terminal or 'cls' in windows cmd. ClearScreen() error // Stop stops the shell. This will stop the shell from auto reading inputs and calling // registered functions. A stopped shell is only inactive but totally functional. // Its functions can still be called and can be restarted. Stop() }
具體的用法說明,有註釋。
如果需要深入,就自己看吧。有什麼問題,可以私信給我。
下面我展示一下demo
