首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >ishell:创建交互式cli应用程序库

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

作者头像
若与
发布2019-03-05 16:16:56
1.5K0
发布2019-03-05 16:16:56
举报

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

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019.01.30 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 代码示例
    • 执行的结果:
    • 常用的属性
    • 分析一下上面的源码
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档