Skip to content
/ cmdr Public

POSIX-compliant command-line UI (CLI) parser and Hierarchical-configuration operations

License

Notifications You must be signed in to change notification settings

hedzr/cmdr

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

cmdr

Build Status GitHub tag (latest SemVer) GoDoc Go Report Card codecov Mentioned in Awesome Go

cmdr is a POSIX/GNU style, command-line UI (CLI) Go library. It is a getopt-like parser of command-line options, be compatible with the getopt_long command line UI, which is an extension of the syntax recommended by POSIX.

see also Examples, and cmdr-http2 (a http2 server with daemon supports, graceful shutdown).

Youtube - 李宗盛2013最新單曲 山丘 官方完整版音檔 / Jonathan Lee - Hill CHT + ENU

image

Import

for non-go-modules user:

import "gopkg.in/hedzr/cmdr.v1"

with go-modules enabled:

import "github.com/hedzr/cmdr"

News

  • Since v1.0.3, we added compatibilities for migrating from go flag:

    Expand to source codes
    // old code
    
    package main
    
    import "flag"
    
    var (
    	serv           = flag.String("service", "hello_service", "service name")
    	host           = flag.String("host", "localhost", "listening host")
    	port           = flag.Int("port", 50001, "listening port")
    	reg            = flag.String("reg", "localhost:32379", "register etcd address")
    	count          = flag.Int("count", 3, "instance's count")
    	connectTimeout = flag.Duration("connect-timeout", 5*time.Second, "connect timeout")
    )
    
    func main(){
        flag.Parse()
        // ...
    }

    to migrate it to cmdr, just replace the import line with:

    import (
    // flag
    "github.com/hedzr/cmdr/flag"
    )
    
    var (
    	serv           = flag.String("service", "hello_service", "service name")
    	host           = flag.String("host", "localhost", "listening host")
    	port           = flag.Int("port", 50001, "listening port")
    	reg            = flag.String("reg", "localhost:32379", "register etcd address")
    	count          = flag.Int("count", 3, "instance's count")
    	connectTimeout = flag.Duration("connect-timeout", 5*time.Second, "connect timeout")
    )
    
    func main(){
        flag.Parse()
        // ...
    }

    if you wanna use the new features from cmdr, try Withxxx:

    import (
    // flag
    	"github.com/hedzr/cmdr"
    	"github.com/hedzr/cmdr/flag"
    )
    
    var (
        // uncomment this line if you like long opt (such as --service)
        //treatAsLongOpt = flag.TreatAsLongOpt(true)
    
        serv = flag.String("service", "hello_service", "service name",
    		flag.WithAction(func(cmd *cmdr.Command, args []string) (err error) {
    			return
    		}))
        // ...
        // WithTitles, WithShort, WithLong, WithAliases, 
        // WithDescription, WithExamples, WithHidden, 
        // WithGroup, WithDeprecated, WithToggleGroup,
        // WithAction, WithDefaultValue, 
        // WithValidArgs, WithHeadLike, WithExternalTool, 
        // ...
    )

Features

  • Unix getopt(3) representation but without its programmatic interface.

    • Options with short names (-h)
    • Options with long names (--help)
    • Options with aliases (--helpme, --usage, --info)
    • Options with and without arguments (bool v.s. other type)
    • Options with optional arguments and default values
    • Multiple option groups each containing a set of options
    • Supports the compat short options -aux
    • Supports namespaces for (nested) option groups
  • Automatic help screen generation (Generates and prints well-formatted help message)

  • Supports the Fluent API style

    root := cmdr.Root("aa", "1.0.3")
        // Or  // .Copyright("All rights reserved", "sombody@example.com")
        .Header("aa - test for cmdr - hedzr")
    rootCmd = root.RootCommand()
    
    co := root.NewSubCommand().
    	Titles("ms", "micro-service").
    	Description("", "").
    	Group("")
    
    co.NewFlag(cmdr.OptFlagTypeUint).
    	Titles("t", "retry").
    	Description("", "").
    	Group("").
    	DefaultValue(3, "RETRY")
    
    cTags := co.NewSubCommand().
    	Titles("t", "tags").
    	Description("", "").
    	Group("")
  • Muiltiple API styles:

  • Strict Mode

    • false: Ignoring unknown command line options (default)
    • true: Report error on unknown commands and options if strict mode enabled (optional) enable strict mode:
      • env var APP_STRICT_MODE=true
      • hidden option: --strict-mode (if cmdr.EnableCmdrCommands == true)
      • entry in config file:
        app:
          strict-mode: true
  • Supports for unlimited multiple sub-commands.

  • Supports -I/usr/include -I=/usr/include -I /usr/include option argument specifications Automatically allows those formats (applied to long option too):

    • -I file, -Ifile, and -I=files
    • -I 'file', -I'file', and -I='files'
    • -I "file", -I"file", and -I="files"
  • Supports for -D+, -D- to enable/disable a bool option.

  • Supports for PassThrough by --. (Passing remaining command line arguments after -- (optional))

  • Supports for options being specified multiple times, with different values

  • Smart suggestions for wrong command and flags

    since v1.1.3, using Jaro-Winkler distance instead of soundex.

  • Groupable commands and options/flags.

    Sortable group name with [0-9A-Za-z]+\..+ format, eg:

    1001.c++, 1100.golang, 1200.java, …;

    abcd.c++, b999.golang, zzzz.java, …;

  • Sortable commands and options/flags. Or sorted by alphabetic order.

  • Predefined commands and flags:

    • Help: -h, -?, --help, ...
    • Version & Build Info: --version/-V, --build-info/-#
      • Simulating version at runtime with —version-sim 1.9.1
      • generally, conf.AppName and conf.Version are originally.
    • Verbose & Debug: —verbose/-v, —debug/-D, —quiet/-q
    • --no-env-overrides, and --strict-mode
    • Generate Commands:
      • generate shell: —bash/—zsh(todo)/--auto
      • generate manual: man 1 ready.
      • generate doc: markdown ready.
  • Generators

    • Todo: manual generator, and markdown/docx/pdf generators.

    • Man Page generator: bin/demo generate man

    • Markdown generator: bin/demo generate [doc|mdk|markdown]

    • Bash and Zsh (not yet, todo) completion.

      bin/wget-demo generate shell --bash
  • Predefined external config file locations:

    • /etc/<appname>/<appname>.yml and conf.d sub-directory.

    • /usr/local/etc/<appname>/<appname>.yml and conf.d sub-directory.

    • $HOME/.<appname>/<appname>.yml and conf.d sub-directory.

    • Watch conf.d directory:

      • AddOnConfigLoadedListener(c)
      • RemoveOnConfigLoadedListener(c)
      • SetOnConfigLoadedListener(c, enabled)
    • As a feature, do NOT watch the changes on <appname>.yml.

    • To customize the searching locations yourself:

      • SetPredefinedLocations(locations)

        SetPredefinedLocations([]string{"./config", "~/.config/cmdr/", "$GOPATH/running-configs/cmdr"})
    • on command-line:

      # version = 1: bin/demo ~~debug
      bin/demo --configci/etc/demo-yy ~~debug
      # version = 1.1
      bin/demo --config=ci/etc/demo-yy/any.yml ~~debug
      # version = 1.2
    • supports muiltiple file formats:

      • Yaml
      • JSON
      • TOML
    • SetNoLoadConfigFiles(bool) to disable external config file loading.

  • Overrides by environment variables.

    priority level: defaultValue -> config-file -> env-var -> command-line opts

  • Unify option value extraction:

    • cmdr.Get(key), cmdr.GetBool(key), cmdr.GetInt(key), cmdr.GetString(key), cmdr.GetStringSlice(key) and cmdr.GetIntSlice(key), cmdr.GetDuration(key) for Option value extractions.

      • bool
      • int, int64, uint, uint64, float32, float64
      • string
      • string slice, int slice
      • time duration
      • todo: float, time, duration, int slice, …, all primitive go types
      • map
      • struct: cmdr.GetSectionFrom(sectionKeyPath, &holderStruct)
    • cmdr.Set(key, value), cmdr.SerNx(key, value)

      • Set() set value by key without RxxtPrefix, eg: cmdr.Set("debug", true) for --debug.

      • SetNx() set value by exact key. so: cmdr.SetNx("app.debug", true) for --debug.

    • Fast Guide for Get, GetP and GetR:

      • cmdr.GetP(prefix, key), cmdr.GetBoolP(prefix, key), ….
      • cmdr.GetR(key), cmdr.GetBoolR(key), …, cmdr.GetMapR(key)
      • cmdr.GetRP(prefix, key), cmdr.GetBoolRP(prefix, key), ….

      cmdr.Get("app.server.port") == cmdr.GetP("app.server", "port") == cmdr.GetR("server.port") (if cmdr.RxxtPrefix == ["app"]); so:

      cmdr.Set("server.port", 7100)
      assert cmdr.GetR("server.port") == 7100
      assert cmdr.Get("app.server.port") == 7100
  • Walkable

    • Customizable Painter interface to loop each command and flag.
    • Walks on all commands with WalkAllCommands(walker).
  • Daemon (Linux Only)

    Uses daemon feature with go-daemon

    import "github.com/hedzr/cmdr/plugin/daemon"
    func main() {
    	daemon.Enable(NewDaemon(), nil, nil, nil)
    	if err := cmdr.Exec(rootCmd); err != nil {
    		log.Fatal("Error:", err)
    	}
    }
    func NewDaemon() daemon.Daemon {
    	return &DaemonImpl{}
    }

    See full codes in demo app, and cmdr-http2.

    bin/demo server [start|stop|status|restart|install|uninstall]

    install/uninstall sub-commands could install demo app as a systemd service.

    Just for Linux

  • ExecWith(rootCmd *RootCommand, beforeXrefBuilding_, afterXrefBuilt_ HookXrefFunc) (err error)

    AddOnBeforeXrefBuilding(cb)

    AddOnAfterXrefBuilt(cb)

  • More Advanced features

    • Launches external editor by &Flag{BaseOpt:BaseOpt{},ExternalTool:cmdr.ExternalToolEditor}:

      just like git -m, try this command:

      EDITOR=nano bin/demo -m ~~debug

      Default is vim. And -m "something" can skip the launching.

    • ToggleGroup: make a group of flags as a radio-button group.

    • Safe password input for end-user: cmdr.ExternalToolPasswordInput

    • head-like option: treat app do sth -1973 as app do sth -a=1973, just like head -1.

      Flags: []*cmdr.Flag{
          {
              BaseOpt: cmdr.BaseOpt{
                  Short:       "h",
                  Full:        "head",
                  Description: "head -1 like",
              },
              DefaultValue: 0,
              HeadLike:     true,
          },
      },
    • limitation with enumerable values:

      Flags: []*cmdr.Flag{
          {
              BaseOpt: cmdr.BaseOpt{
                  Short:       "e",
                  Full:        "enum",
                  Description: "enum tests",
              },
              DefaultValue: "", // "apple",
              ValidArgs:    []string{"apple", "banana", "orange"},
          },
      },

      While a non-in-list value found, An error (*ErrorForCmdr) will be thrown:

      cmdr.ShouldIgnoreWrongEnumValue = true
      if err := cmdr.Exec(rootCmd); err != nil {
          if e, ok := err(*cmdr.ErrorForCmdr); ok {
              // e.Ignorable is a copy of [cmdr.ShouldIgnoreWrongEnumValue]
              if e.Ignorable {
                  logrus.Warning("Non-recognaizable value found: ", e)
                  os.Exit(0)
              }
          }
          logrus.Fatal(err)
      }
  • More...

Examples

  1. short
    simple codes with structured data style.

  2. demo
    normal demo with external config files.

  3. wget-demo
    partial-covered for GNU wget.

  4. fluent
    demostrates how to define your command-ui with the fluent api style.

  5. ffmain

    a demo to show you how to migrate from go flag smoothly.

  6. cmdr-http2
    http2 server with daemon supports, graceful shutdown

  7. awesome-tool
    awesome-tool is a cli app that fetch the repo stars and generate a markdown summary, accordingly with most of awesome-xxx list in github (such as awesome-go).

Documentation

Uses Fluent API

Expand to source codes
	root := cmdr.Root("aa", "1.0.1").Header("aa - test for cmdr - hedzr")
	rootCmd = root.RootCommand()

	co := root.NewSubCommand().
		Titles("ms", "micro-service").
		Description("", "").
		Group("")

	co.NewFlag(cmdr.OptFlagTypeUint).
		Titles("t", "retry").
		Description("", "").
		Group("").
		DefaultValue(3, "RETRY")

	cTags := co.NewSubCommand().
		Titles("t", "tags").
		Description("", "").
		Group("")

	cTags.NewFlag(cmdr.OptFlagTypeString).
		Titles("a", "addr").
		Description("", "").
		Group("").
		DefaultValue("consul.ops.local", "ADDR")

	cTags.NewSubCommand().
		Titles("ls", "list").
		Description("", "").
		Group("").
		Action(func(cmd *cmdr.Command, args []string) (err error) {
			return
		})

	cTags.NewSubCommand().
		Titles("a", "add").
		Description("", "").
		Group("").
		Action(func(cmd *cmdr.Command, args []string) (err error) {
			return
		})

Uses

Contrib

Feel free to issue me bug reports and fixes. Many thanks to all contributors.

License

MIT.