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
generic func(*Context)
interrupt func(*Context, int, string)
interruptCount int
eof func(*Context)
reader *shellReader
writer io.Writer
active bool
activeMutex sync.RWMutex
ignoreCase bool
customCompleter bool
multiChoiceActive bool
haltChan chan struct{}
historyFile string
autoHelp bool
rawArgs []string
progressBar ProgressBar
pager string
pagerArgs []string
contextValues
Actions
}
执行的结果:
Sample Interactive Shell
>>> help
Commands:
clear clear the screen
greet greet user
exit exit the program
help display 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
err error
// 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