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.

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

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

  • The v1.6.1 released: those deprecated functions have been removed.
    This is a bug-fixed version on v1.6.0

  • The v1.5.5 is last release that the DEPRECATED functions would be kept.

    THE DEPRECATED FUNCTIONS
    • Functions:

      1. AddOnBeforeXrefBuilding()
      2. AddOnAfterXrefBuilt()
      3. SetInternalOutputStreams()
      4. SetCustomShowVersion()
      5. SetCustomShowBuildInfo()
      6. PrintBuildInfo()
      7. SetNoLoadConfigFiles()
      8. SetCurrentHelpPainter()
      9. SetHelpTabStop()
      10. ExecWith()
      11. SetUnknownOptionHandler()
      12. SetInternalOutputStreams()
      13. daemon.Enable()
    • Global Variables:

      1. EnableVersionCommands
      2. EnableHelpCommands
      3. EnableVerboseCommands
      4. EnableCmdrCommands
      5. EnableGenerateCommands
      6. EnvPrefix
      7. RxxtPrefix
      8. ShouldIgnoreWrongEnumValue
  • See also Examples, and cmdr-http2 (a http2 server with daemon supports, graceful shutdown).

  • Go Playground ready now, play cmdr at: https://play.golang.org/p/ieExm3V1Pcx

    wget-demo at playground: https://play.golang.org/p/wpEZgQGzKyt demo with daemon plugin: https://play.golang.org/p/wJUA59uGu2M

  • Since v1.5.0, main entry Exec() uses With Functional Options style too:

    Expand to source codes
    err := cmdr.Exec(rootCmd,
      	cmdr.WithXrefBuildingHooks(func(root *cmdr.RootCommand, args []string) {}, func(root *cmdr.RootCommand, args []string) {}),
      	cmdr.WithAutomaticEnvHooks(func(root *cmdr.RootCommand, opts *cmdr.Options) {}),
      	cmdr.WithEnvPrefix("CMDR"), // WithEnvPrefix("F","YY") == T_YY_xxx
      	cmdr.WithOptionsPrefix("app"), // cmdr.WithRxxtPrefix("app"),
      	cmdr.WithPredefinedLocations(nil),
      	cmdr.WithIgnoreWrongEnumValue(true),
      	cmdr.WithBuiltinCommands(true, true, true, true, true),
      	cmdr.WithInternalOutputStreams(nil, nil),
      	cmdr.WithCustomShowVersion(func() {}),
      	cmdr.WithCustomShowBuildInfo(func() {}),
      	cmdr.WithNoLoadConfigFiles(false),
      	cmdr.WithHelpPainter(nil),
      	cmdr.WithConfigLoadedListener(nil),
      	cmdr.WithHelpTabStop(70),
      	cmdr.WithUnknownOptionHandler(func(isFlag bool, title string, cmd *cmdr.Command, args []string) (fallbackToDefaultDetector bool) {
      			return true
      	}), // since v1.5.5
      	cmdr.WithSimilarThreshold(0.73), // since v1.5.5
      	cmdr.WithNoColor(true), // since v1.6.2
      	cmdr.WithNoEnvOverrides(true), // since v1.6.2
      	cmdr.WithStrictMode(true), // since v1.6.2
      )
  • 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 == -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

    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

    since v1.5.0:

    • and multiple flags -vvv == -v -v -v, then cmdr.FindFlagRecursive("verbose", nil).GetTriggeredTime() 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 == --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/.<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
      	"/usr/local/etc/%s/%s.yml", // regular macOS HomeBrew location
      		"$HOME/.config/%s/%s.yml",  // per user
      	"$HOME/.%s/%s.yml",         // ext location per user
      		"$THIS/%s.yml",             // executable's directory
      	"%s.yml",                   // current directory
      },
  • 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.

    • To customize the searching locations yourself:

      • SetPredefinedLocations(locations)

        SetPredefinedLocations([]string{"./config", "~/.config/cmdr/", "$GOPATH/running-configs/cmdr"})
      • since v1.5.0, uses cmdr.WithPredefinedLocations("a","b",...),

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

    • cmdr.Exec(root, cmdr.WithNoLoadConfigFiles(false)): to disabled loading external config files.

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

    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)

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

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

Uses

Contrib

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

License

MIT.