ishell:创建交互式cli应用程序库

若与 · · 1076 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

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



有疑问加站长微信联系(非本文作者)

本文来自:简书

感谢作者:若与

查看原文:ishell:创建交互式cli应用程序库

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

1076 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传