diff --git a/cline.go b/cline.go index 0a2da75..c160f36 100644 --- a/cline.go +++ b/cline.go @@ -62,43 +62,54 @@ func New() *App { return &App{} } +// validateCommands checks if a command is valid and initialize it +func validateCommands(commands []Cmd) ([]Cmd, error) { + var cmds []Cmd + for _, c := range commands { + name := strings.TrimSpace(c.Name) + if name == "" { + return nil, fmt.Errorf("command name has empty value") + } + cflags, err := validateAndInitFlags(c.Flags) + if err != nil { + return nil, err + } + c.Flags = cflags + cmds = append(cmds, c) + } + return cmds, nil +} + // Run executes the current application. func (app *App) Run(vArgs []string) error { // Commands and flags validation - // 1. Check application flags - aflags, err := checkAndInitFlags(app.Flags) + // 1. Check application global flags + vflags, err := validateAndInitFlags(app.Flags) if err != nil { return err } - app.Flags = aflags + app.Flags = vflags // 2. Check commands and their flags - var cmds []Cmd - for _, c := range app.Commands { - name := strings.ToLower(strings.TrimSpace(c.Name)) - if name == "" { - return fmt.Errorf("command name has empty value") - } - cflags, err := checkAndInitFlags(c.Flags) - if err != nil { - return err - } - c.Flags = cflags - cmds = append(cmds, c) + vcmds, err := validateCommands(app.Commands) + if err != nil { + return err } - app.Commands = cmds + app.Commands = vcmds // 3. Process commands and flags var lastCmd Cmd var lastFlag Flag + var lastFlagIndex int = -1 + // var lastCmdFlagList []Flag var tailArgs []string - var hasCommand = false + var hasCmd = false var hasHelp = false var hasVersion = false for i := 1; i < len(vArgs); i++ { - arg := strings.ToLower(strings.TrimSpace(vArgs[i])) + arg := strings.TrimSpace(vArgs[i]) // Check for no supported arguments (remaining) if len(tailArgs) > 0 { @@ -110,7 +121,7 @@ func (app *App) Run(vArgs []string) error { if strings.HasPrefix(arg, "-") { flagKey := strings.TrimPrefix(strings.TrimPrefix(arg, "-"), "-") // Skip unsupported fags - if strings.HasPrefix(flagKey, "-") { + if flagKey == "" || strings.HasPrefix(flagKey, "-") { tailArgs = append(tailArgs, arg) continue } @@ -120,7 +131,9 @@ func (app *App) Run(vArgs []string) error { case "help", "h": hasHelp = true case "version", "v": - hasVersion = true + if !hasCmd { + hasVersion = true + } } if hasHelp || hasVersion { break @@ -128,32 +141,63 @@ func (app *App) Run(vArgs []string) error { // Assign flag default values var flags []Flag - if hasCommand { + if hasCmd { flags = lastCmd.Flags } else { flags = app.Flags } // Find argument key flag on flag list - flag := findFlagByKey(flagKey, flags) + i, flag := findFlagByKey(flagKey, flags) if flag == nil { return fmt.Errorf("argument `%s` is not recognised", arg) } lastFlag = flag + lastFlagIndex = i + + switch fl := lastFlag.(type) { + case FlagBool: + if fl.Name != "" { + if fl.zflagAssigned { + tailArgs = append(tailArgs, arg) + continue + } + + // If bool flag is defined is assumed as `true` + s := FlagValue("1") + fl.zflag = s + fl.zflagAssigned = true + lastFlag = fl + + if hasCmd { + if len(lastCmd.Flags) > 0 && lastFlagIndex > -1 { + lastCmd.Flags[lastFlagIndex] = fl + } + } else { + if len(app.Flags) > 0 && lastFlagIndex > -1 { + app.Flags[lastFlagIndex] = fl + } + } + + tailArgs = append(tailArgs, arg) + continue + } + } + continue } // 3.2. Commands // 3.2.1 Check for a valid command (first time) - if !hasCommand { + if !hasCmd { for _, c := range app.Commands { if c.Name == arg { - hasCommand = true + hasCmd = true lastCmd = c break } } - if hasCommand { + if hasCmd { continue } } @@ -172,14 +216,24 @@ func (app *App) Run(vArgs []string) error { tailArgs = append(tailArgs, arg) continue } - s := FlagValue(arg) - if _, err := s.Bool(); err == nil { - fl.zflag = s - fl.zflagAssigned = true - lastFlag = fl + + // If bool flag is defined is assumed as `true` + s := FlagValue("1") + fl.zflag = s + fl.zflagAssigned = true + lastFlag = fl + + if hasCmd { + if len(lastCmd.Flags) > 0 && lastFlagIndex > -1 { + lastCmd.Flags[lastFlagIndex] = fl + } } else { - tailArgs = append(tailArgs, arg) + if len(app.Flags) > 0 && lastFlagIndex > -1 { + app.Flags[lastFlagIndex] = fl + } } + + tailArgs = append(tailArgs, arg) continue } case FlagInt: @@ -193,6 +247,16 @@ func (app *App) Run(vArgs []string) error { fl.zflag = s fl.zflagAssigned = true lastFlag = fl + + if hasCmd { + if len(lastCmd.Flags) > 0 && lastFlagIndex > -1 { + lastCmd.Flags[lastFlagIndex] = fl + } + } else { + if len(app.Flags) > 0 && lastFlagIndex > -1 { + app.Flags[lastFlagIndex] = fl + } + } } else { tailArgs = append(tailArgs, arg) } @@ -207,6 +271,16 @@ func (app *App) Run(vArgs []string) error { fl.zflag = FlagValue(arg) fl.zflagAssigned = true lastFlag = fl + + if hasCmd { + if len(lastCmd.Flags) > 0 && lastFlagIndex > -1 { + lastCmd.Flags[lastFlagIndex] = fl + } + } else { + if len(app.Flags) > 0 && lastFlagIndex > -1 { + app.Flags[lastFlagIndex] = fl + } + } continue } case FlagStringSlice: @@ -218,6 +292,16 @@ func (app *App) Run(vArgs []string) error { fl.zflag = FlagValue(arg) fl.zflagAssigned = true lastFlag = fl + + if hasCmd { + if len(lastCmd.Flags) > 0 && lastFlagIndex > -1 { + lastCmd.Flags[lastFlagIndex] = fl + } + } else { + if len(app.Flags) > 0 && lastFlagIndex > -1 { + app.Flags[lastFlagIndex] = fl + } + } continue } default: @@ -228,7 +312,11 @@ func (app *App) Run(vArgs []string) error { // Show `help` flag details if hasHelp { - return app.printHelp() + if hasCmd { + return printHelp(app, &lastCmd) + } + + return printHelp(app, nil) } // Show `version` flag details @@ -238,7 +326,7 @@ func (app *App) Run(vArgs []string) error { } // Call command handler - if hasCommand && lastCmd.Handler != nil { + if hasCmd && lastCmd.Handler != nil { return lastCmd.Handler(&CmdContext{ Cmd: &lastCmd, Flags: &FlagMapValues{ diff --git a/examples/main.go b/examples/main.go index 62e60d7..d784f99 100644 --- a/examples/main.go +++ b/examples/main.go @@ -31,7 +31,7 @@ func main() { Name: "verbose", Summary: "Enable more verbose info", Value: false, - Aliases: []string{"v"}, + Aliases: []string{"V"}, EnvVar: "ENV_VERBOSE", }, } @@ -41,10 +41,10 @@ func main() { Summary: "Show command information", Flags: []cli.Flag{ cli.FlagInt{ - Name: "version", - Summary: "Enable more verbose command information", + Name: "trace", + Summary: "Enable tracing mode", Value: 10, - Aliases: []string{"z"}, + Aliases: []string{"t"}, }, cli.FlagBool{ Name: "detailed", @@ -61,6 +61,9 @@ func main() { os.Exit(1) } fmt.Printf("Cmd Flag `version` opted: `%d` (%T)\n", i, i) + + d := ctx.Flags.String("detailed") + fmt.Printf("Cmd Flag `detailed` opted: `%s` (%T)\n", d, d) fmt.Printf("Cmd Tail arguments: %#v\n", ctx.TailArgs) return nil }, @@ -70,7 +73,10 @@ func main() { fmt.Printf("App `%s` executed!\n", ctx.App.Name) fmt.Printf("App Tail arguments: %#v\n", ctx.TailArgs) fmt.Printf("App Flag `file` opted: `%s`\n", ctx.Flags.StringSlice("file")) - fmt.Printf("App Flag `verbose` opted: `%s`\n", ctx.Flags.StringSlice("verbose")) + + b, _ := ctx.Flags.Bool("verbose") + + fmt.Printf("App Flag `verbose` opted: `%v`\n", b) return nil } if err := app.Run(os.Args); err != nil { diff --git a/flag_helpers.go b/flag_helpers.go index fa04e5e..d56ec38 100644 --- a/flag_helpers.go +++ b/flag_helpers.go @@ -5,8 +5,8 @@ import ( "strings" ) -// checkAndInitFlags checks for a flag and initialize it -func checkAndInitFlags(flags []Flag) ([]Flag, error) { +// validateAndInitFlags checks for a flag and initialize it +func validateAndInitFlags(flags []Flag) ([]Flag, error) { var sFlags []Flag for _, v := range flags { switch f := v.(type) { @@ -46,54 +46,54 @@ func checkAndInitFlags(flags []Flag) ([]Flag, error) { } // findFlagByKey finds a flag item in a flag's array by key. -func findFlagByKey(key string, flags []Flag) Flag { - for _, f := range flags { +func findFlagByKey(key string, flags []Flag) (int, Flag) { + for i, f := range flags { switch fl := f.(type) { case FlagBool: // Check for long named flags if key == fl.Name { - return fl + return i, fl } // Check for short named flags for _, s := range fl.Aliases { if key == s { - return fl + return i, fl } } case FlagInt: // Check for long named flags if key == fl.Name { - return fl + return i, fl } // Check for short named flags for _, s := range fl.Aliases { if key == s { - return fl + return i, fl } } case FlagString: // Check for long named flags if key == fl.Name { - return fl + return i, fl } // Check for short named flags for _, s := range fl.Aliases { if key == s { - return fl + return i, fl } } case FlagStringSlice: // Check for long named flags if key == fl.Name { - return fl + return i, fl } // Check for short named flags for _, s := range fl.Aliases { if key == s { - return fl + return i, fl } } } } - return nil + return -1, nil } diff --git a/print_help.go b/print_help.go index 367684b..18f2494 100644 --- a/print_help.go +++ b/print_help.go @@ -5,31 +5,47 @@ import ( "strings" ) -// printHelp prints current application flags and commands (--help). -func (app *App) printHelp() error { - fmt.Printf("NAME: %s [OPTIONS] COMMAND\n\n", app.Name) - fmt.Printf("%s\n\n", app.Summary) +// printHelp prints current application flags and commands info (--help). +func printHelp(app *App, cmd *Cmd) error { + var summary string + var flags []Flag + + if cmd == nil { + summary = app.Summary + flags = app.Flags + } else { + summary = cmd.Summary + flags = cmd.Flags + } + + // TODO: subcommands support + if cmd == nil { + fmt.Printf("NAME: %s [OPTIONS] COMMAND\n\n", app.Name) + } else { + fmt.Printf("NAME: %s %s [OPTIONS] COMMAND\n\n", app.Name, cmd.Name) + } + fmt.Printf("%s\n\n", summary) // Print options fmt.Printf("OPTIONS:\n") - var flags [][]string + var vflags [][]string var flagLen int = 0 - for _, fl := range app.Flags { + for _, fl := range flags { fname := "" switch f := fl.(type) { case FlagBool: fname = f.Name - flags = append(flags, []string{f.Name, f.Summary}) + vflags = append(vflags, []string{f.Name, f.Summary}) case FlagInt: fname = f.Name - flags = append(flags, []string{f.Name, f.Summary}) + vflags = append(vflags, []string{f.Name, f.Summary}) case FlagString: fname = f.Name - flags = append(flags, []string{f.Name, f.Summary}) + vflags = append(vflags, []string{f.Name, f.Summary}) case FlagStringSlice: fname = f.Name - flags = append(flags, []string{f.Name, f.Summary}) + vflags = append(vflags, []string{f.Name, f.Summary}) } if len([]rune(fname)) > flagLen { flagLen = len([]rune(fname)) @@ -41,31 +57,55 @@ func (app *App) printHelp() error { if len([]rune("help")) > flagLen { flagLen = len([]rune("help")) } - for _, f := range flags { - fmt.Printf(" --%s%s %s\n", f[0], strings.Repeat(" ", flagLen-len([]rune(f[0]))), f[1]) + for _, f := range vflags { + fmt.Printf( + " --%s%s %s\n", + f[0], + strings.Repeat(" ", flagLen-len([]rune(f[0]))), + f[1], + ) } - // Print special flags - fmt.Printf(" --%s%s %s\n", "version", strings.Repeat(" ", flagLen-len([]rune("version"))), "Prints version information") - fmt.Printf(" --%s%s %s\n", "help", strings.Repeat(" ", flagLen-len([]rune("help"))), "Prints help information") + // App with commands + if cmd == nil { + // Print special flags + fmt.Printf( + " --%s%s %s\n", + "version", + strings.Repeat(" ", flagLen-len([]rune("version"))), + "Prints version information", + ) + fmt.Printf( + " --%s%s %s\n", + "help", + strings.Repeat(" ", flagLen-len([]rune("help"))), + "Prints help information", + ) - // Print commands - fmt.Printf("\n") - fmt.Printf("COMMANDS:\n") + // Print commands + fmt.Printf("\n") + fmt.Printf("COMMANDS:\n") - var cmds [][]string - var cmdLen int = 0 - for _, c := range app.Commands { - cmds = append(cmds, []string{c.Name, c.Summary}) - if len([]rune(c.Name)) > cmdLen { - cmdLen = len([]rune(c.Name)) + var vcmds [][]string + var cmdLen int = 0 + for _, c := range app.Commands { + vcmds = append(vcmds, []string{c.Name, c.Summary}) + if len([]rune(c.Name)) > cmdLen { + cmdLen = len([]rune(c.Name)) + } } - } - for _, c := range cmds { - fmt.Printf(" %s%s %s\n", c[0], strings.Repeat(" ", cmdLen-len([]rune(c[0]))), c[1]) + for _, c := range vcmds { + fmt.Printf(" %s%s %s\n", c[0], strings.Repeat( + " ", cmdLen-len([]rune(c[0]))), c[1], + ) + } + + fmt.Printf("\n") + fmt.Printf("Run '%s COMMAND --help' for more information on a command\n", app.Name) + } else { + fmt.Printf("\n") + fmt.Printf("Run '%s %s COMMAND --help' for more information on a command\n", app.Name, cmd.Name) } - fmt.Printf("\n") - fmt.Printf("Run '%s COMMAND --help' for more information on a command\n", app.Name) return nil }