CLI - A package for building command line app with go

Command line interface

License Travis branch Coverage Status Go Report Card GoDoc

Screenshot

screenshot2

Key features

  • Lightweight and easy to use.
  • Defines flag by tag, e.g. flag name(short or/and long), description, default value, password, prompt and so on.
  • Type safety.
  • Output looks very nice.
  • Supports custom Validator.
  • Supports slice and map as a flag.
  • Supports any type as a flag field which implements cli.Decoder interface.
  • Supports any type as a flag field which uses FlagParser.
  • Suggestions for command.(e.g. hl => help, "veron" => "version").
  • Supports default value for flag, even expression about env variable(e.g. dft:"$HOME/dev").
  • Supports editor like git commit command.(See example 21 and 22)

API documentation

See godoc

Examples

Example 1: Hello

back to examples

// main.go
// This is a HelloWorld-like example

package main

import (
	"os"

	"github.com/mkideal/cli"
)

type argT struct {
	Name string `cli:"name" usage:"tell me your name"`
}

func main() {
	os.Exit(cli.Run(new(argT), func(ctx *cli.Context) error {
		argv := ctx.Argv().(*argT)
		ctx.String("Hello, %s!\n", argv.Name)
		return nil
	}))
}
$ go build -o hello
$ ./hello --name Clipher
Hello, Clipher!

Example 2: Flag

back to examples

// main.go
// This example show basic usage of flag

package main

import (
	"os"

	"github.com/mkideal/cli"
)

type argT struct {
	cli.Helper
	Port int  `cli:"p,port" usage:"short and long format flags both are supported"`
	X    bool `cli:"x" usage:"boolean type"`
	Y    bool `cli:"y" usage:"boolean type, too"`
}

func main() {
	os.Exit(cli.Run(new(argT), func(ctx *cli.Context) error {
		argv := ctx.Argv().(*argT)
		ctx.String("port=%d, x=%v, y=%v\n", argv.Port, argv.X, argv.Y)
		return nil
	}))
}
$ go build -o app
$ ./app -h
Options:

  -h, --help     display help information
  -p, --port     short and long format flags both are supported
  -x             boolean type
  -y             boolean type, too
$ ./app -p=8080 -x
port=8080, x=true, y=false
$ ./app -p 8080 -x=true
port=8080, x=true, y=false
$ ./app -p8080 -y true
port=8080, x=false, y=true
$ ./app --port=8080 -xy
port=8080, x=true, y=true
$ ./app --port 8080 -yx
port=8080, x=true, y=true

Example 3: Required flag

back to examples

// main.go
// This example show how to use required flag

package main

import (
	"os"

	"github.com/mkideal/cli"
)

type argT struct {
	cli.Helper
	Id uint8 `cli:"*id" usage:"this is a required flag, note the *"`
}

func main() {
	os.Exit(cli.Run(new(argT), func(ctx *cli.Context) error {
		argv := ctx.Argv().(*argT)
		ctx.String("%d\n", argv.Id)
		return nil
	}))
}
$ go build -o app
$ ./app
ERR! required argument --id missing
$ ./app --id=2
2

Example 4: Default flag

back to examples

// main.go
// This example show how to use default flag

package main

import (
	"os"

	"github.com/mkideal/cli"
)

type argT struct {
	cli.Helper
	Basic  int    `cli:"basic" usage:"basic usage of default" dft:"2"`
	Env    string `cli:"env" usage:"env variable as default" dft:"$HOME"`
	Expr   int    `cli:"expr" usage:"expression as default" dft:"$BASE_PORT+1000"`
	DevDir string `cli:"devdir" usage:"directory of developer" dft:"$HOME/dev"`
}

func main() {
	os.Exit(cli.Run(new(argT), func(ctx *cli.Context) error {
		argv := ctx.Argv().(*argT)
		ctx.String("%d, %s, %d, %s\n", argv.Basic, argv.Env, argv.Expr, argv.DevDir)
		return nil
	}))
}
$ go build -o app
$ ./app -h
Options:

  -h, --help                       display help information
      --basic[=2]                  basic usage of default
      --env[=$HOME]                env variable as default
      --expr[=$BASE_PORT+1000]     expression as default
      --devdir[=$HOME/dev]         directory of developer
$ ./app
2, /Users/wang, 1000, /Users/wang/dev
$ BASE_PORT=8000 ./app --basic=3
3, /Users/wang, 9000, /Users/wang/dev

Example 5: Slice

back to examples

// main.go
// This example show how to use slice as a flag

package main

import (
	"os"

	"github.com/mkideal/cli"
)

type argT struct {
	// []bool, []int, []float32, ... supported too.
	Friends []string `cli:"F" usage:"my friends"`
}

func main() {
	os.Exit(cli.Run(new(argT), func(ctx *cli.Context) error {
		ctx.JSONln(ctx.Argv())
		return nil
	}))
}
$ go build -o app
$ ./app
{"Friends":null}
$ ./app -FAlice -FBob -F Charlie
{"Friends":["Alice","Bob","Charlie"]}

Example 6: Map

back to examples

// main.go
// This example show how to use map as a flag

package main

import (
	"os"

	"github.com/mkideal/cli"
)

type argT struct {
	Macros map[string]int `cli:"D" usage:"define macros"`
}

func main() {
	os.Exit(cli.Run(new(argT), func(ctx *cli.Context) error {
		ctx.JSONln(ctx.Argv())
		return nil
	}))
}
$ go build -o app
$ ./app
{"Macros":null}
$ ./app -Dx=not-a-number
ERR! `not-a-number` couldn't converted to an int value
$ ./app -Dx=1 -D y=2
{"Macros":{"x":1,"y":2}}

Example 7: Force flag

back to examples

// main.go
// This example show usage of force flag
// Force flag has prefix !, and must be a boolean.
// Will prevent validating flags if some force flag assigned true

package main

import (
	"os"

	"github.com/mkideal/cli"
)

type argT struct {
	Version  bool `cli:"!v" usage:"force flag, note the !"`
	Required int  `cli:"*r" usage:"required flag"`
}

func main() {
	os.Exit(cli.Run(new(argT), func(ctx *cli.Context) error {
		argv := ctx.Argv().(*argT)
		if argv.Version {
			ctx.String("v0.0.1\n")
		}
		return nil
	}))
}
$ go build -o app
$ ./app
ERR! required argument -r missing

# -v is a force flag, and assigned true, so `ERR` disappear.
$ ./app -v
v0.0.1

Example 8: Child command

back to examples

// main.go
// This example demonstrates usage of child command

package main

import (
	"fmt"
	"os"

	"github.com/mkideal/cli"
)

func main() {
	if err := cli.Root(root,
		cli.Tree(help),
		cli.Tree(child),
	).Run(os.Args[1:]); err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
}

var help = cli.HelpCommand("display help information")

// root command
type rootT struct {
	cli.Helper
	Name string `cli:"name" usage:"your name"`
}

var root = &cli.Command{
	Desc: "this is root command",
	// Argv is a factory function of argument object
	// ctx.Argv() is if Command.Argv == nil or Command.Argv() is nil
	Argv: func() interface{} { return new(rootT) },
	Fn: func(ctx *cli.Context) error {
		argv := ctx.Argv().(*rootT)
		ctx.String("Hello, root command, I am %s\n", argv.Name)
		return nil
	},
}

// child command
type childT struct {
	cli.Helper
	Name string `cli:"name" usage:"your name"`
}

var child = &cli.Command{
	Name: "child",
	Desc: "this is a child command",
	Argv: func() interface{} { return new(childT) },
	Fn: func(ctx *cli.Context) error {
		argv := ctx.Argv().(*childT)
		ctx.String("Hello, child command, I am %s\n", argv.Name)
		return nil
	},
}
$ go build -o app

# help for root
# equivalent to "./app -h"
$ ./app help
this is root command

Options:

  -h, --help     display help information
      --name     your name

Commands:
  help    display help information
  child   this is a child command

# help for specific command
# equivalent to "./app child -h"
$ ./app help child
this is a child command

Options:

  -h, --help     display help information
      --name     your name

# execute root command
$ ./app --name 123
Hello, root command, I am 123

# execute child command
$ ./app child --name=123
Hello, child command, I am 123

# something wrong, but got a suggestion.
$ ./app chd
ERR! command chd not found
Did you mean child?

Example 9: Auto help

back to examples

// main.go
// This example demonstrates cli.AutoHelper

package main

import (
	"os"

	"github.com/mkideal/cli"
)

type argT struct {
	Help bool `cli:"h,help" usage:"show help"`
}

// AutoHelp implements cli.AutoHelper interface
// NOTE: cli.Helper is a predefined type which implements cli.AutoHelper
func (argv *argT) AutoHelp() bool {
	return argv.Help
}

func main() {
	os.Exit(cli.Run(new(argT), func(ctx *cli.Context) error {
		return nil
	}))
}
$ go build -o app
$ ./app -h
Options:

  -h, --help     show help

Try comment AutoHelp method and rerun it.

Example 10: Usage of Validator

back to examples

// main.go
// This example demonstrates how to utilize Validator

package main

import (
	"fmt"
	"os"

	"github.com/mkideal/cli"
)

type argT struct {
	cli.Helper
	Age    int    `cli:"age" usage:"your age"`
	Gender string `cli:"g,gender" usage:"your gender" dft:"male"`
}

// Validate implements cli.Validator interface
func (argv *argT) Validate(ctx *cli.Context) error {
	if argv.Age < 0 || argv.Age > 300 {
		return fmt.Errorf("age %d out of range", argv.Age)
	}
	if argv.Gender != "male" && argv.Gender != "female" {
		return fmt.Errorf("invalid gender %s", ctx.Color().Yellow(argv.Gender))
	}
	return nil
}

func main() {
	os.Exit(cli.Run(new(argT), func(ctx *cli.Context) error {
		ctx.JSONln(ctx.Argv())
		return nil
	}))
}
$ go build -o app
$ ./app --age=-1
ERR! age -1 out of range
$ ./app --age=1000
ERR! age 1000 out of range
$ ./app -g balabala
ERR! invalid gender balabala
$ ./app --age 88 --gender female
{"Help":false,"Age":88,"Gender":"female"}

Example 11: Prompt and Password

back to examples

// main.go
// This example introduce prompt and pw tag

package main

import (
	"os"

	"github.com/mkideal/cli"
)

type argT struct {
	cli.Helper
	Username string `cli:"u,username" usage:"github account" prompt:"type github account"`
	Password string `pw:"p,password" usage:"password of github account" prompt:"type the password"`
}

func main() {
	os.Exit(cli.Run(new(argT), func(ctx *cli.Context) error {
		argv := ctx.Argv().(*argT)
		ctx.String("username=%s, password=%s\n", argv.Username, argv.Password)
		return nil
	}))
}
$ go build -o app
$ ./app
type github account: hahaha # visible
type the password: # invisible because of `pw` tag
username=hahaha, password=123456

Example 12: Decoder

back to examples

// main.go
// This example show how to use decoder

package main

import (
	"os"
	"strings"

	"github.com/mkideal/cli"
)

type exampleDecoder struct {
	list []string
}

// Decode implements cli.Decoder interface
func (d *exampleDecoder) Decode(s string) error {
	d.list = strings.Split(s, ",")
	return nil
}

type argT struct {
	Example exampleDecoder `cli:"d" usage:"example decoder"`
}

func main() {
	os.Exit(cli.Run(new(argT), func(ctx *cli.Context) error {
		argv := ctx.Argv().(*argT)
		ctx.JSONln(argv.Example.list)
		return nil
	}))
}
$ go build -o app
$ ./app -d a,b,c
["a","b","c"]

Example 13: Pid file

back to examples

// main.go
// This example show how to use builtin Decoder: PidFile

package main

import (
	"os"

	"github.com/mkideal/cli"
	clix "github.com/mkideal/cli/ext"
)

type argT struct {
	cli.Helper
	PidFile clix.PidFile `cli:"pid" usage:"pid file" dft:"013-pidfile.pid"`
}

func main() {
	os.Exit(cli.Run(new(argT), func(ctx *cli.Context) error {
		argv := ctx.Argv().(*argT)

		if err := argv.PidFile.New(); err != nil {
			return err
		}
		defer argv.PidFile.Remove()

		return nil
	}))
}

Example 14: Time and Duration

back to examples

// main.go
// This example show how to use builtin Decoder: Time and Duration

package main

import (
	"os"

	"github.com/mkideal/cli"
	clix "github.com/mkideal/cli/ext"
)

type argT struct {
	Time     clix.Time     `cli:"t" usage:"time"`
	Duration clix.Duration `cli:"d" usage:"duration"`
}

func main() {
	os.Exit(cli.Run(new(argT), func(ctx *cli.Context) error {
		argv := ctx.Argv().(*argT)
		ctx.String("time=%v, duration=%v\n", argv.Time, argv.Duration)
		return nil
	}))
}
$ go build -o app
$ ./app -t '2016-1-2 3:5' -d=10ms
time=2016-01-02 03:05:00 +0800 CST, duration=10ms

Example 15: File

back to examples

// main.go
// This example show how to use builtin Decoder: File

package main

import (
	"os"

	"github.com/mkideal/cli"
	clix "github.com/mkideal/cli/ext"
)

type argT struct {
	Content clix.File `cli:"f,file" usage:"read content from file or stdin"`
}

func main() {
	os.Exit(cli.Run(new(argT), func(ctx *cli.Context) error {
		argv := ctx.Argv().(*argT)
		ctx.String(argv.Content.String())
		return nil
	}))
}
$ go build -o app
# read from stdin
$ echo hello | ./app -f
hello
# read from file
$ echo hello > test.txt && ./app -f test.txt
hello
$ rm test.txt

Example 16: Parser

back to examples

// main.go
// This example introduce Parser
// `Parser` is another way to use custom type of data.
// Unlike `Decoder`, `Parser` used to parse string according to specific rule,
// like json,yaml and so on.
//
// Builtin parsers:
// * json
// * jsonfile

package main

import (
	"os"

	"github.com/mkideal/cli"
)

type config struct {
	A string
	B int
	C bool
}

type argT struct {
	JSON config `cli:"c,config" usage:"parse json string" parser:"json"`
}

func main() {
	os.Exit(cli.Run(new(argT), func(ctx *cli.Context) error {
		argv := ctx.Argv().(*argT)
		ctx.JSONIndentln(argv.JSON, "", "    ")
		return nil
	}))
}
$ go build -o app
$ ./app
{
    "A": "",
    "B": 0,
    "C": false
}
$ ./app -c '{"A": "hello", "b": 22, "C": true}'
{
    "A": "hello",
    "B": 22,
    "C": true
}

Example 17: JSON file

back to examples

// main.go
// This example show how to use builtin parser: jsonfile
// It's similar to json, but read string from file.

package main

import (
	"os"

	"github.com/mkideal/cli"
)

type config struct {
	A string
	B int
	C bool
}

type argT struct {
	JSON config `cli:"c,config" usage:"parse json from file" parser:"jsonfile"`
}

func main() {
	os.Exit(cli.Run(new(argT), func(ctx *cli.Context) error {
		argv := ctx.Argv().(*argT)
		ctx.JSONIndentln(argv.JSON, "", "    ")
		return nil
	}))
}
$ go build -o app
$ echo '{"A": "hello", "b": 22, "C": true}' > test.json
$ ./app -c test.json
{
    "A": "hello",
    "B": 22,
    "C": true
}
$ rm test.json

Example 18: Custom parser

back to examples

// main.go
// This example demonstrates how to use custom parser

package main

import (
	"os"
	"reflect"

	"github.com/mkideal/cli"
)

type myParser struct {
	ptr interface{}
}

func newMyParser(ptr interface{}) cli.FlagParser {
	return &myParser{ptr}
}

// Parse implements FlagParser.Parse interface
func (parser *myParser) Parse(s string) error {
	typ := reflect.TypeOf(parser.ptr)
	val := reflect.ValueOf(parser.ptr)
	if typ.Kind() == reflect.Ptr {
		kind := reflect.Indirect(val).Type().Kind()
		if kind == reflect.Struct {
			typElem, valElem := typ.Elem(), val.Elem()
			numField := valElem.NumField()
			for i := 0; i < numField; i++ {
				_, valField := typElem.Field(i), valElem.Field(i)
				if valField.Kind() == reflect.Int &&
					valField.CanSet() {
					valField.SetInt(2)
				}
				if valField.Kind() == reflect.String &&
					valField.CanSet() {
					valField.SetString("B")
				}
			}
		}
	}
	return nil
}

type config struct {
	A int
	B string
}

type argT struct {
	Cfg config `cli:"cfg" parser:"myparser"`
}

func main() {
	// register parser factory function
	cli.RegisterFlagParser("myparser", newMyParser)

	os.Exit(cli.Run(new(argT), func(ctx *cli.Context) error {
		argv := ctx.Argv().(*argT)
		ctx.String("%v\n", argv.Cfg)
		return nil
	}))
}
$ go build -o app
$ ./app
{0 }
$ ./app --cfg xxx
{2 B}

Example 19: Hooks

back to examples

// main.go
// This example demonstrates how to use hooks

package main

import (
	"fmt"
	"os"

	"github.com/mkideal/cli"
)

func main() {
	if err := cli.Root(root,
		cli.Tree(child1),
		cli.Tree(child2),
	).Run(os.Args[1:]); err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
}

type argT struct {
	Error bool `cli:"e" usage:"return error"`
}

var root = &cli.Command{
	Name: "app",
	Argv: func() interface{} { return new(argT) },
	OnRootBefore: func(ctx *cli.Context) error {
		ctx.String("OnRootBefore invoked\n")
		return nil
	},
	OnRootAfter: func(ctx *cli.Context) error {
		ctx.String("OnRootAfter invoked\n")
		return nil
	},
	Fn: func(ctx *cli.Context) error {
		ctx.String("exec root command\n")
		argv := ctx.Argv().(*argT)
		if argv.Error {
			return fmt.Errorf("root command returns error")
		}
		return nil
	},
}

var child1 = &cli.Command{
	Name: "child1",
	Argv: func() interface{} { return new(argT) },
	OnBefore: func(ctx *cli.Context) error {
		ctx.String("child1's OnBefore invoked\n")
		return nil
	},
	OnAfter: func(ctx *cli.Context) error {
		ctx.String("child1's OnAfter invoked\n")
		return nil
	},
	Fn: func(ctx *cli.Context) error {
		ctx.String("exec child1 command\n")
		argv := ctx.Argv().(*argT)
		if argv.Error {
			return fmt.Errorf("child1 command returns error")
		}
		return nil
	},
}

var child2 = &cli.Command{
	Name:   "child2",
	NoHook: true,
	Fn: func(ctx *cli.Context) error {
		ctx.String("exec child2 command\n")
		return nil
	},
}
$ go build -o app
# OnRootBefore => Fn => OnRootAfter
$ ./app
OnRootBefore invoked
exec root command
OnRootAfter invoked
# OnBefore => OnRootBefore => Fn => OnRootAfter => OnAfter
$ ./app child1
child1 OnBefore invoked
OnRootBefore invoked
exec child1 command
OnRootAfter invoked
child1 OnAfter invoked
# No hooks
$ ./app child2
exec child2 command
# OnRootBefore => Fn --> Error
$ ./app -e
OnRootBefore invoked
exec root command
root command returns error
# OnBefore => OnRootBefore => Fn --> Error
$ ./app child1 -e
child1 OnBefore invoked
OnRootBefore invoked
exec child1 command
child1 command returns error

Example 20: Daemon

back to examples

// main.go
// This example demonstrates how to use `Daemon`

package main

import (
	"fmt"
	"os"
	"time"

	"github.com/mkideal/cli"
)

type argT struct {
	cli.Helper
	Wait  uint `cli:"wait" usage:"seconds for waiting" dft:"10"`
	Error bool `cli:"e" usage:"create an error"`
}

const successResponsePrefix = "start ok"

func main() {
	if err := cli.Root(root,
		cli.Tree(daemon),
	).Run(os.Args[1:]); err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
}

var root = &cli.Command{
	Argv: func() interface{} { return new(argT) },
	Fn: func(ctx *cli.Context) error {
		argv := ctx.Argv().(*argT)
		if argv.Error {
			err := fmt.Errorf("occurs error")
			cli.DaemonResponse(err.Error())
			return err
		}
		cli.DaemonResponse(successResponsePrefix)
		<-time.After(time.Duration(argv.Wait) * time.Second)
		return nil
	},
}

var daemon = &cli.Command{
	Name: "daemon",
	Argv: func() interface{} { return new(argT) },
	Fn: func(ctx *cli.Context) error {
		return cli.Daemon(ctx, successResponsePrefix)
	},
}
$ go build -o daemon-app
$ ./daemone-app daemon
start ok
# Within 10 seconds, you will see process "./daemon-app"
$ ps | grep daemon-app
11913 ttys002    0:00.01 ./daemon-app
11915 ttys002    0:00.00 grep daemon-app
# After 10 seconds
$ ps | grep daemon-app
11936 ttys002    0:00.00 grep daemon-app
# try again with an error
$ ./daemon-app daemon -e
occurs error
$ ps | grep daemon-app
11936 ttys002    0:00.00 grep daemon-app

Example 21: Editor

back to examples

// main.go
// This example demonstrates how to use `editor`. This similar to git commit

package main

import (
	"os"

	"github.com/mkideal/cli"
)

type argT struct {
	cli.Helper
	Msg string `edit:"m" usage:"message"`
}

func main() {
	os.Exit(cli.Run(new(argT), func(ctx *cli.Context) error {
		argv := ctx.Argv().(*argT)
		ctx.String("msg: %s", argv.Msg)
		return nil
	}))
}
$ go build -o app
$ ./app -m "hello, editor"
msg: hello, editor
$ ./app # Then, launch a editor(default is vim) and type `hello, editor`, quit the editor
msg: hello, editor

Example 22: Custom Editor

back to examples

// main.go
// This example demonstrates specific editor.

package main

import (
	"os"

	"github.com/mkideal/cli"
)

type argT struct {
	cli.Helper
	Msg string `edit:"m" usage:"message"`
}

func main() {
	cli.GetEditor = func() (string, error) {
		if editor := os.Getenv("EDITOR"); editor != "" {
			return editor, nil
		}
		return cli.DefaultEditor, nil
	}
	os.Exit(cli.Run(new(argT), func(ctx *cli.Context) error {
		argv := ctx.Argv().(*argT)
		ctx.String("msg: %s", argv.Msg)
		return nil
	}))
}
$ go build -o app
$ ./app -m "hello, editor"
msg: hello, editor
$ EDITOR=nano ./app # Then, launch nano and type `hello, editor`, quit the editor
msg: hello, editor
Owner
Comments
  • File options

    File options

    1. The clix.File allows to read content from file or stdin, does something equivalent exist for write to file or stdout as well?
    2. If there will be, for file options, better provide a function to expose the file-handle (io.Reader & io.Writer) for users to read/write files themselves.

    Thanks

  • Excessive spacing with defaults when in NormalStyle

    Excessive spacing with defaults when in NormalStyle

    Hope the help text can use double lines when using jsonfile in NormalStyle. Otherwise the option + default file will make the option section too wide and text section too narrow. Here is the contrast:

    # Without  jsonfile option
    Options:
    
      -h, --help       display help information
      -U, --rul        usage URL from ISP
          --css        css selection for usage text from usage rul
          --to         email address to send to
          --template   mail sending command template string
      -d, --days       days to shift from billing date
      -v, --verbose    Verbose mode (Multiple -v options increase the verbosity.)
    
    # With  jsonfile option
      -h, --help                        display help information
      -U, --rul                         usage URL from ISP
          --css                         css selection for usage text from usage rul
          --to                          email address to send to
          --template                    mail sending command template string
      -d, --days                        days to shift from billing date
      -v, --verbose                     Verbose mode (Multiple -v options increase the verbosity.)
      -c, --config[=cfg_default.json]   config file
    

    Too much space has been wasted in the second case.

    I know there is also a ManualStyle, but that will make the help text too long if there are a few options. So using the second line for the default value seems to be a good compromise.

     -c, --config            config file
        [=cfg_default.json]
    

    Please consider. Thx.

  • Show usage by default

    Show usage by default

    Hi,

    Can you add an option to show usage help by default, when no command line options are provided please?

    Many of my sub commands have complicated cli options (that's why I need cli, hehe), so I'd need to have a way to show usage help when no command line options are provided.

    • The case when none-option parameters are needed can be dealt with NumArg, however
    • The case when all parameters are provided as cli options, including some mandatory (required) ones, this is the case I need to show usage help when no command line options are provided as well, even without specifying that -h.

    This is not the same as when there is some error (required argument missing) happens then show usage help, because it is all about friendliness to me. I.e., as long as friendliness is concerned, here is my view of the order going from bad to better:

    1. Complains about required argument missing
    2. Complains about required argument missing but then show usage
    3. Show usage up front, when no command line options are provided. If users have given anything else on the command line, then complains about missing arguments etc

    What do you think?

  • Need to loosen the tight control of the config name

    Need to loosen the tight control of the config name

    With the introduction of $__EXEC_FILENAME the config can go with the executable now. However, now the problem is that the config MUST be named precisely according to the executable -- if people want to name it to something else, e.g., like this, things will be broken again.

    I think it is time to loosen the tight control of how the config should be named, and give the freedom back to the user. I.e., even when people name their config file to whatever they like, the self-config file should still work.

    Please consider.

    PS. for my daily job, I have 3 or 4 different tools doing different things, but they all rely on a central config file. I put the exes together and they all use the same config file. I know this might be a rare case, but I do consider it a very valid one.

    Please reconsider -- people need the freedom, not the unnecessary restriction. Thx.

  • NeedArgs clarification

    NeedArgs clarification

    Hey 仕晋,

    Does NeedArgs means need argument or need option?

    Please see my demo how to handle none-option parameters, in which I added a none-option parameters to build:

    Build golang application
    
    Usage:
      gogo build [Options] Arch(i386|amd64)
    
    

    It runs fine:

    $ gogo build --dir=.. -o out i386
    build: {
        "Help": false,
        "Dir": "..",
        "Suffix": ".go,.c,.s",
        "Out": "out"
    }
    Arch: i386
    
    

    However, my code would panic if the Arch none-option parameter is not specified. So we need clarification here. Once again, rsync takes an awful lot of options, but besides them, at the end of command line, there are none-option parameters, normally called argument. I hope that NeedArgs really means this, which goes with common understanding and all other commands.

    Thanks

  • Handle positional arguments when a command doesn't have subcommands, and the user doesn't provide flags

    Handle positional arguments when a command doesn't have subcommands, and the user doesn't provide flags

    I am not sure, but is it possible to give a command positional arguments without adding flags? I have a couple of commands in an application I was trying to move to this framework, and I ran into some problems.

    In this simple example, I just try to print positional arguments:

    package main
    
    import (
    	"fmt"
    	"os"
    
    	"github.com/mkideal/cli"
    )
    
    type argMain struct {
    	cli.Helper
    	Why bool `cli:"why"`
    }
    
    func main() {
    	var main = &cli.Command{
    		Name: "test",
    		Argv: func() interface{} { return new(argMain) },
    		Fn: func(ctx *cli.Context) error {
    			ctx.String("args: '%+v'\n", ctx.Args())
    			return nil
    		},
    	}
    
    	if err := main.Run(os.Args[1:]); err != nil {
    		fmt.Fprintln(os.Stderr, err)
    		os.Exit(1)
    	}
    }
    

    If I compile and run the tool I get an error, indicating that the framework thinks that positional arguments are subcommands

    ⟩ go build -o test
    ⟩ ./test xxx yyy zzz
    ERR! command xxx yyy zzz not found
    

    When I look at Command.prepare(), I can see why:

    func (cmd *Command) prepare(clr color.Color, args []string, writer io.Writer, resp http.ResponseWriter, httpMethods ...string) (ctx *Context, suggestion string, err error) {
    	// split args
    	router := []string{}
    	for _, arg := range args {
    		if strings.HasPrefix(arg, dashOne) {
    			break
    		}
    		router = append(router, arg)
    	}
    	path := strings.Join(router, " ")
    	child, end := cmd.SubRoute(router)
    
           ...
    }
    

    The first part of parsing tries to build a path to a command, and it doesn't stop until it sees a dash. And here, there isn't any dashes. So, prepare() concludes that I am trying to invoke a command that isn't registered. It's right on the second part, not so much on the first.

    I can get positional arguments, though. I just need a flag first. If you were wondering what the Why argument did in the code above, this should demonstrate it:

    ⟩ ./test --why xxx yyy zzz
    args: '[xxx yyy zzz]'
    

    The parsing stops when it sees --why, so the arguments after the flag are parsed to the command just fine. The test in context_test.go also has a flag in front of free arguments, which is why it passes. It would fail without it, as far as I can see. But it seems a bit silly to have a flag just for that...

     I tried modifying the prepare() function so it checks if the child it finds while routing actually does have subcommands, and if not, it doesn't report an error. The tests in the package runs, plus a new that checks for positional arguments, but of course I don't know if there are other cases where the change might break something.

  • This project is now dormant, but there is a new forked repo

    This project is now dormant, but there is a new forked repo

    Hi @mkideal/@wangshijin,

    Have you been looking at the issues / PRs lately?

    I'm willing to step forward to take the small maintaining chores over, would you add me to the project co-maintainer please?

    Otherwise, I'm afraid we have to fork the project, because it's been un-maintained for a while. Thx.

  • Preserve orders for map types

    Preserve orders for map types

    The go map is known to not preserver map orders. I.e., if we run the following sample code a couple of times,

    $ go run 006-map.go -Dx=1 -D y=2
    {"Macros":{"x":1,"y":2}}
    
    

    The output might not always be the same. Is it possible to obtain/preserver output orders to that given on command line? I know the current data-structure Macros map[string]int need to be replaced by some cli specific types, i.e., API changes. So,

    Just asking. Thanks.

  • Extra dependency for Time and Duration

    Extra dependency for Time and Duration

    The example-14-time-and-duration reveals that for a normal Time and Duration operation, an extra package is required, github.com/jinzhu/now.

    Since Time and Duration operations are such normal operations, I'm wondering why such extra package is necessary.

    Excellent package, BTW.

    Thanks

  • Add support for url.URL argument type

    Add support for url.URL argument type

    Urls are pretty common args to go apps and it makes sense to support url validation early on. The alternative is to rely on rather ugly boilerplate in all apps in need of that feature.

    The example of ugly boilerplate that better be hidden inside the lib:

    type urlValue struct {
    	u *url.URL
    }
    
    func (uv *urlValue) Parse(s string) error {
    	u, err := url.Parse(s)
    	*uv.u = *u
    	return err
    }
    
    cli.RegisterFlagParser("urlParser", func(ptr interface{}) cli.FlagParser {
    	return &urlValue{
    		u: ptr.(*url.URL),
    	}
    })
    
  • Something went wrong with dep

    Something went wrong with dep

    1. Create simple main.go
    package main
    
    import (
    	"github.com/mkideal/cli"
    )
    
    func main() {
    	cli.HelpCommand("aaa")
    }
    
    1. Run dep init
    2. Run go build Result:
    $: go build
    # github.com/alxmsl/temp/vendor/github.com/mkideal/cli
    vendor/github.com/mkideal/cli/flag.go:66: cannot convert v (type expr.Value) to type int64
    vendor/github.com/mkideal/cli/flag.go:68: cannot convert v (type expr.Value) to type float64
    
  • optional arguments

    optional arguments

    The GNU getopt supports optional arguments:

    A long option normally begins with '--' followed by the long option name. If the option has a required argument, it may be written directly after the long option name, separated by '=', or as the next argument (i.e. separated by whitespace on the command line). If the option has an optional argument, it must be written directly after the long option name, separated by '=', if present.

    Would cli support optional arguments as well? If true, please make it clear of the following three stages:

    1. not specified
    2. specified but no argument
    3. specified and with argument

    Thanks

  • bash completion

    bash completion

    Need bash completion for command, even for flags. For example, app has a command hello, app hello

    And hello has flags --aha and --oho.

    Now, I want complete to app hello when typing app he in terminal and complete to app hello --aha when typing app hello --a

Related tags
A simple, fast, and fun package for building command line apps in Go

cli cli is a simple, fast, and fun package for building command line apps in Go. The goal is to enable developers to write fast and distributable comm

Dec 31, 2022
Watcher - A simple command line app to watch files in a directory for changes and run a command when files change!

Watcher - Develop your programs easily Watcher watches all the files present in the directory it is run from of the directory that is specified while

Mar 27, 2022
An open-source GitLab command line tool bringing GitLab's cool features to your command line
An open-source GitLab command line tool bringing GitLab's cool features to your command line

GLab is an open source GitLab CLI tool bringing GitLab to your terminal next to where you are already working with git and your code without switching

Dec 30, 2022
A command line tool that builds and (re)starts your web application everytime you save a Go or template fileA command line tool that builds and (re)starts your web application everytime you save a Go or template file

# Fresh Fresh is a command line tool that builds and (re)starts your web application everytime you save a Go or template file. If the web framework yo

Nov 22, 2021
A command line tool to prompt for a value to be included in another command line.

readval is a command line tool which is designed for one specific purpose—to prompt for a value to be included in another command line. readval prints

Dec 22, 2021
Simple and complete API for building command line applications in Go

Simple and complete API for building command line applications in Go Module cli provides a simple, fast and complete API for building command line app

Nov 23, 2022
A Go library for building command line applications

gocmd A Go library for building command line applications. Features Advanced command line arguments handling Subcommand handling Short and long comman

Dec 21, 2022
Go package to make lightweight ASCII line graph ╭┈╯ in command line apps with no other dependencies.
Go package to make lightweight ASCII line graph ╭┈╯ in command line apps with no other dependencies.

asciigraph Go package to make lightweight ASCII line graphs ╭┈╯. Installation go get github.com/guptarohit/asciigraph Usage Basic graph package main

Jan 8, 2023
git-xargs is a command-line tool (CLI) for making updates across multiple Github repositories with a single command.
git-xargs is a command-line tool (CLI) for making updates across multiple Github repositories with a single command.

Table of contents Introduction Reference Contributing Introduction Overview git-xargs is a command-line tool (CLI) for making updates across multiple

Dec 31, 2022
git-xargs is a command-line tool (CLI) for making updates across multiple GitHub repositories with a single command
git-xargs is a command-line tool (CLI) for making updates across multiple GitHub repositories with a single command

git-xargs is a command-line tool (CLI) for making updates across multiple GitHub repositories with a single command. You give git-xargs:

Feb 5, 2022
Package command provide simple API to create modern command-line interface

Package command Package command provide simple API to create modern command-line interface, mainly for lightweight usage, inspired by cobra Usage pack

Jan 16, 2022
Go-file-downloader-ftctl - A file downloader cli built using golang. Makes use of cobra for building the cli and go concurrent feature to download files.

ftctl This is a file downloader cli written in Golang which uses the concurrent feature of go to download files. The cli is built using cobra. How to

Jan 2, 2022
CLI and web app to convert HTML markup to go-app.dev's syntax.
CLI and web app to convert HTML markup to go-app.dev's syntax.

HTML to go-app Converter CLI and web app to convert HTML markup to go-app.dev's syntax. Installation CLI Static binaries are also available on GitHub

Dec 18, 2022
Go-cent-app - CENT.APP GO Package

CENT.APP - GO Package Official documentation - https://cent.app/en/merchant/api

Dec 20, 2022
A command line tool for simplified docker volume command built with go

dockervol A command line tool for simplified docker volume command built with go. Features: Remove anonymous volume (beta) Remove volume by matching n

Dec 18, 2021
Golang library with POSIX-compliant command-line UI (CLI) and Hierarchical-configuration. Better substitute for stdlib flag.
Golang library with POSIX-compliant command-line UI (CLI) and Hierarchical-configuration. Better substitute for stdlib flag.

cmdr cmdr is a POSIX-compliant, command-line UI (CLI) library in Golang. It is a getopt-like parser of command-line options, be compatible with the ge

Oct 28, 2022
FireFly Command Line Interface (CLI)
FireFly Command Line Interface (CLI)

FireFly CLI The FireFly CLI can be used to create a local FireFly stacks for offline development of blockchain apps. This allows developers to rapidly

Mar 1, 2022
FireFly Command Line Interface (CLI)
FireFly Command Line Interface (CLI)

FireFly CLI The FireFly CLI can be used to create a local FireFly stacks for offline development of blockchain apps. This allows developers to rapidly

Dec 13, 2022