Introduction
Confetti comes with a handy command-line interface that provides a SOLID solution for building commands quickly and keeping them organized.
Usage
To view a list of all available commands, run the script without further commands:
go run main.go
Or, if the application is built, use the executable binary:
main
Writing Commands
Your First Command
In Confetti Framework, each command implements the Command interface:
type Command interface {
    Name() string
    Description() string
    Handle() error
}
Each command must define a name, description, and a handler function to execute its logic. Commands should be placed inside the command package and registered in cmd/api/main.go.
Defining Input Expectations with the Standard Go Approach
Flags
Commands can accept flags to modify their behavior. The idiomatic Go way is to use the built-in flag package.
Options With Values
For example, to create a command that accepts a name flag:
package command
import (
    "flag"
    "fmt"
    "os"
)
type UserCreate struct{}
func (u UserCreate) Name() string {
    return "user:create"
}
func (u UserCreate) Description() string {
    return "Create a new user with the specified name"
}
func (u UserCreate) Handle() error {
    // Define a flag for the user's name.
    name := flag.String("name", "", "Name of the user")
    // Alternatively, you can also support a shorthand flag.
    n := flag.String("n", "", "Name of the user (shorthand)")
    
    // Parse command-line flags.
    flag.Parse()
    // Prefer the long flag if provided, otherwise use the short flag.
    userName := *name
    if userName == "" {
        userName = *n
    }
    if userName == "" {
        fmt.Fprintln(os.Stderr, "Error: --name or -n flag is required")
        os.Exit(1)
    }
    fmt.Println("Name provided:", userName)
    return nil
}
You can run the command as follows:
go run main.go user:create --name "John Doe"
Flag Arrays
When a flag expects multiple input values, accept a comma-separated string and split it:
package command
import (
    "flag"
    "fmt"
    "os"
    "strings"
)
type MailSend struct{}
func (m MailSend) Name() string {
    return "mail:send"
}
func (m MailSend) Description() string {
    return "Send mail to a list of user IDs"
}
func (m MailSend) Handle() error {
    // Define a flag that accepts a comma-separated list of IDs.
    idsStr := flag.String("ids", "", "Comma-separated list of user IDs (e.g., 1,2,3)")
    flag.Parse()
    if *idsStr == "" {
        fmt.Fprintln(os.Stderr, "Error: --ids flag is required")
        os.Exit(1)
    }
    // Split the string into a slice.
    ids := strings.Split(*idsStr, ",")
    fmt.Println("IDs provided:", ids)
    return nil
}
Checking If a Flag Is Provided
In the Go approach, you check whether a flag was provided by validating its value (e.g., checking for an empty string):
package command
import (
    "flag"
    "fmt"
    "os"
)
type CheckFlag struct{}
func (c CheckFlag) Name() string {
    return "flag:check"
}
func (c CheckFlag) Description() string {
    return "Check if the name flag is provided"
}
func (c CheckFlag) Handle() error {
    name := flag.String("name", "", "Name of the user")
    flag.Parse()
    if *name != "" {
        fmt.Println("Name provided:", *name)
    } else {
        fmt.Fprintln(os.Stderr, "Error: --name flag is required")
        os.Exit(1)
    }
    return nil
}
Command I/O
Retrieving Input
Using the standard library, simply retrieve the flag values as shown above.
Prompting For Input
For interactive input (when flags are not enough), use Go’s standard input methods:
package command
import (
    "bufio"
    "fmt"
    "os"
)
type Interactive struct{}
func (i Interactive) Name() string {
    return "interactive:prompt"
}
func (i Interactive) Description() string {
    return "Prompt the user for input interactively"
}
func (i Interactive) Handle() error {
    reader := bufio.NewReader(os.Stdin)
    fmt.Print("Enter your name: ")
    name, _ := reader.ReadString('\n')
    fmt.Println("Hello,", name)
    return nil
}
Writing Output
Output to the console using:
fmt.Println("Hello, World!")
For error messages, write to standard error:
fmt.Fprintln(os.Stderr, "An error occurred")
Registering Commands
When building a CLI with multiple commands, register each command in cmd/api/main.go:
package main
import (
    "yourproject/command"
)
var commands = []command.Command{
    command.UserCreate{},
    command.MailSend{},
    command.CheckFlag{},
    command.Interactive{},
    command.AppStatus{},
}
func main() {
    // Your logic to select and execute the command based on input.
}
Example: Checking Application Status and Uptime
Below is an example of a command that checks the application status and uptime, with flag parsing performed directly in the Handle() method:
package command
import (
    "flag"
    "fmt"
    "os"
    "time"
)
type AppStatus struct {
    startTime time.Time
}
func (s AppStatus) Name() string {
    return "app:status"
}
func (s AppStatus) Description() string {
    return "Check the current status of the application and uptime"
}
func (s AppStatus) Handle() error {
    // Define a flag for demonstration purposes.
    dummyFlag := flag.String("dummy", "", "A dummy flag for example purposes")
    
    // Parse the command-line flags.
    flag.Parse()
    
    // Validate that the dummy flag is provided.
    if *dummyFlag == "" {
        fmt.Fprintln(os.Stderr, "Error: --dummy flag is required")
        os.Exit(1)
    }
    
    // Calculate and print the application uptime.
    uptime := time.Since(s.startTime)
    fmt.Printf("Application is running. Uptime: %s\n", uptime)
    
    return nil
}
In this example, flag parsing and validation are done within the Handle() method. If the --dummy flag is not provided, an error is printed to standard error and the application exits. Otherwise, the command calculates and displays the application uptime.