Skip to content

Latest commit

 

History

History
127 lines (96 loc) · 3.14 KB

GUIDE.md

File metadata and controls

127 lines (96 loc) · 3.14 KB

Guide for acmd

Default command

Sometimes the app should work without any commands (ex ./app), acmd doesn't have a feature for that, if command is not passed help will be used.

But if you really want to have default command for the app you can do:

func main() {
	cmds := []acmd.Command{...}

	if len(os.Args) <= 1 { // no command is passed?
	    os.Args = []string{"", cmds[0].Name} // change the app args
	}

	// just create acmd.Runner as usual
}

Note: this solution doesn't work with flags, consider to create a command even if you have just 1.

Flag

Example with command like that ./dummy server ./openapi.yml -port=8080 from dummy

func run() error {
	cmds := []acmd.Command{
		{
			// Command "server"
			Name:        "server",
			Description: "run mock server",
			ExecFunc: func(ctx context.Context, args []string) error {
				cfg := config.NewConfig()
                
				// Argument ./openapi.yml
				cfg.Server.Path = args[0]
                
				// Flags after argument
				fs := flag.NewFlagSet("dummy", flag.ContinueOnError)
				fs.StringVar(&cfg.Server.Port, "port", "8080", "")
				fs.StringVar(&cfg.Logger.Level, "logger-level", "INFO", "")
				if err := fs.Parse(args[1:]); err != nil {
					return err
				}

				...
			},
		},
	}

	r := acmd.RunnerOf(cmds, acmd.Config{
		AppName: "Dummy",
		Version: version,
	})

	return r.Run()
}

Flags propagation

There is no special methods, config fields to propagate flags to subcommands. However, it's not hard to make this, because every command can access predefined flags, which are shared across handlers.

// generalFlags can be used as flags for all command
type generalFlags struct {
	IsVerbose bool
	Dir       string
}

func (c *generalFlags) Flags() *flag.FlagSet {
	fs := flag.NewFlagSet("", flag.ContinueOnError)
	fs.BoolVar(&c.IsVerbose, "verbose", false, "should app be verbose")
	fs.StringVar(&c.Dir, "dir", ".", "directory to process")
	return fs
}

// commandFlags is a flags for a command
// using struct embedding we can inherit other flags
type commandFlags struct {
	generalFlags
	File string
}

func (c *commandFlags) Flags() *flag.FlagSet {
	fs := c.generalFlags.Flags()
	fs.StringVar(&c.File, "file", "input.txt", "file to process")
	return fs
}

func cmdFoo(ctx context.Context, args []string) error {
	var cfg generalFlags
	if fs := cfg.Flags().Parse(args); err != nil{
		return err
	}

	// use cfg fields or any other flags that you have defined
	return nil
}

func cmdBar(ctx context.Context, args []string) error {
	var cfg commandFlags
	if fs := cfg.Flags().Parse(args); err != nil{
		return err
	}
	
	// use cfg fields or any other flags that you have defined
	return nil
}

Also see ExamplePropagateFlags test.

Build version

Let's assume you have var Version string in main package. To populate acmd.Config.Version field you can do:

$ go build -ldflags="-X 'main.Version=(local)'" -o my_binary .

$ ./my_binary version
./my_binary version: (local)

Starting from Go 1.18 this information is available in runtime/debug.BuildInfo, see: golang/go#37475