MCPcopy Index your code
hub / github.com/jawher/mow.cli

github.com/jawher/mow.cli @v1.2.0

Chat with this repo
repository ↗ · DeepWiki ↗ · release v1.2.0 ↗ · + Follow
472 symbols 1,981 edges 48 files 176 documented · 37% 2 cross-repo links
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

mow.cli

CI GoDoc Coverage Status

Package cli provides a framework to build command line applications in Go with most of the burden of arguments parsing and validation placed on the framework instead of the user.

Getting Started

The following examples demonstrate basic usage the package.

Simple Application

In this simple application, we mimic the argument parsing of the standard UNIX cp command. Our application requires the user to specify one or more source files followed by a destination. An optional recursive flag may be provided.

package main

import (
    "fmt"
    "os"

    "github.com/jawher/mow.cli"
)

func main() {
    // create an app
    app := cli.App("cp", "Copy files around")

    // Here's what differentiates mow.cli from other CLI libraries:
    // This line is not just for help message generation.
    // It also validates the call to reject calls with less than 2 arguments
    // and split the arguments between SRC or DST
    app.Spec = "[-r] SRC... DST"

    var (
        // declare the -r flag as a boolean flag
        recursive = app.BoolOpt("r recursive", false, "Copy files recursively")
        // declare the SRC argument as a multi-string argument
        src = app.StringsArg("SRC", nil, "Source files to copy")
        // declare the DST argument as a single string (string slice) arguments
        dst = app.StringArg("DST", "", "Destination where to copy files to")
    )

    // Specify the action to execute when the app is invoked correctly
    app.Action = func() {
        fmt.Printf("Copying %v to %s [recursively: %v]\n", *src, *dst, *recursive)
    }

    // Invoke the app passing in os.Args
    app.Run(os.Args)
}

Pointers to existing variables

This variant of the cp command uses the Ptr variants, where you can pass pointers to existing variables instead of declaring new ones for the options/arguments:

package main

import (
    "fmt"
    "os"

    cli "github.com/jawher/mow.cli"
)

type Config struct {
    Recursive bool
    Src       []string
    Dst       string
}

func main() {
    var (
        app = cli.App("cp", "Copy files around")
        cfg Config
    )
    // Here's what differentiates mow.cli from other CLI libraries:
    // This line is not just for help message generation.
    // It also validates the call to reject calls with less than 2 arguments
    // and split the arguments between SRC or DST
    app.Spec = "[-r] SRC... DST"

    // declare the -r flag as a boolean flag
    app.BoolOptPtr(&cfg.Recursive, "r recursive", false, "Copy files recursively")
    // declare the SRC argument as a multi-string argument
    app.StringsArgPtr(&cfg.Src, "SRC", nil, "Source files to copy")
    // declare the DST argument as a single string (string slice) arguments
    app.StringArgPtr(&cfg.Dst, "DST", "", "Destination where to copy files to")

    // Specify the action to execute when the app is invoked correctly
    app.Action = func() {
        fmt.Printf("Copying using config: %+v\n", cfg)
    }
    // Invoke the app passing in os.Args
    app.Run(os.Args)
}

Multi-Command Application

In the next example, we create a multi-command application in the same style as familiar commands such as git and docker. We build a fictional utility called uman to manage users in a system. It provides two commands that can be invoked: list and get. The list command takes an optional flag to specify all users including disabled ones. The get command requires one argument, the user ID, and takes an optional flag to specify a detailed listing.

package main

import (
    "fmt"
    "os"

    "github.com/jawher/mow.cli"
)

func main() {
    app := cli.App("uman", "User Manager")

    app.Spec = "[-v]"

    var (
        verbose = app.BoolOpt("v verbose", false, "Verbose debug mode")
    )

    app.Before = func() {
        if *verbose {
            // Here we can enable debug output in our logger for example
            fmt.Println("Verbose mode enabled")
        }
    }

    // Declare our first command, which is invocable with "uman list"
    app.Command("list", "list the users", func(cmd *cli.Cmd) {
        // These are the command-specific options and args, nicely scoped
        // inside a func so they don't pollute the namespace
        var (
            all = cmd.BoolOpt("all", false, "List all users, including disabled")
        )

        // Run this function when the command is invoked
        cmd.Action = func() {
            // Inside the action, and only inside, we can safely access the
            // values of the options and arguments
            fmt.Printf("user list (including disabled ones: %v)\n", *all)
        }
    })

    // Declare our second command, which is invocable with "uman get"
    app.Command("get", "get a user details", func(cmd *cli.Cmd) {
        var (
            detailed = cmd.BoolOpt("detailed", false, "Display detailed info")
            id       = cmd.StringArg("ID", "", "The user id to display")
        )

        cmd.Action = func() {
            fmt.Printf("user %q details (detailed mode: %v)\n", *id, *detailed)
        }
    })

    // With the app configured, execute it, passing in the os.Args array
    app.Run(os.Args)
}

A Larger Multi-Command Example

This example shows an alternate way of organizing our code when dealing with a larger number of commands and subcommands. This layout emphasizes the command structure and defers the details of each command to subsequent functions. Like the prior examples, options and arguments are still scoped to their respective functions and don't pollute the global namespace.

package main

import (
    "fmt"
    "os"

    "github.com/jawher/mow.cli"
)

// Global options available to any of the commands
var filename *string

func main() {
    app := cli.App("vault", "Password Keeper")

    // Define our top-level global options
    filename = app.StringOpt("f file", os.Getenv("HOME")+"/.safe", "Path to safe")

    // Define our command structure for usage like this:
    app.Command("list", "list accounts", cmdList)
    app.Command("creds", "display account credentials", cmdCreds)
    app.Command("config", "manage accounts", func(config *cli.Cmd) {
        config.Command("list", "list accounts", cmdList)
        config.Command("add", "add an account", cmdAdd)
        config.Command("remove", "remove an account(s)", cmdRemove)
    })

    app.Run(os.Args)
}

// Sample use: vault list OR vault config list
func cmdList(cmd *cli.Cmd) {
    cmd.Action = func() {
        fmt.Printf("list the contents of the safe here")
    }
}

// Sample use: vault creds reddit.com
func cmdCreds(cmd *cli.Cmd) {
    cmd.Spec = "ACCOUNT"
    account := cmd.StringArg("ACCOUNT", "", "Name of account")
    cmd.Action = func() {
        fmt.Printf("display account info for %s\n", *account)
    }
}

// Sample use: vault config add reddit.com -u username -p password
func cmdAdd(cmd *cli.Cmd) {
    cmd.Spec = "ACCOUNT [ -u=<username> ] [ -p=<password> ]"
    var (
        account  = cmd.StringArg("ACCOUNT", "", "Account name")
        username = cmd.StringOpt("u username", "admin", "Account username")
        password = cmd.StringOpt("p password", "admin", "Account password")
    )
    cmd.Action = func() {
        fmt.Printf("Adding account %s:%s@%s", *username, *password, *account)
    }
}

// Sample use: vault config remove reddit.com twitter.com
func cmdRemove(cmd *cli.Cmd) {
    cmd.Spec = "ACCOUNT..."
    var (
        accounts = cmd.StringsArg("ACCOUNT", nil, "Account names to remove")
    )
    cmd.Action = func() {
        fmt.Printf("Deleting accounts: %v", *accounts)
    }
}

Comparison to Other Tools

There are several tools in the Go ecosystem to facilitate the creation of command line tools. The following is a comparison to the built-in flag package as well as the popular urfave/cli (formerly known as codegangsta/cli):

mow.cli urfave/cli flag
Contextual help
Commands
Option folding -xyz
Option value folding -fValue
Option exclusion --start ❘ --stop
Option dependency [-a -b] or [-a [-b]]
Arguments validation SRC DST
Argument optionality SRC [DST]
Argument repetition SRC... DST
Option/argument dependency SRC [-f DST]
Any combination of the above [-d ❘ --rm] IMAGE [COMMAND [ARG...]]

Unlike the simple packages above, docopt is another library that supports rich set of flag and argument validation. It does, however, fall short for many use cases including:

mow.cli docopt
Contextual help
Backtracking SRC... DST
Backtracking [SRC] DST
Branching (SRC ❘ -f DST)

Installation

To install this package, run the following:

go get github.com/jawher/mow.cli

Package Documentation

Package cli provides a framework to build command line applications in Go with most of the burden of arguments parsing and validation placed on the framework instead of the user.

Basics

To create a new application, initialize an app with cli.App. Specify a name and a brief description for the application:

cp := cli.App("cp", "Copy files around")

To attach code to execute when the app is launched, assign a function to the Action field:

cp.Action = func() {
    fmt.Printf("Hello world\n")
}

To assign a version to the application, use Version method and specify the flags that will be used to invoke the version command:

cp.Version("v version", "cp 1.2.3")

Finally, in the main func, call Run passing in the arguments for parsing:

cp.Run(os.Args)

Options

To add one or more command line options (also known as flags), use one of the short-form StringOpt, StringsOpt, IntOpt, IntsOpt, Float64Opt, Floats64Opt, or BoolOpt methods on App (or Cmd if adding flags to a command or a subcommand). For example, to add a boolean flag to the cp command that specifies recursive mode, use the following:

recursive := cp.BoolOpt("R recursive", false, "recursively copy the src to dst")

or:

cp.BoolOptPtr(&cfg.recursive, "R recursive", false, "recursively copy the src to dst")

The first version returns a new pointer to a bool value which will be populated when the app is run, whereas the second version will populate a pointer to an existing variable you specify.

The option name(s) is a space separated list of names (without the dashes). The one letter names can then be called with a single dash (short option, -R), the others with two dashes (long options, --recursive).

You also specify the default value for the option if it is not supplied by the user.

The last parameter is the description to be shown in help messages.

There is also a second set of methods on App called String, Strings, Int, Ints, and Bool, which accept a long-form struct of the type: cli.StringOpt, cli.StringsOpt, cli.IntOpt, cli.IntsOpt, cli.Float64Opt, cli.Floats64Opt, cli.BoolOpt. The struct describes the option and allows the use of additional features not available in the short-form methods described above:

recursive = cp.Bool(cli.BoolOpt{
    Name:       "R recursive",
    Value:      false,
    Desc:       "copy src files recursively",
    EnvVar:     "VAR_RECURSIVE",
    SetByUser:  &recursiveSetByUser,
})

Or:

recursive = cp.BoolPtr(&recursive, cli.BoolOpt{
    Name:       "R recursive",
    Value:      false,
    Desc:       "copy src files recursively",
    EnvVar:     "VAR_RECURSIVE",
    SetByUser:  &recursiveSetByUser,
})

The first version returns a new pointer to a value which will be populated when the app is run, whereas the second version will populate a pointer to an existing variable you specify.

Two features, EnvVar and SetByUser, can be defined in the long-form struct method. EnvVar is a space separated list of environment variables used to initialize the option if a value is not provided by the user. When help messages are shown, the value of any environment variables will be displayed. SetByUser is a pointer to a boolean variable that is set to true if the user specified the value on the command line. This can be useful to determine if the value of the option was explicitly set by the user or set via the default value.

You can only access the values stored in the pointers in the Action func, which is invoked after argument parsing has been completed. This precludes using the value of one option as the default value of another.

On the command line, the following syntaxes are supported when specifying options.

Boolean options:

-f         single dash one letter name
-f=false   single dash one letter name, equal sign followed by true or false
--force    double dash for longer option names
-it        single dash for multiple one letter names (option folding), this is equivalent to: -i -t

String, int and float options:

``` -e=value single dash one letter name, equal sign, followed by the value -e value single dash one letter name, space followed by the value -Ivalue single dash one letter name, immediately followed by the value --extra=

Extension points exported contracts — how you extend this code

Core symbols most depended-on inside this repo

Shape

Function 215
Method 186
Struct 40
TypeAlias 18
Interface 12
FuncType 1

Languages

Go100%

Modules by API surface

cli_test.go56 symbols
commands.go49 symbols
internal/values/values.go43 symbols
commands_test.go43 symbols
options.go33 symbols
args.go33 symbols
internal/fsm/fsmtest/fsmtest.go25 symbols
internal/fsm/fsm.go18 symbols
var_test.go15 symbols
internal/parser/parser.go14 symbols
internal/lexer/lexer.go13 symbols
cli.go10 symbols

Used by 2 indexed graphs manifest dependencies, hub-wide

For agents

$ claude mcp add mow.cli \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact

Ask about this repo answers extend the page