251 lines
5.4 KiB
Go
251 lines
5.4 KiB
Go
package parser
|
|
|
|
import (
|
|
"os"
|
|
"fmt"
|
|
"errors"
|
|
"git.arcanium.tech/tristan/go_logging"
|
|
)
|
|
|
|
|
|
type RunHook func(*Command)
|
|
type WorkFunc func(*Context) error
|
|
|
|
var commands []*Command
|
|
|
|
type CommandOptions struct {
|
|
Name string
|
|
Description string
|
|
}
|
|
|
|
type Command struct {
|
|
Name string
|
|
Description string
|
|
SubCommands []*Command
|
|
Flags []*ArgFlag
|
|
App *App
|
|
Work WorkFunc
|
|
preRunHooks []RunHook
|
|
postRunHooks []RunHook
|
|
}
|
|
|
|
func (c *Command) getFlag(name string) (*ArgFlag, error) {
|
|
for _, flag := range c.Flags {
|
|
if flag.Matches(name) {
|
|
return flag, nil
|
|
}
|
|
}
|
|
return nil, fmt.Errorf("Command(%s) : ERROR : there is no flag by name %s", c.Name, name)
|
|
}
|
|
|
|
func (c *Command) Run(args []string) error {
|
|
if len(c.preRunHooks) > 0 {
|
|
for _, hook := range c.preRunHooks {
|
|
hook(c)
|
|
}
|
|
}
|
|
|
|
err := c.run(args)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if len(c.postRunHooks) > 0 {
|
|
for _, hook := range c.postRunHooks {
|
|
hook(c)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *Command) isSubCommand(name string) (bool) {
|
|
for _, subcommand := range c.SubCommands {
|
|
if subcommand.Name == name {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (c *Command) getSubCommand(name string) (*Command, error) {
|
|
for _, subcommand := range c.SubCommands {
|
|
if subcommand.Name == name {
|
|
return subcommand, nil
|
|
}
|
|
}
|
|
|
|
return nil, fmt.Errorf("ERROR : Command(%s) has no subcommand named %s", c.Name, name)
|
|
}
|
|
|
|
func (c *Command) determineMatch(arg string) (matchType, error) {
|
|
if arg == "-h" || arg == "--help" || arg == "help" {
|
|
return helpMatch, nil
|
|
}
|
|
flag_match, err := isFlag(arg)
|
|
if err != nil {
|
|
return noMatch, err
|
|
}
|
|
|
|
if flag_match {
|
|
_, err := c.getFlag(arg)
|
|
if err != nil {
|
|
return unknownFlagMatch, nil
|
|
}
|
|
return flagMatch, nil
|
|
}
|
|
|
|
subcommand := c.isSubCommand(arg)
|
|
if subcommand == true {
|
|
return commandMatch, nil
|
|
}
|
|
|
|
return noMatch, nil
|
|
}
|
|
|
|
func (c *Command) run (args []string) error {
|
|
logging.Debug("Command(%s) : Entering Run", c.Name)
|
|
|
|
if c.Work == nil && len(args) == 0 {
|
|
logging.Debug("Command(%s) : No arguments. Assuming help", c.Name)
|
|
c.Help()
|
|
return nil
|
|
}
|
|
|
|
for i := 0; i < len(args); i++ {
|
|
arg := args[i]
|
|
|
|
logging.Debug("Command(%s) : Iterating on provided args (i: %d, arg: %s)", c.Name, i, arg)
|
|
|
|
match_type, err := c.determineMatch(arg)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
switch match_type {
|
|
case noMatch:
|
|
return fmt.Errorf("No match found for Arg(%s) for Command(%s)", arg, c.Name)
|
|
case helpMatch:
|
|
c.Help()
|
|
return nil
|
|
case unknownFlagMatch:
|
|
return fmt.Errorf("Command(%s) : ERROR : Unconfigured Flag(%s) detected", c.Name, arg)
|
|
case flagMatch:
|
|
flag, err := c.getFlag(arg)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if flag.requireArg(){
|
|
if len(args[i:]) <= 1 {
|
|
return fmt.Errorf("Command(%s) : ArgFlag(%s) : Required argument not found", c.Name, flag.Name)
|
|
}
|
|
arglist := args[i:i+2]
|
|
i++
|
|
// want to call the flag with itself & the argument it wants
|
|
err = flag.CheckArg(arglist)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
// Since we want to preserve that they were called at all. (particularly for BoolType)
|
|
flag.Args = args[i:i+1]
|
|
}
|
|
|
|
if flag.OnMatch != nil {
|
|
err := flag.OnMatch()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
case commandMatch:
|
|
remaining_args := args[i+1:]
|
|
subcommand, err := c.getSubCommand(arg)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return subcommand.Run(remaining_args)
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
|
|
if c.Work != nil {
|
|
flagmap := make(map[string]*ArgFlag)
|
|
for _, flag := range c.Flags {
|
|
flagmap[flag.Name] = flag
|
|
}
|
|
|
|
ctx := Context{
|
|
Command: c,
|
|
Flags: flagmap,
|
|
App: c.App,
|
|
Args: args,
|
|
}
|
|
return c.Work(&ctx)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *Command) Help () error {
|
|
commandHelpTemplate.Execute(os.Stdout, c)
|
|
return nil
|
|
}
|
|
|
|
func (c *Command) Check() error {
|
|
if c.Name == "" {
|
|
return errors.New("ERROR : Command Name wasn't set")
|
|
}
|
|
|
|
if c.Description == "" {
|
|
return fmt.Errorf("Command(%s) : ERROR : Description wasn't set", c.Name)
|
|
}
|
|
|
|
if len(c.SubCommands) == 0 && c.Work == nil {
|
|
return fmt.Errorf("Command(%s) : ERROR : Command was configured with neither commands (SubCommands) nor a work function (Work)", c.Name)
|
|
}
|
|
|
|
if len(c.SubCommands) > 0 && c.Work != nil {
|
|
return fmt.Errorf("Command(%s) : ERROR : Command was defined with both subcommands & a work function", c.Name)
|
|
}
|
|
|
|
subcommands := make(map[string]bool)
|
|
for _, subcommand := range c.SubCommands {
|
|
err := subcommand.Check()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if _, exist := subcommands[subcommand.Name]; exist {
|
|
return fmt.Errorf("Command(%s) : SubCommand(%s) : SubCommand Name was found as duplicate", c.Name, subcommand.Name)
|
|
}
|
|
subcommands[subcommand.Name]=true
|
|
}
|
|
for _, flag := range c.Flags {
|
|
err := flag.Check()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
shorts := make(map[string]bool)
|
|
longs := make(map[string]bool)
|
|
names := make(map[string]bool)
|
|
for _, flag := range c.Flags {
|
|
if _, exist := shorts[flag.Short]; exist {
|
|
return fmt.Errorf("Command(%s) : ArgFlag(%s) : flag short duplicate found (%s)", c.Name, flag.Name, flag.Short)
|
|
}
|
|
shorts[flag.Short] = true
|
|
if _, exist := longs[flag.Long]; exist {
|
|
return fmt.Errorf("Command(%s) : ArgFlag(%s) : flag long duplicate found (%s)", c.Name, flag.Name, flag.Long)
|
|
}
|
|
longs[flag.Long] = true
|
|
if _, exist := names[flag.Name]; exist {
|
|
return fmt.Errorf("Command(%s) : ArgFlag(Long: %s) : flag name duplicate found (%s)", c.Name, flag.Long, flag.Name)
|
|
}
|
|
names[flag.Name] = true
|
|
}
|
|
|
|
|
|
return nil
|
|
}
|