├── .gitignore ├── whip.go ├── Arg.go ├── go.mod ├── Cmd.go ├── go.sum ├── util.go ├── Cli.go └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /whip.go: -------------------------------------------------------------------------------- 1 | package whip 2 | -------------------------------------------------------------------------------- /Arg.go: -------------------------------------------------------------------------------- 1 | package whip 2 | 3 | type Arg struct { 4 | Position int 5 | Value string 6 | } 7 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/phillip-england/whip 2 | 3 | go 1.25.3 4 | 5 | require github.com/phillip-england/wherr v0.0.1 6 | -------------------------------------------------------------------------------- /Cmd.go: -------------------------------------------------------------------------------- 1 | package whip 2 | 3 | type Cmd interface { 4 | Execute(cli *Cli) error 5 | } 6 | 7 | type CommandFactory func(cli *Cli) (Cmd, error) 8 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/phillip-england/wherr v0.0.1 h1:FBPY3a7a5o6GkEkcSyEyCeHCPNg+HeGXoLeL5aVs5sQ= 2 | github.com/phillip-england/wherr v0.0.1/go.mod h1:WFwfUHC6l3+edXnwrdOb6VnXWo7XppiBeDWNOnDh7GU= 3 | -------------------------------------------------------------------------------- /util.go: -------------------------------------------------------------------------------- 1 | package whip 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | "strings" 7 | ) 8 | 9 | func FileExists(path string) bool { 10 | _, err := os.Stat(path) 11 | return err == nil 12 | } 13 | 14 | func DirExists(path string) bool { 15 | info, err := os.Stat(path) 16 | if err != nil { 17 | return false 18 | } 19 | return info.IsDir() 20 | } 21 | 22 | func IsFile(path string) bool { 23 | p := filepath.Clean(path) 24 | if strings.HasSuffix(p, string(filepath.Separator)) { 25 | return false 26 | } 27 | ext := filepath.Ext(p) 28 | return ext != "" 29 | } 30 | 31 | func IsDir(path string) bool { 32 | p := filepath.Clean(path) 33 | if strings.HasSuffix(path, string(filepath.Separator)) { 34 | return true 35 | } 36 | ext := filepath.Ext(p) 37 | return ext == "" 38 | } 39 | -------------------------------------------------------------------------------- /Cli.go: -------------------------------------------------------------------------------- 1 | package whip 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/phillip-england/wherr" 7 | ) 8 | 9 | type Cli struct { 10 | Source string 11 | Args map[string]*Arg 12 | Flags map[string]*Arg 13 | Commands map[string]CommandFactory 14 | DefaultFactory CommandFactory 15 | DefaultCmd Cmd 16 | Store map[string]any 17 | Cwd string 18 | } 19 | 20 | func New(factory CommandFactory) (Cli, error) { 21 | cwd, err := os.Getwd() 22 | if err != nil { 23 | return Cli{}, wherr.Consume(wherr.Here(), err, "") 24 | } 25 | osArgs := os.Args 26 | flags := make(map[string]*Arg) 27 | args := make(map[string]*Arg) 28 | source := "" 29 | if len(osArgs) > 0 { 30 | source = osArgs[0] 31 | } 32 | for i, arg := range osArgs { 33 | if len(arg) > 1 && i > 0 && (arg[0] == '-' || (len(arg) > 2 && arg[:2] == "--")) { 34 | flags[arg] = &Arg{ 35 | Position: i, 36 | Value: arg, 37 | } 38 | continue 39 | } 40 | args[arg] = &Arg{ 41 | Position: i, 42 | Value: arg, 43 | } 44 | } 45 | cli := Cli{ 46 | Source: source, 47 | Args: args, 48 | Flags: flags, 49 | Commands: make(map[string]CommandFactory), 50 | Cwd: cwd, 51 | } 52 | err = cli.setDefault(factory) 53 | if err != nil { 54 | return cli, wherr.Consume(wherr.Here(), err, "") 55 | } 56 | return cli, nil 57 | } 58 | 59 | func (cli *Cli) At(commandName string, factory CommandFactory) { 60 | cli.Commands[commandName] = factory 61 | } 62 | 63 | func (cli *Cli) setDefault(factory CommandFactory) error { 64 | cli.DefaultFactory = factory 65 | cmd, err := factory(cli) 66 | if err != nil { 67 | return wherr.Consume(wherr.Here(), err, "") 68 | } 69 | cli.DefaultCmd = cmd 70 | return nil 71 | } 72 | 73 | func (cli *Cli) Run() error { 74 | 75 | firstArgPosition := 1 76 | var firstArg string 77 | 78 | for _, arg := range cli.Args { 79 | if arg.Position == firstArgPosition { 80 | firstArg = arg.Value 81 | break 82 | } 83 | } 84 | 85 | if firstArg == "" { 86 | return cli.DefaultCmd.Execute(cli) 87 | } 88 | 89 | if factory, exists := cli.Commands[firstArg]; exists { 90 | cmd, err := factory(cli) 91 | if err != nil { 92 | return wherr.Consume(wherr.Here(), err, "") 93 | } 94 | return cmd.Execute(cli) 95 | } 96 | 97 | return cli.DefaultCmd.Execute(cli) 98 | } 99 | 100 | func (cli *Cli) FlagExists(flag string) bool { 101 | _, exists := cli.Flags[flag] 102 | return exists 103 | } 104 | 105 | func (cli *Cli) ArgExists(arg string) bool { 106 | _, exists := cli.Args[arg] 107 | return exists 108 | } 109 | 110 | func (cli *Cli) ArgGetByStr(arg string) (string, bool) { 111 | val, exists := cli.Args[arg] 112 | return val.Value, exists 113 | } 114 | 115 | func (cli *Cli) ArgForceByStr(arg string) (string, error) { 116 | val, exists := cli.Args[arg] 117 | if !exists { 118 | return "", wherr.Err(wherr.Here(), "arg %s not found", arg) 119 | } 120 | return val.Value, nil 121 | } 122 | 123 | func (cli *Cli) ArgGetOrDefaultValue(arg string, defaultValue string) string { 124 | if val, exists := cli.Args[arg]; exists { 125 | return val.Value 126 | } 127 | return defaultValue 128 | } 129 | 130 | func (cli *Cli) ArgGetByPosition(position int) (string, bool) { 131 | for _, arg := range cli.Args { 132 | if arg.Position == position { 133 | return arg.Value, true 134 | } 135 | } 136 | return "", false 137 | } 138 | 139 | func (cli *Cli) ArgGetByPositionForce(position int, errMsg string) (string, error) { 140 | arg, exists := cli.ArgGetByPosition(position) 141 | if !exists { 142 | return "", wherr.Err(wherr.Here(), "%s", errMsg) 143 | } 144 | return arg, nil 145 | } 146 | 147 | func (cli *Cli) ArgMorphAtPosition(position int, value string) { 148 | for _, arg := range cli.Args { 149 | if arg.Position == position { 150 | arg.Value = value 151 | } 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | mood Package: Quick Start Guide 2 | =============================== 3 | 4 | The `mood` package is a minimal framework for building command-line 5 | interfaces (CLI) in Go. It handles argument parsing, command 6 | dispatching, and provides a simple execution context. 7 | 8 | Installation 9 | ------------ 10 | 11 | Install the `mood` package using the standard Go command: 12 | 13 | ```bash 14 | go get github.com/phillip-england/mood 15 | ``` 16 | 17 | Basic Usage: Running a Default Command 18 | -------------------------------------- 19 | 20 | Every `mood` application requires a default command to run when no other 21 | subcommand is specified. Start by defining the `Cmd` interface and the 22 | factory function. 23 | 24 | The Command Interface (Cmd) 25 | --------------------------- 26 | 27 | All commands must implement the `Cmd` interface: 28 | 29 | ```go 30 | type Cmd interface { 31 | Execute(cli *Cli) error 32 | } 33 | ``` 34 | 35 | Example: main.go 36 | ---------------- 37 | 38 | This example shows how to set up the default command and run the 39 | application. 40 | 41 | ```go 42 | package main 43 | 44 | import ( 45 | "fmt" 46 | "os" 47 | "github.com/phillip-england/mood" 48 | ) 49 | 50 | // 1. Define the Default Command struct 51 | type HelloCmd struct {} 52 | 53 | // 2. Implement the Execute method 54 | func (c *HelloCmd) Execute(cli *mood.Cli) error { 55 | // Access the command source (the executable name) 56 | fmt.Printf("Welcome to the %s CLI!\n", cli.Source) 57 | // Access the current working directory 58 | fmt.Printf("Current Directory: %s\n", cli.Cwd) 59 | 60 | // The Run method handles errors automatically 61 | return nil 62 | } 63 | 64 | // 3. Define the factory function 65 | func HelloFactory(cli *mood.Cli) (mood.Cmd, error) { 66 | return &HelloCmd{}, nil 67 | } 68 | 69 | func main() { 70 | // Initialize the CLI with the default command factory 71 | cli, err := mood.New(HelloFactory) 72 | if err != nil { 73 | fmt.Fprintf(os.Stderr, "Error initializing CLI: %v\n", err) 74 | os.Exit(1) 75 | } 76 | 77 | // Run the application 78 | if err := cli.Run(); err != nil { 79 | fmt.Fprintf(os.Stderr, "Execution Error: %v\n", err) 80 | os.Exit(1) 81 | } 82 | } 83 | ``` 84 | 85 | Registering Subcommands 86 | ----------------------- 87 | 88 | Use the `Cli.At` method to register commands that execute when a 89 | specific first argument is provided (e.g., `app run` or `app config`). 90 | 91 | Registering Commands Example 92 | ---------------------------- 93 | 94 | ```go 95 | // Inside your main function, after mood.New: 96 | func main() { 97 | cli, err := mood.New(HelloFactory) 98 | if err != nil { /* error handling */ } 99 | 100 | // Register a new command named "run" 101 | cli.At("run", RunFactory) 102 | 103 | // Register another command named "config" 104 | cli.At("config", ConfigFactory) 105 | 106 | if err := cli.Run(); err != nil { /* error handling */ } 107 | } 108 | ``` 109 | 110 | Accessing Arguments and Flags 111 | ----------------------------- 112 | 113 | The `Cli` object provides methods to check for and retrieve arguments 114 | and flags. 115 | 116 | - `FlagExists(flag string) bool`: Check for presence (e.g., 117 | `cli.FlagExists("-v")`) 118 | - `ArgGetByPosition(position int) (string, bool)`: Get a non-flag 119 | argument by its index (1 for the first positional arg after the 120 | command, if any). 121 | - `ArgGetOrDefaultValue(arg string, defaultValue string) string`: Get 122 | an argument by its value string, or use a fallback. 123 | 124 | Example: Getting the second positional argument (e.g., in 125 | `app run VALUE`): 126 | 127 | ```go 128 | // In the Execute method of the "run" command: 129 | if val, exists := cli.ArgGetByPosition(2); exists { 130 | // val contains the VALUE string 131 | } 132 | ``` 133 | 134 | Documentation generated for the `mood` package. 135 | --------------------------------------------------------------------------------