Categorygithub.com/Golang-Tools/schema-entry-go

# README

schema-entry-go/V2

通过定义结构体同时声明jsonschem提供复杂的启动参数设置项

V2版本针对1.18以上的golang,大量使用了泛型.低版本请使用V1版本,V1版本将不再更新

特性

  • 支持多级子命令
  • 每级子命令会提示其下一级的子命令
  • 终点节点可以通过定义一个满足接口EndPointConfigInterface的结构体来指定参数的解析行为和入口函数的执行过程
  • 可以通过默认值,指定位置文件,环境变量,命令行参数来构造配置结构,其顺序是命令行参数->环境变量->命令行指定的配置文件->配置指定的配置文件路径->默认值
  • 通过定义满足接口EndPointConfigInterface的结构体中的jsonschematag来定义配置的校验规则
  • 支持jsonyaml两种格式的配置文件
  • 支持watch模式,可以通过监听指定文件更新配置

概念和一些规则

节点

我们使用节点来描述命令之间的关系,定义3类节点:

  1. 根节点,一颗节点树的起始点,它没有父节点
  2. 叶子节点,节点树当中没有子节点的节点
  3. 枝节点,既有父节点又有子节点的节点

叶子节点或者是要执行的的根节点我们使用func NewEndPoint[T EndPointConfigInterface](config T, opts ...optparams.Option[EntryPointMeta]) (*EndPoint[T], error)来创建

不用执行的根节点和枝节点我们使用func NewEntryPoint(opts ...optparams.Option[EntryPointMeta]) (*EntryPoint, error)来创建

一个节点最多只能有一个父节点,但可以有多个子节点.节点与节点间可以使用如下几个方式来注册:

  • func RegistSubNode(parent, child EntryPointInterface)函数
  • func (EntryPointInterface) SetChild(child EntryPointInterface) error,这个方法当在叶子节点上注册子节点时会报错
  • func (EntryPointInterface) SetParent(parent EntryPointInterface) EntryPointInterface,这个方法一般从叶子节点开始向根节点注册.返回的是父节点,所以可以用pipeline的形式注册

参数的解析规则

  1. 所有节点都会解析其--help命令,叶子节点会解析命令,用法,说明,以及参数,其他节点则是解析其命令,说明和支持的子命令.

    节点的名字使用EntryPointMeta.Name定义如果不定义则查找config字段有没有设置,如果设置了则使用config对象的结构体名,如果没有则会报错.

    命令为根节点到当前节点名字中间用空格分隔.

    说明使用EntryPointMeta.Usage设置.

  2. 只有定义了config对象的叶子节点才会进行参数解析 config满足接口:

    //EndPointConfigInterface 叶子节点配置接口
    type EndPointConfigInterface interface {
        Main()         //进入时执行的程序
    }
    

    解析的流程如下:

    使用配置指定路径的配置替换默认值(可以通过`EntryPointMeta.DefaultConfigFilePaths`配置默认路径)
                |
                v
    使用命令行参数`--config`指定的配置文件替换默认值(如果不为空字符串)
                |
                v
    
    使用环境变量替换默认值(如果设置`EntryPointMeta.NotParseEnv: false`)
    可以设置前缀`EntryPointMeta.EnvPrefix`作为环境变量的命名空间,默认前缀为根节点到当前节点间所有节点名中间以`_`分隔.
    前缀和参数间使用`_`分隔
                |
                v
    使用命令行参数替换默认值(如果对应flag有设置)
                |
                v
    解析jsonschema校验规则(如果设置`EntryPointMeta.NotVerifySchema:true`)
                |
                v
    执行`config.Main`
    

watchmode

监听模式用于持续监听一个文件以保持配置最新,在更新模式下我们必须在命令行里显示的指定-c或者--config,来指向一个路径,支持两种方式指定路径:

  • path模式,即直接指定文件系统中的路径,这种方式只能针对本地文件系统,使用的路径可以为绝对路径或者相对路径
  • url模式,即使用url的形式指定路径,其形式为schema://user:password@host:port/path?params这种方式相对比较有扩展性,支持的获取方式有
    • 本地文件系统,使用schema可以为file, fs,使用的路径只能是绝对路径,比如file:///Users/mac/WORKSPACE/GITHUB/GolangTools/schema-entry-go/watch.json
    • docker容器中的文件系统,使用schemadockerfs,使用的路径只能是绝对路径,比如dockerfs:///Users/mac/WORKSPACE/GITHUB/GolangTools/schema-entry-go/watch.json
    • etcd中的内容,使用schemaetcd,其形式如etcd://localhost:9800/foo/bar?address=192.168.1.1:4324&address=192.168.1.2:4324&serialize=JSON,其中
      • host:port部分以及address部分填写etcd的访问地址,也就是说至少要有一个地址
      • path部分为etcd中的内容所在的key
      • 参数中的serialize指明序列化协议,一样的目前只支持JSON和YAML.
      • 其他支持的配置项还包括:
        • auto-sync-interval-ms
        • dial-timeout-ms
        • dial-keep-alive-time-ms
        • dial-keep-alive-timeout-ms
        • max-call-send-msg-size-bytes
        • max-call-recv-msg-size-bytes
        • reject-old-cluster
        • permit-without-stream
        • query-timeout-ms,请求etcd的超时,默认50ms

使用方法

整个使用流程可以拆分为如下步骤

  1. 定义一个配置结构体,并为其实现Main()接口,这个Main()接口就是业务的入口
  2. 使用NewEndPoint来构造一个叶子节点,如果有多个叶子节点可以用NewEntryPoint来构造根节点和枝节点用于串联叶子节点
  3. [可选]如果有根节点和枝节点则将各个节点串联成树
  4. 调用根节点的Parse(argv []string)方法解析配置,一般是写成root.Parse(os.Args)

下面是一个例子,例子中使用github.com/alecthomas/jsonschema在结构体构造时声明了jsonschema约束.

package main

import (
    "fmt"
    "os"
    "time"

    s "github.com/Golang-Tools/schema-entry-go/v2"
    jsoniter "github.com/json-iterator/go"
)

var json = jsoniter.ConfigCompatibleWithStandardLibrary

type C struct {
    A            int   `yaml:"aa" jsonschema:"required,title=a,description=测试int,maximum=10,default=10"`
    B            int   `yaml:"b" jsonschema:"required,title=b,description=测试int,maximum=10,default=100"`
    OK           bool  `json:"ok" jsonschema:"title=o,description=测试bool"`
    Field        []int `json:"field" jsonschema:"required,title=f,description=测试列表"`
    FieldDefault []int `json:"field_default" jsonschema:"required,title=d,description=测试列表默认值,default=1,default=2,default=3,default=4,default=5"`
    WatchValue   int   `json:"WatchValue" jsonschema:"required,title=w,description=测试监听"`
    s            int
}

func (c *C) Test() {
    fmt.Println(c)
}

func (c *C) Main() {
    c.Test()
    time.Sleep(time.Duration(1) * time.Minute)
}

func main() {
    root, _ := s.NewEntryPoint(s.WithName("foo"), s.WithDescription("测试用foo"), s.WithUsage("foo cmd test"))
    nodeb, _ := s.NewEntryPoint(s.WithName("bar"), s.WithDescription("测试用foo bar"), s.WithUsage("foo bar cmd test"))
    nodec, _ := s.NewEndPoint(new(C), s.WithName("par"), s.WithNotVerifySchema(),
        s.WithDefaultConfigFilePaths("conf.json", "config.json", "testconf.yml"),
        s.WithDescription("测试用foo bar par"),
        s.WithUsage("foo bar par cmd test"),
        s.WithLoadAllConfigFile(),
        s.WithWatchMode(),
    )
    nodec.OnRefresh(func(c *C) {
        c.Test()
    })
    os.Setenv("FOO_BAR_PAR_A", "123")
    nodec.SetParent(nodeb).SetParent(root).Parse(os.Args)
}

缺陷

  • 目前不支持命令行位置参数.(依赖的github.com/akamensky/argparse目前不支持)

  • 目前只支持如下几种数据类型(依赖的github.com/akamensky/argparse目前不支持)

    • int,float64,bool,string

    • []int,[]float64,[]string

    • 如果是[]int,[]float64,[]string这三种类型设置default需要像这样写

      FieldDefault []int `json:"field_default" jsonschema:"default=1,default=2,default=3,default=4,default=5"`
      

      它等价于JSONSchema中的default:[1,2,3,4,5]

# Packages

No description provided by the author