CLI Applications
About Hiboot CLI Application
Hiboot CLI application is built on top of Cobra with Hiboot’s dependency injection and auto configuration capabilities.
Features
- Dependency injection for commands
- Sub-command support with hierarchical structure
- Flag parsing and validation
- Property value injection
- Auto configuration support
Project Structure
.
├── cmd
│ ├── root.go
│ ├── root_test.go
│ ├── sub.go
│ └── sub_test.go
├── config
│ └── application.yml
├── main.go
└── main_test.go
Basic Example
main.go
package main
import (
"github.com/hidevopsio/hiboot/pkg/app"
"github.com/hidevopsio/hiboot/pkg/app/cli"
)
func main() {
cli.NewApplication(NewRootCommand).
SetProperty(app.BannerDisabled, true).
Run()
}
Root Command
Every CLI application has a root command. Embed cli.RootCommand in your struct to mark it as the root:
package main
import (
"fmt"
"github.com/hidevopsio/hiboot/pkg/app/cli"
)
// RootCommand is the root command
type RootCommand struct {
cli.RootCommand
name string
verbose bool
}
// NewRootCommand creates the root command
func NewRootCommand() *RootCommand {
c := new(RootCommand)
c.Use = "myapp"
c.Short = "My CLI application"
c.Long = "A CLI application built with Hiboot"
c.Example = `
myapp -n John
myapp --name John --verbose
`
// Define flags
c.PersistentFlags().StringVarP(&c.name, "name", "n", "World", "Name to greet")
c.PersistentFlags().BoolVarP(&c.verbose, "verbose", "v", false, "Enable verbose output")
return c
}
// Run executes the command
func (c *RootCommand) Run(args []string) error {
if c.verbose {
fmt.Printf("Verbose mode enabled\n")
}
fmt.Printf("Hello, %s!\n", c.name)
return nil
}
Advanced Example with Sub-commands
main.go
package main
import (
"github.com/hidevopsio/hiboot/pkg/app"
"github.com/hidevopsio/hiboot/pkg/app/cli"
"github.com/hidevopsio/hiboot/pkg/starter/logging"
"myapp/cmd"
)
func main() {
cli.NewApplication(cmd.NewRootCommand).
SetProperty(app.BannerDisabled, true).
SetProperty(app.ProfilesInclude, logging.Profile).
Run()
}
cmd/root.go
package cmd
import (
"github.com/hidevopsio/hiboot/pkg/app/cli"
"github.com/hidevopsio/hiboot/pkg/log"
)
// RootCommand is the root command
type RootCommand struct {
cli.RootCommand
profile string
timeout int
// Property injection
AppName string `value:"${app.name}"`
}
// NewRootCommand creates root command with sub-commands injected
func NewRootCommand(userCmd *userCommand, configCmd *configCommand) *RootCommand {
c := new(RootCommand)
c.Use = "myapp"
c.Short = "My CLI application"
c.Long = "A CLI application with sub-commands"
pf := c.PersistentFlags()
pf.StringVarP(&c.profile, "profile", "p", "dev", "Environment profile")
pf.IntVarP(&c.timeout, "timeout", "t", 30, "Timeout in seconds")
// Add sub-commands
c.Add(userCmd, configCmd)
return c
}
// Run executes when no sub-command is specified
func (c *RootCommand) Run(args []string) error {
log.Infof("App: %s, Profile: %s, Timeout: %d", c.AppName, c.profile, c.timeout)
return nil
}
cmd/user.go (Sub-command)
package cmd
import (
"github.com/hidevopsio/hiboot/pkg/app"
"github.com/hidevopsio/hiboot/pkg/app/cli"
"github.com/hidevopsio/hiboot/pkg/log"
)
type userCommand struct {
cli.SubCommand
}
func init() {
app.Register(newUserCommand)
}
func newUserCommand(listCmd *listCommand, addCmd *addCommand) *userCommand {
c := new(userCommand)
c.Use = "user"
c.Short = "User management"
c.Long = "Manage users in the application"
c.Add(listCmd, addCmd)
return c
}
func (c *userCommand) Run(args []string) error {
log.Info("User command - use a sub-command")
return nil
}
cmd/list.go (Nested Sub-command)
package cmd
import (
"fmt"
"github.com/hidevopsio/hiboot/pkg/app"
"github.com/hidevopsio/hiboot/pkg/app/cli"
)
type listCommand struct {
cli.SubCommand
all bool
}
func init() {
app.Register(newListCommand)
}
func newListCommand() *listCommand {
c := new(listCommand)
c.Use = "list"
c.Short = "List users"
c.Long = "List all users in the system"
c.Flags().BoolVarP(&c.all, "all", "a", false, "Show all users including inactive")
return c
}
func (c *listCommand) Run(args []string) error {
if c.all {
fmt.Println("Listing all users (including inactive)...")
} else {
fmt.Println("Listing active users...")
}
return nil
}
Running the Application
# Run root command
go run main.go
# Run with flags
go run main.go --profile=prod --timeout=60
# Run sub-command
go run main.go user list
# Run sub-command with flags
go run main.go user list --all
# Get help
go run main.go --help
go run main.go user --help
Configuration
CLI applications can use configuration files just like web applications:
config/application.yml
app:
name: my-cli-app
logging:
level: info
Properties can be injected into commands using the value:"" tag:
type RootCommand struct {
cli.RootCommand
AppName string `value:"${app.name}"`
LogLevel string `value:"${logging.level}"`
}
Unit Testing
package cmd
import (
"testing"
"github.com/hidevopsio/hiboot/pkg/app/cli"
"github.com/stretchr/testify/assert"
)
func TestRootCommand(t *testing.T) {
testApp := cli.NewTestApplication(t, NewRootCommand)
t.Run("should run root command", func(t *testing.T) {
_, err := testApp.Run("--name", "Test")
assert.NoError(t, err)
})
t.Run("should run with verbose flag", func(t *testing.T) {
_, err := testApp.Run("--name", "Test", "--verbose")
assert.NoError(t, err)
})
}
What’s Next?
- Inversion of Control - Deep dive into dependency injection
- Auto Configuration - Create custom starters
- Web Application - Build REST APIs