cmdr

package module
v2.0.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 20, 2024 License: Apache-2.0 Imports: 5 Imported by: 0

README

cmdr

Go GitHub go.mod Go version GitHub tag (latest SemVer) GoDoc FOSSA Status go.dev Go Report Card codecov Mentioned in Awesome Go

Unstable v2: Working in progress, the main API might be stable till v2.1.0

cmdr is a POSIX-compliant, command-line argument parser library with Golang.

Since v2, our license moved to Apache 2.0.

ee99d078e2f7

See the image frames at #1.

Motivation

There are many dirty codes in the cmdr.v1 which cannot be refactored as well. It prompted we reimplment a new one as v2.

The passing winter, we did rewrite the cmdr.v2 to keep it clean and absorbed in parsing and dispatching. Some abilities were removed and relayouted to new modules. That's why the Option Store has been split as a standalone module hedzr/store[^1]. A faster and colorful slog-like logger has been implemented freshly as hedzr/logg[^3]. hedzr/evendeep[^2] provides a deep fully-functional object copy tool. It helps to deep copy some internal objects easily. It is also ready for you. hedzr/is[^4] is an environment detecting framework with many out-of-the-box detectors, such as is.InTesting and is.InDebugging.

Anyway, the whole supply chain painted:

graph BT
  hzis(hedzr/is)-->hzlogg(hedzr/logg/slog)
  hzis-->hzdiff(hedzr/evendeep)
  hzlogg-->hzdiff
  hzerrors(gopkg.in/hedzr/errors.v3)-->hzdiff
  hzerrors-->hzstore(hedzr/store)
  hzis-->hzstore(hedzr/store)
  hzlogg-->hzstore(hedzr/store)
  hzdiff-->hzstore(hedzr/store)
  hzlogg-->cmdr(hedzr/cmdr/v2)
  hzis-->cmdr
  hzlogg-->cmdr
  hzdiff-->cmdr
  hzstore-->cmdr

  1. The .netCore version Cmdr.Core is available now.
  2. A cxx version cmdr-cxx was released (Happy Spring Festival 2021).

Features

v2 is in earlier state but the baseline is stable:

  • Basic command-line arguments parser like POSIX getopt and go stdlib flag.

    • Short flag, single character or a string here to support golang CLI style

      • Compact flags if possible. Also the sticking value will be parsed. For example: -c1b23zv = -c 1 -b 23 -z -v
      • Hit info: -v -v -v = -v (hitCount == 3, hitTitle == 'v')
      • Optimized for slice: -a 1,2,3 -a 4 -a 5,6 => []int{1,2,3,4,5,6}
      • Value can be sticked or not. Valid forms: -c1, -c 1, -c=1 and quoted: -c"1", -c'1', -c="1", -c='1', etc.
      • ...
    • Long flags and aliases

    • Eventual subcommands: an OnAction handler can be attached.

    • Eventual subcommands and flags: PreActions, PostAction, OnMatching, OnMatched, ...,

    • Auto bind to environment variables, For instance: command line HELP=1 app = app --help.

    • Builtin commands and flags:

      • --help, -h
      • --version, -V
      • --verbose. -v
      • ...
    • Help Screen: auto generate and print

    • Smart suggestions when wrong cmd or flag parsed. Jaro-winkler distance is used.

  • Loosely parse subcmds and flags:

    • Subcommands and flags can be input in any order
    • Lookup a flag along with subcommands tree for resolving the duplicated flags
  • Can integrate with hedzr/store[^1]

    • High-performance in-memory KV store for hierarchical data.
    • Extract data to user-spec type with auto-converting
    • Loadable external sources: environ, config files, consul, etcd, etc..
      • extensible codecs and providers for loading from data sources
  • Three kinds of config files are searched and loaded via loaders.NewConfigFileLoader():

    • Primary: main config, shipped with installable package.
    • Secondary: 2ndry config. Wrapped by reseller(s).
    • Alternative: user's local config, writeable. The runtime changeset will be written back to this file while app stopping.
  • TODO

    • Shell autocompletion
    • ...

[^1]: hedzr/store is a high-performance configure management library [^2]: hedzr/evendeep offers a customizable deepcopy tool to you. There are also deepequal, deepdiff tools in it. [^3]: hedzr/logg provides a slog like and colorful logging library [^4]: hedzr/is is a basic environ detectors library

More minor details need to be evaluated and reimplemented if it's still meaningful in v2.

History

v2 is staying in earlier state:

  • Latest: v2.0.3

    • split loaders as a standalone repo
    • split examples and tests to standalone
    • update deps
    • fix bugs
  • Full list: CHANGELOG

Guide

A simple cli-app can be:

package main

import (
	logz "github.com/hedzr/logg/slog"

	"github.com/hedzr/cmdr/v2"
	"github.com/hedzr/cmdr/v2/cli"
	"github.com/hedzr/cmdr/v2/loaders"
	"github.com/hedzr/cmdr/v2/pkg/dir"
	"github.com/hedzr/store"
)

func main() {
	app := prepareApp()

	// // simple run the parser of app and trigger the matched command's action
	// _ = app.Run(
	// 	cmdr.WithForceDefaultAction(false), // true for debug in developing time
	// )

	if err := app.Run(
		cmdr.WithStore(store.New()),
		cmdr.WithExternalLoaders(
			loaders.NewConfigFileLoader(),
			loaders.NewEnvVarLoader(),
		),
		cmdr.WithForceDefaultAction(true), // true for debug in developing time
	); err != nil {
		logz.Error("Application Error:", "err", err)
	}
}

func prepareApp() (app cli.App) {
	app = cmdr.New().
		Info("demo-app", "0.3.1").
		Author("hedzr")
	app.AddFlg(func(b cli.FlagBuilder) {
		b.Titles("no-default").
			Description("disable force default action").
			OnMatched(func(f *cli.Flag, position int, hitState *cli.MatchState) (err error) {
				app.Store().Set("app.force-default-action", false)
				return
			})
	})
	app.AddCmd(func(b cli.CommandBuilder) {
		b.Titles("jump").
			Description("jump command").
			Examples(`jump example`).
			Deprecated(`jump is a demo command`).
			Hidden(false)

		b.AddCmd(func(b cli.CommandBuilder) {
			b.Titles("to").
				Description("to command").
				Examples(``).
				Deprecated(`v0.1.1`).
				Hidden(false).
				OnAction(func(cmd *cli.Command, args []string) (err error) {
					app.Store().Set("app.demo.working", dir.GetCurrentDir())
					println()
					println(dir.GetCurrentDir())
					println()
					println(app.Store().Dump())
					return // handling command action here
				})
			b.AddFlg(func(b cli.FlagBuilder) {
				b.Default(false).
					Titles("full", "f").
					Description("full command").
					Build()
			})
		})
	})

	app.AddFlg(func(b cli.FlagBuilder) {
		b.Titles("dry-run", "n").
			Default(false).
			Description("run all but without committing")
	})

	app.Flg("wet-run", "w").
		Default(false).
		Description("run all but with committing").
		Build() // no matter even if you're adding the duplicated one.
	return
}

Thanks to JODL

Thanks to JetBrains for donating product licenses to help develop cmdr
jetbrains goland

License

Since v2, our license moved to Apache 2.0.

The v1 keeps under MIT itself.

FOSSA Status

Documentation

Index

Constants

View Source
const Version = "v2.0.3" // Version fir hedzr/cmdr/v2

Variables

This section is empty.

Functions

func App

func App() cli.Runner

App returns a light version of builder.Runner (a.k.a. *worker.Worker).

Generally it's a unique instance in one system.

It's available once New() / Exec() called, else nil.

func Exec

func Exec(rootCmd *cli.RootCommand, opts ...cli.Opt) (err error)

Exec starts a new cmdr app (parsing cmdline args based on the given rootCmd) from scratch.

It's a reserved API for back-compatible with cmdr v1.

It'll be removed completely at the recently future version.

Deprecated since 2.1

func New

func New(opts ...cli.Opt) cli.App

New starts a new cmdr app.

With the returned builder.App, you may build root and sub-commands fluently.

app := cmdr.New().
    Info("demo-app", "0.3.1").
    Author("hedzr")
app.AddCmd(func(b config.CommandBuilder) {
    b.Titles("jump").
        Description("jump command").
        Examples(``).
        Deprecated(``).
        Hidden(false).
        AddCmd(func(b config.CommandBuilder) {
            b.Titles("to").
                Description("to command").
                Examples(``).
                Deprecated(``).
                Hidden(false).
                OnAction(func(cmd *obj.Command, args []string) (err error) {
                    return // handling command action here
                }).
                Build()
        }).
        Build()
}).AddFlg(func(b config.FlagBuilder) {
    b.Titles("dry-run", "n").Default(false).Build()
})

app.Flg("dry-run", "n").
    Default(false).
    Build() // no matter even if you're adding the duplicated one.

// // simple run the parser of app and trigger the matched command's action
// _ = app.Run(
//     cmdr.WithForceDefaultAction(false), // true for debug in developing time
// )

if err := app.Run(
    cmdr.WithForceDefaultAction(false), // true for debug in developing time
); err != nil {
    logz.Error("Application Error:", "err", err)
}

After the root command and all its children are built, use app.[config.App.Run] to parse end-user's command-line arguments, and invoke the bound action on the hit subcommand.

It is not necessary to attach an action onto a parent command, because its subcommands are the main characters - but you still can do that.

func WithExternalLoaders

func WithExternalLoaders(loaders ...cli.Loader) cli.Opt

func WithForceDefaultAction

func WithForceDefaultAction(b bool) cli.Opt

func WithStore

func WithStore(conf store.Store) cli.Opt

func WithTasksBeforeParse

func WithTasksBeforeParse(tasks ...cli.Task) cli.Opt

func WithTasksBeforeRun

func WithTasksBeforeRun(tasks ...cli.Task) cli.Opt

func WithUnmatchedAsError

func WithUnmatchedAsError(b bool) cli.Opt

Types

This section is empty.

Directories

Path Synopsis
cli
atoa
Package atoa - converters for any to any
Package atoa - converters for any to any
Package conf are used to store the app-level constants (app name/vaersion) for cmdr and your app.
Package conf are used to store the app-level constants (app name/vaersion) for cmdr and your app.
internal
hs
pkg
dir
Package dir provides a series of directory/file operations
Package dir provides a series of directory/file operations

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL