My CLI Application

CLI Application

一、 简介

1.1. 什么是CLI

命令行界面(英语:command-line interface缩写CLI)是在图形用户界面得到普及之前使用最为广泛的用户界面,它通常不支持鼠标,用户通过键盘输入指令,计算机接收到指令后,予以执行。也有人称之为字符用户界面CUI)。

通常认为,命令行界面(CLI)没有图形用户界面GUI)那么方便用户操作。因为,命令行界面的软件通常需要用户记忆操作的命令,但是,由于其本身的特点,命令行界面要较图形用户界面节约计算机系统的资源。在熟记命令的前提下,使用命令行界面往往要较使用图形用户界面的操作速度要快。所以,图形用户界面的操作系统中,都保留着可选的命令行界面。

1.2. 应用场景

可以为自己的应用/框架提供一套客户端命令工具,通过命令行的形式启动自己的应用/框架。

常见应用:k8s、docker、MySQL、Kitex(?)

1.3. 开发框架

博主使用的是Go语言开发的Cobra,至于原因嘛,无他,就是用的人比较多,构建的企业级应用比较多,star也多,2022年5月12日已经达到26.6k了

二、 Cobra基础

官方文档

2.1. 提供的功能

  • Easy subcommand-based CLIs: app server, app fetch, etc.
  • Fully POSIX-compliant flags (including short & long versions)
  • Nested subcommands
  • Global, local and cascading flags
  • Intelligent suggestions (app srver... did you mean app server?)
  • Automatic help generation for commands and flags
  • Automatic help flag recognition of -h, --help, etc.
  • Automatically generated shell autocomplete for your application (bash, zsh, fish, powershell)
  • Automatically generated man pages for your application
  • Command aliases so you can change things without breaking them
  • The flexibility to define your own help, usage, etc.
  • Optional seamless integration with viper for 12-factor apps

2.2. 安装

默认你已经有了Go环境

go install github.com/spf13/cobra-cli@latest

2.3. 模板生成

cobra-cli初始化模板,在项目目录下执行如下命令:

# 指定作者和证书
cobra-cli init --author "[作者名] [邮箱]" --license [证书类型]

# 使用配置文件生成
cobra-cli init --config ./cobra.yaml

直接使用人家官网文档的模板好了

author: Steve Francia <spf@spf13.com>
year: 2020
license:
	# 项目文件的copyright
  header: This file is part of CLI application foo.
  # 证书模板
  text: |
    {{ .copyright }}

    This is my license. There are many like it, but this one is mine.
    My license is my best friend. It is my life. I must master it as I must
    master my life.

初始化以后项目结构如下:

image-20220512210111488

后续如果要添加额外的命令,可以使用如下的命令

cobra-cli add [命令] -p 'configCmd'

这里的-p指定生成的模板代码中的父命令是什么,默认是rootCmd

image-20220512210720767

2.4. 用法概述

如果想了解全部,建议还是去看官方文档,我这里只是把自己在看官方文档中遇到的不是很清楚的点稍作了一下整理。

# Bool BoolVar BoolVarP三则的区别,从下面的函数签名我们可以知道,Bool代表参数的类型,Var表示是否与变量绑定,O则代表这个flag有短命名(长命名:--config; 短命名:-c)
func (f *FlagSet) Bool(name string, value bool, usage string) *bool
func (f *FlagSet) BoolP(name, shorthand string, value bool, usage string) *bool
func (f *FlagSet) BoolVarP(p *bool, name, shorthand string, value bool, usage string)

TraverseChildren这个属性的含义:在执行子命令时一定会执行父命令的所有flag

// TraverseChildren parses flags on all parents before executing child command.
TraverseChildren bool

PersistentFlags对该命令及其子命令都生效

// PersistentFlags returns the persistent FlagSet specifically set in the current command.
func (c *Command) PersistentFlags() *flag.FlagSet

MarkFlagRequired保证某个flag一定需要存在

// MarkFlagRequired instructs the various shell completion implementations to
// prioritize the named flag when performing completion,
// and causes your command to report an error if invoked without the flag.
func (c *Command) MarkFlagRequired(name string) error

这里有个小问题,就是文档里的Cobra版本比实际master代码的版本要高,也就是说文档里的MarkFlagsRequiredTogether方法还没有出现在master代码中,就有点无语,第一次见文档更新速度比代码快的......

有兴趣可以看一下这个issue


Hook的执行顺序:

  • PersistentPreRun
  • PreRun
  • Run
  • PostRun
  • PersistentPostRun

这里需要注意Persistent*Run有继承性,即如果子命令没有定义自己的Persistent*Run,则子命令会使用父命令的Persistent*Run。可以看一下官网的例子加深一下印象。

package main

import (
  "fmt"

  "github.com/spf13/cobra"
)

func main() {

  var rootCmd = &cobra.Command{
    Use:   "root [sub]",
    Short: "My root command",
    PersistentPreRun: func(cmd *cobra.Command, args []string) {
      fmt.Printf("Inside rootCmd PersistentPreRun with args: %v\n", args)
    },
    PreRun: func(cmd *cobra.Command, args []string) {
      fmt.Printf("Inside rootCmd PreRun with args: %v\n", args)
    },
    Run: func(cmd *cobra.Command, args []string) {
      fmt.Printf("Inside rootCmd Run with args: %v\n", args)
    },
    PostRun: func(cmd *cobra.Command, args []string) {
      fmt.Printf("Inside rootCmd PostRun with args: %v\n", args)
    },
    PersistentPostRun: func(cmd *cobra.Command, args []string) {
      fmt.Printf("Inside rootCmd PersistentPostRun with args: %v\n", args)
    },
  }

  var subCmd = &cobra.Command{
    Use:   "sub [no options!]",
    Short: "My subcommand",
    PreRun: func(cmd *cobra.Command, args []string) {
      fmt.Printf("Inside subCmd PreRun with args: %v\n", args)
    },
    Run: func(cmd *cobra.Command, args []string) {
      fmt.Printf("Inside subCmd Run with args: %v\n", args)
    },
    PostRun: func(cmd *cobra.Command, args []string) {
      fmt.Printf("Inside subCmd PostRun with args: %v\n", args)
    },
    PersistentPostRun: func(cmd *cobra.Command, args []string) {
      fmt.Printf("Inside subCmd PersistentPostRun with args: %v\n", args)
    },
  }

  rootCmd.AddCommand(subCmd)

  rootCmd.SetArgs([]string{""})
  rootCmd.Execute()
  fmt.Println()
  rootCmd.SetArgs([]string{"sub", "arg1", "arg2"})
  rootCmd.Execute()
}

Output:

Inside rootCmd PersistentPreRun with args: []
Inside rootCmd PreRun with args: []
Inside rootCmd Run with args: []
Inside rootCmd PostRun with args: []
Inside rootCmd PersistentPostRun with args: []

Inside rootCmd PersistentPreRun with args: [arg1 arg2]
Inside subCmd PreRun with args: [arg1 arg2]
Inside subCmd Run with args: [arg1 arg2]
Inside subCmd PostRun with args: [arg1 arg2]
Inside subCmd PersistentPostRun with args: [arg1 arg2]

大概学习了一下用法,接下来是如何实操了,去实现一个CLI App。

三、 一个用于便捷生活的CLI App

还没想好要做成什么样子......

  1. 选择生活模式,打开相应的应用
    1. 工作——Lark、Goland、Google(办公网页)
    2. 摸鱼——B站、Wechat(可以选定和谁聊天)、关闭工作网页(???)....
    3. 学习——Github、Goland、打开Booking、退出其他应用
    4. 养生——退出所有应用并关机
  2. XXXXX

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×