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.

There are couples of enhancements beyond the standard library flag.

There is a full Options Store (configurations) for your hierarchy configuration data too.

| News | Features | Examples | Docs: For Developer | Docs: Uses Fluent API | At Playground |

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

gif

To review the image frames, go surfing at #1 (comment)

Import

for non-go-modules user:

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

with go-modules enabled:

import "github.com/hedzr/cmdr"

News

  • WIP: v1.6.19

    • added: GetKibibytesXXX, GetKilobytesXXX. with further info, run go run ./examples/fluent kb --size --help.
  • v1.6.18

    • added: WithOnSwitchCharHit, WithOnPassThruCharHit
    • bug fixed
    • reviewing and refactoring
  • For more information to refer to CHANGELOG

Features

cmdr has rich features:

  • builds multi-level command and sub-commands
  • builds short, long and alias options with kinds of data types
  • defines commands and options via fluent api style
  • or defines its with enhanced stdlib flag style
  • full featured Options Store for hosted any application configurations
    • watchable external config file and child directory conf.d
    • watchable option value merging event: while option value modified in external config file and loaded automatically.
    • watchable option value modifying event: while option value modified (from config file, or programmatically)
    • connectable with external configuration center

More

  • 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 == -a -u -x
    • Supports namespaces for (nested) option groups
  • Automatic help screen generation (Generates and prints well-formatted help message)

  • Supports the Fluent API style

    Sample codes
    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("")
    
    // deprecated since v1.6.9
    // co.NewFlag(cmdr.OptFlagTypeUint).
    //  	Titles("t", "retry").
    // 	Description("", "").
    // 	Group("").
    // 	DefaultValue(3, "RETRY")
    
    co.NewFlagV(3).
    	Titles("t", "retry").
    	Description("", "").
    	Group("").
    	Palceholder("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 multi-level 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

    since v1.5.0:

    • and multiple flags -vvv == -v -v -v, then cmdr.FindFlagRecursive("verbose", nil).GetTriggeredTimes() should be 3

    • for bool, string, int, ... flags, last one will be kept and others abandoned:

      -t 1 -t 2 -t3 == -t 3

    • for slice flags, all of its will be merged (NOTE that duplicated entries are as is):

      slice flag overlapped

      • --root A --root B,C,D --root Z,A == --root A,B,C,D,Z cmdr.GetStringSliceR("root") will return []string{"A","B","C","D","Z"}
  • 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, --info, --usage, --helpme, ...
    • Version & Build Info: --version/--ver/-V, --build-info/-#
      • Simulating version at runtime with —version-sim 1.9.1
      • generally, conf.AppName and conf.Version are originally.
      • ~~tree: list all commands and sub-commands.
      • --config <location>: specify the location of the root config file.
    • Verbose & Debug: —verbose/-v, —debug/-D, —quiet/-q
    • Generate Commands:
      • generate shell: —bash/—zsh(todo)/--auto
      • generate manual: man 1 ready.
      • generate doc: markdown ready.
    • cmdr Specials:
      • --no-env-overrides, and --strict-mode
      • --no-color: print the plain text to console without ANSI colors.
  • 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/.config/<appname>/<appname>.yml and conf.d sub-directory.

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

    • all predefined locations are:

      predefinedLocations: []string{
      	"./ci/etc/%s/%s.yml",       // for developer
      	"/etc/%s/%s.yml",           // regular location: /etc/$APPNAME/$APPNAME.yml
      	"/usr/local/etc/%s/%s.yml", // regular macOS HomeBrew location
      	"$HOME/.config/%s/%s.yml",  // per user: $HOME/.config/$APPNAME/$APPNAME.yml
      	"$HOME/.%s/%s.yml",         // ext location per user
      	"$THIS/%s.yml",             // executable's directory
      	"%s.yml",                   // current directory
      },
    • since v1.5.0, uses cmdr.WithPredefinedLocations("a","b",...),

  • Watch conf.d directory:

    • cmdr.WithConfigLoadedListener(listener)

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

      • since v1.6.9, WithWatchMainConfigFileToo(true) allows the main config file <appname>.yml to be watched.
    • on command-line:

      $ bin/demo --configci/etc/demo-yy ~~debug
      $ bin/demo --config=ci/etc/demo-yy/any.yml ~~debug
      $ bin/demo --config ci/etc/demo-yy/any.yml ~~debug
    • supports muiltiple file formats:

      • Yaml
      • JSON
      • TOML
    • cmdr.Exec(root, cmdr.WithNoLoadConfigFiles(false)): disable loading external config files.

  • Overrides by environment variables.

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

  • Option Store - Unify option value extraction:

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

      • bool
      • int, int64, uint, uint64, float32, float64
        $ app -t 1    #  float: 1.1, 1e10, hex: 0x9d, oct: 0700, bin: 0b00010010
      • string
      • string slice, int slice (comma-separated)
        $ app -t apple,banana      # => []string{"apple", "banana"}
        $ app -t apple -t banana   # => []string{"apple", "banana"}
      • time duration (1ns, 1ms, 1s, 1m, 1h, 1d, ...)
        $ app -t 1ns -t 1ms -t 1s -t 1m -t 1h -t 1d
      • 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), ….

      As a fact, 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

      In most cases, GetXxxR() are recommended.

      While extracting string value, the evnvar will be expanded automatically but raw version GetStringNoExpandXXX() available since v1.6.7. For example:

      fmt.Println(cmdr.GetStringNoExpandR("kk"))  // = $HOME/Downloads
      fmt.Println(cmdr.GetStringR("kk"))          // = /home/ubuntu/Downloads
  • cmdr Options Store

    internal rxxtOptions

  • Walkable

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

    rewrote since v1.6.0

    import "github.com/hedzr/cmdr/plugin/daemon"
    func main() {
    	if err := cmdr.Exec(rootCmd,
          daemon.WithDaemon(NewDaemon(), nil,nil,nil),
      	); 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)

  • cmdr.WithXrefBuildingHooks(beforeXrefBuilding, afterXrefBuilding)

  • Debugging options:

    • ~~debug: dump all key value pairs in parsed options store

      $ bin/demo -? ~~debug
      $ bin/demo -? ~~debug ~~raw  # without envvar expanding
      $ bin/demo -? ~~debug ~~env  # print envvar k-v pairs too
      $ bin/demo -? ~~debug --more

      ~~debug depends on --help present (or invoking a command which have one ore more children)

    • InDebugging(), isdelve (refer to here) - To use it, add -tags=delve:

      go build -tags=delve cli/main.go
      go run -tags=delve cli/main.go --help
  • ~~tree: dump all sub-commands

    $ bin/demo ~~tree

    ~~tree is a special option/flag like a command.

  • 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)
      }
    • cmdr.TrapSignals(fn, signals...)

      It is a helper to simplify your infidonite loop before exit program:

      Sample codes Here is sample fragment: ```go func enteringLoop() { waiter := cmdr.TrapSignals(func(s os.Signal) { logrus.Debugf("receive signal '%v' in onTrapped()", s) }) go waiter() } ```
  • 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

For Developer

To build and test cmdr:

$ make help   # see all available sub-targets
$ make info   # display building environment
$ make build  # build binary files for examples
$ make gocov  # test

# customizing
$ GOPROXY_CUSTOM=https://goproxy.io make info
$ GOPROXY_CUSTOM=https://goproxy.io make build
$ GOPROXY_CUSTOM=https://goproxy.io make gocov

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

At Playground

Try its out :

Uses

Contrib

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

License

MIT.