Cmdr - A POSIX/GNU style, getopt-like command-line UI Go library

cmdr

Build Status Go GitHub tag (latest SemVer) GoDoc Go Report Card codecov Coverage Status 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.

ee99d078e2f7

To review the image frames, go surfing at https://github.com/hedzr/cmdr/issues/1#issuecomment-567779978

Table of Contents

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

Import

Need go-modules enabled:

import “github.com/hedzr/cmdr

News

  • v1.6.36
    • ToggleGroup :
      • assume the empty Group field with ToggleGroup
      • set “command-path.toggleGroupName” to the hit flag full name as flipping a toggle-group.
        For example, supposed a toggle-group ‘KK’ under ‘server’ command with 3 choices/flags: apple, banana, orange. For the input ‘–orange’, these entries will be set in option store:
        server.orange <== true;
        server.KK <== ‘orange’;
    • fixed: GetStringSliceXxx() return the value array without expand the envvar.
    • improved: some supports for plan9
    • fixed: can’t expand envvar correectly at earlier initializing.
  • v1.6.35
    • routine maintenance: take effects with bug fixed of logex, etc.
    • typo, doc
    • fixed a data racing scene: the fs-watcher and build-auto-env [rarely]
  • v1.6.33
    • fixed the wrong prototype for nacl/plan9
  • v1.6.32
    • routine maintainance
      • downward compatibility: to go1.11
      • enable github actions
  • 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 styleSample codes
  • 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 specificationsAutomatically 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 flagssince 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

* <del> *todo: float, time, duration, int slice, …, all primitive go types* </del>
* 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 Storeinternal 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
  • 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. ffmaina 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

Build your cli app with cmdr

APP_NAME=your-app-name APP_VERSION=your-app-version W_PKG=github.com/hedzr/cmdr/conf TIMESTAMP=$(date -u ‘+%Y-%m-%d_%I:%M:%S%p’) GITHASH=$(git rev-parse HEAD) GOVERSION=$(go version) LDFLAGS="-s -w -X ‘$W_PKG.Buildstamp=$TIMESTAMP’ -X ‘$W_PKG.Githash=$GITHASH’ -X ‘$W_PKG.GoVersion=$GOVERSION’ -X ‘$W_PKG.Version=$APP_VERSION’ -X '$W_PKG.AppName=$APP_NAME" go build -ldflags “$LDFLAGS” -o bin/app-name ./cli

Uses Fluent API

Expand to source codes

At Playground

Try its out :

Uses

Contrib

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

Thanks to JODL

JODL (JetBrains OpenSource Development License) is good:

goland jetbrains

License

MIT.

Refer: cmdr