专栏首页友弟技术工作室ishell:创建交互式cli应用程序库

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
    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

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Beego Models之四模型定义

    使用orm定义,然后使用cmd方式,自动建表,不过在实际生产中还是直接使用sql操作的,这种模型定义在生产环境中定义的比较少,基本上都是直接使用基本类型,一些特...

    若与
  • iptables系列五

    iptables系列之layer7 ? 一块网卡多个IP,这张网卡上连接一个交换机,交换机上连接了多个不同网段的主机,如果设置网关,以及转发功能。不同网段主机可...

    若与
  • Nginx架构--nginx系列之二Nginx的架构详解

    Nginx的架构详解 今天,回家,这篇文章在机场候机,原文来自这里 NGINX 在网络应用中表现超群,在于其独特的设计。许多网络或应用服务器大都是基于线程或者进...

    若与
  • ORA-03113的解决

    Windows环境下的Oracle 11g在一次关机后,无法正常启动,且无法启动到mount状态,一直提示:

    孙杰
  • [Go小技巧] 如何写很酷的连贯操作? 原

    (adsbygoogle = window.adsbygoogle || []).push({});

    henrylee2cn
  • 给机器人编舞什么感觉?

    黄翊和机器人”库卡“ 给机器人编舞是一种怎样的体验?在黄翊眼中,名叫“库卡”的机器人有呼吸、有温度,和一只小狗,一头小象,或是一个孩子没什么分别。...

    机器人网
  • associateBy 和 groupBy 之间的区别

    函数associateBy和groupBy构建来自由指定键索引的集合的元素的映射。key在keySelector参数中定义。

    一个会写诗的程序员
  • Python on VS Code

    绿巨人
  • 【Go 语言社区】go 学习中遇到一些语法问题

    1.可变参数,传入数组的饿时候,必须加三个... func sums(nums ...int){} nums := []int{1, 2, 3, 4, 5}...

    李海彬
  • 人的大脑也可以被黑客入侵?这不是危言耸听

    比利时天主教鲁汶大学的一位学术安全研究人员团队发现,大脑植入物非常不安全,因为它们都使用了无线接口。

    FB客服

扫码关注云+社区

领取腾讯云代金券