Home

Awesome

cmdr

Go GitHub go.mod Go version GitHub tag (latest SemVer) GoDoc FOSSA Status go.dev Go Report Card codecov<!-- [![Coverage Status](https://coveralls.io/repos/github/hedzr/cmdr/badge.svg?branch=master)](https://coveralls.io/github/hedzr/cmdr?branch=master)--> 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.

cover<!-- built by https://ezgif.com/ -->

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/store1. A faster and colorful slog-like logger has been implemented freshly as hedzr/logg2. hedzr/evendeep3 provides a deep fully-functional object copy tool. It helps to deep copy some internal objects easily. It is also ready for you. hedzr/is4 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:

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

cmdr-loaders

Since v2.0.3, loaders had been splitted as a standalone repo so that we can keep cmdr v2 smaller and independer. See the relevant subproject cmdr-loaders.

History

v2 is staying in earlier state:

Guide

A simple cli-app can be:

package main

// Simplest tiny app

import (
    "context"
    "io"
    "os"

    "gopkg.in/hedzr/errors.v3"

    "github.com/hedzr/cmdr/v2"
    "github.com/hedzr/cmdr/v2/cli"
    "github.com/hedzr/cmdr/v2/pkg/dir"
    logz "github.com/hedzr/logg/slog"
    "github.com/hedzr/store"
)

func main() {
    ctx := context.Background() // with cancel can be passed thru in your actions
    app := prepareApp(
        cmdr.WithStore(store.New()), // use an option store explicitly, or a dummy store by default

        // cmdr.WithExternalLoaders(
        //     local.NewConfigFileLoader(), // import "github.com/hedzr/cmdr-loaders/local" to get in advanced external loading features
        //     local.NewEnvVarLoader(),
        // ),

        cmdr.WithTasksBeforeRun(func(ctx context.Context, cmd cli.Cmd, runner cli.Runner, extras ...any) (err error) {
            logz.DebugContext(ctx, "command running...", "cmd", cmd, "runner", runner, "extras", extras)
            return
        }),

        // true for debug in developing time, it'll disable onAction on each Cmd.
        // for productive mode, comment this line.
        // The envvars FORCE_DEFAULT_ACTION & FORCE_RUN can override this.
        // cmdr.WithForceDefaultAction(true),

        // cmdr.WithAutoEnvBindings(true),
    )
    if err := app.Run(ctx); err != nil {
        logz.ErrorContext(ctx, "Application Error:", "err", err) // stacktrace if in debug mode/build
        os.Exit(app.SuggestRetCode())
    }
}

func prepareApp(opts ...cli.Opt) (app cli.App) {
    app = cmdr.New(opts...).
        Info("tiny-app", "0.3.1").
        Author("The Example Authors") // .Description(``).Header(``).Footer(``)

    // another way to disable `cmdr.WithForceDefaultAction(true)` is using
    // env-var FORCE_RUN=1 (builtin already).
    app.Flg("no-default").
        Description("disable force default action").
        // Group(cli.UnsortedGroup).
        OnMatched(func(f *cli.Flag, position int, hitState *cli.MatchState) (err error) {
            if b, ok := hitState.Value.(bool); ok {
                // disable/enable the final state about 'force default action'
                f.Set().Set("app.force-default-action", b)
            }
            return
        }).
        Build()

    app.Cmd("jump").
        Description("jump command").
        Examples(`jump example`). // {{.AppName}}, {{.AppVersion}}, {{.DadCommands}}, {{.Commands}}, ...
        Deprecated(`v1.1.0`).
        // Group(cli.UnsortedGroup).
        Hidden(false).
        // Both With(cb) and Build() to end a building sequence
        With(func(b cli.CommandBuilder) {
            b.Cmd("to").
                Description("to command").
                Examples(``).
                Deprecated(`v0.1.1`).
                OnAction(func(ctx context.Context, cmd cli.Cmd, args []string) (err error) {
                    // cmd.Set() == cmdr.Store(), cmd.Store() == cmdr.Store()
                    cmd.Set().Set("app.demo.working", dir.GetCurrentDir())
                    println()
                    println(cmd.Set().WithPrefix("app.demo").MustString("working"))

                    cs := cmdr.Store().WithPrefix("jump.to")
                    if cs.MustBool("full") {
                        println()
                        println(cmd.Set().Dump())
                    }
                    cs2 := cmd.Store()
                    if cs2.MustBool("full") != cs.MustBool("full") {
                        logz.Panic("a bug found")
                    }
                    app.SetSuggestRetCode(1) // ret code must be in 0-255
                    return                   // handling command action here
                }).
                With(func(b cli.CommandBuilder) {
                    b.Flg("full", "f").
                        Default(false).
                        Description("full command").
                        Build()
                })
        })

    app.Flg("dry-run", "n").
        Default(false).
        Description("run all but without committing").
        Build()

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

    app.Cmd("wrong").
        Description("a wrong command to return error for testing").
        // cmdline `FORCE_RUN=1 go run ./tiny wrong -d 8s` to verify this command to see the returned application error.
        OnAction(func(ctx context.Context, cmd cli.Cmd, args []string) (err error) {
            dur := cmd.Store().MustDuration("duration")
            println("the duration is:", dur.String())

            ec := errors.New()
            defer ec.Defer(&err) // store the collected errors in native err and return it
            ec.Attach(io.ErrClosedPipe, errors.New("something's wrong"), os.ErrPermission)
            // see the application error by running `go run ./tiny/tiny/main.go wrong`.
            return
        }).
        With(func(b cli.CommandBuilder) {
            b.Flg("duration", "d").
                Default("5s").
                Description("a duration var").
                Build()
        })
    return
}

More examples please go to cmdr-tests/examples.

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

Footnotes

  1. hedzr/store is a high-performance configure management library 2

  2. hedzr/logg provides a slog like and colorful logging library

  3. hedzr/evendeep offers a customizable deepcopy tool to you. There are also deepequal, deepdiff tools in it.

  4. hedzr/is is a basic environ detectors library