Skip to content

KingOfTac/flagon

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

24 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

flagon logo

A declarative CLI framework for GO

Go Reference CI Status

Table of Contents


Installation

go get github.com/kingoftac/flagon/cli

Getting Started

Flagon is a declarative CLI framework for Go that allows you to define commands, arguments, flags, and handlers in a structured way. It supports middleware, hooks, and Lua scripting for plugins.

Basic Example

package main

import (
	"context"
	"log"
	"os"

	"github.com/kingoftac/flagon/cli"
)

func main() {
	c := cli.New(&cli.Command{
		Name:        "myapp",
		Description: "My awesome CLI app",
		Commands: []*cli.Command{
			{
				Name:        "greet",
				Description: "Greet someone",
				Args: []cli.Arg{
					{Name: "name", Description: "Name to greet"},
				},
				Handler: func(ctx context.Context) error {
					name := cli.Args(ctx)[0]
					log.Printf("Hello, %s!", name)
					return nil
				},
			},
		},
	})

	if err := c.Run(os.Args[1:]); err != nil {
		log.Fatal(err)
	}
}

Run it:

go run main.go greet "World"
# Output: Hello, World!

Examples

Using Flags

{
	Name: "build",
	Description: "Build the project",
	Flags: func(fs *flag.FlagSet) {
		fs.Bool("verbose", false, "enable verbose output")
		fs.String("output", "build", "output directory")
	},
	Handler: func(ctx context.Context) error {
		flags := cli.Flags(ctx)
		verbose := flags["verbose"].(bool)
		output := flags["output"].(string)
		
		if verbose {
			log.Println("Building with verbose output")
		}
		log.Printf("Output directory: %s", output)
		return nil
	},
}

Middleware

func LoggingMiddleware(logger *log.Logger) cli.Middleware {
	return func(next cli.Handler) cli.Handler {
		return func(ctx context.Context) error {
			cmd := cli.CurrentCommand(ctx)
			logger.Printf("Executing command: %s", cmd.Name)
			err := next(ctx)
			logger.Printf("Finished command: %s", cmd.Name)
			return err
		}
	}
}

func main() {
	c := cli.New(&cli.Command{
		Name: "myapp",
		Middleware: []cli.Middleware{
			LoggingMiddleware(log.Default()),
		},
		Commands: []*cli.Command{
			{
				Name: "test",
				Handler: func(ctx context.Context) error {
					log.Println("Running tests...")
					return nil
				},
			},
		},
	})
	// ...
}

Hooks

func main() {
	c := cli.New(&cli.Command{
		Name: "myapp",
		Commands: []*cli.Command{
			{
				Name: "deploy",
				Before: []cli.Hook{
					func(ctx context.Context) error {
						log.Println("Pre-deployment checks...")
						return nil
					},
				},
				After: []cli.Hook{
					func(ctx context.Context) error {
						log.Println("Cleanup after deployment...")
						return nil
					},
				},
				Handler: func(ctx context.Context) error {
					log.Println("Deploying application...")
					return nil
				},
			},
		},
	})

	// Global hooks
	c.Hook(cli.BeforeRun, func(ctx context.Context) error {
		log.Println("Application starting...")
		return nil
	})
	c.Hook(cli.AfterRun, func(ctx context.Context) error {
		log.Println("Application finished.")
		return nil
	})
	// ...
}

Nested Commands

c := cli.New(&cli.Command{
	Name: "myapp",
	Commands: []*cli.Command{
		{
			Name: "db",
			Description: "Database operations",
			Commands: []*cli.Command{
				{
					Name: "migrate",
					Description: "Run database migrations",
					Handler: func(ctx context.Context) error {
						log.Println("Running migrations...")
						return nil
					},
				},
				{
					Name: "seed",
					Description: "Seed database",
					Handler: func(ctx context.Context) error {
						log.Println("Seeding database...")
						return nil
					},
				},
			},
		},
	},
})

Command-Specific Middleware

{
	Name: "sensitive",
	Description: "Sensitive operation",
	Middleware: []cli.Middleware{
		func(next cli.Handler) cli.Handler {
			return func(ctx context.Context) error {
				log.Println("Checking permissions...")
				// Permission check logic
				return next(ctx)
			}
		},
	},
	Handler: func(ctx context.Context) error {
		log.Println("Performing sensitive operation...")
		return nil
	},
}

API Documentation

Core Types

Command

Defines a CLI command with its properties:

type Command struct {
	Name        string
	Description string
	Summary     string
	Hidden      bool
	Aliases     []string
	Args        []Arg
	Flags       func(fs *flag.FlagSet)
	Handler     Handler
	Commands    []*Command
	Before      []Hook
	After       []Hook
	Middleware  []Middleware
}

Arg

Defines a command argument:

type Arg struct {
	Name        string
	Description string
	Optional    bool
	Variadic    bool
}

Handler

Function signature for command handlers:

type Handler func(ctx context.Context) error

Middleware

Function for wrapping handlers:

type Middleware func(next Handler) Handler

Hook

Function for lifecycle hooks:

type Hook func(ctx context.Context) error

Key Functions

New

Creates a new CLI instance:

func New(root *Command, opts ...Option) *CLI

Run

Executes the CLI with given arguments:

func (c *CLI) Run(args []string) error

Context Helpers

  • AppFromContext(ctx): Get the app instance
  • CurrentCommand(ctx): Get current command
  • Args(ctx): Get positional arguments
  • Flags(ctx): Get flag values

Options

  • WithLogger(log.Logger): Set custom logger
  • WithAppData(map[string]any): Set app data
  • WithWriters(out, err io.Writer): Set output writers

Lua Plugin System

Flagon supports extending CLI functionality with Lua scripts for dynamic command definition.

Installation

go get github.com/kingoftac/flagon/lua

Loading Plugins

package main

import (
	"log"
	"os"

	"github.com/kingoftac/flagon/cli"
	"github.com/kingoftac/flagon/lua"
)

func main() {
	c := cli.New(&cli.Command{
		Name: "myapp",
	})

	// Load Lua plugins
	engine := lua.NewEngine(c)
	if err := engine.LoadFile("plugins/myplugin.lua"); err != nil {
		log.Fatal(err)
	}

	if err := c.Run(os.Args[1:]); err != nil {
		log.Fatal(err)
	}
}

Authoring Plugins

Create a .lua file to define commands:

command {
  name = "hello",
  description = "Say hello",

  args = {
    { name = "name", description = "Name to greet" }
  },

  flags = {
    -- Lua doesn't handle flags directly, use Go for complex flags
  },

  middleware = {
    function(ctx)
      ctx.log("info", "Before hello")
      ctx.next()
      ctx.log("info", "After hello")
    end
  },

  handler = function(ctx)
    print("Hello, " .. ctx.args[1] .. "!")
  end
}

Plugin Context

In Lua handlers and middleware, ctx provides:

  • ctx.args: Array of positional arguments
  • ctx.log(level, message): Log messages
  • ctx.next(): Call next middleware/handler (middleware only)

Lua Environment

Plugins run in a sandboxed Lua environment with:

  • Base libraries: table, string, math
  • Safe functions only (no dofile, loadfile, etc.)
  • print redirected to CLI logger

Contributing

We welcome contributions! Please:

  1. Fork the repository
  2. Create a feature branch
  3. Make your changes
  4. Add tests if applicable
  5. Submit a pull request

Development

# Clone the repo
git clone https://github.com/kingoftac/flagon.git
cd flagon

# Run unit tests (excludes fuzz tests)
make test

# Build WASM demo
make build-wasm

Testing

Flagon includes comprehensive unit tests and fuzz tests for security-critical components.

Unit Tests

# Run all unit tests
make test

# Run tests for a specific package
cd cli && go test -v
cd lua && go test -v

Fuzz Tests

Fuzz tests help discover edge cases and potential security vulnerabilities in input parsing and the Lua sandbox.

# Run a specific fuzz test (default: FuzzCLIRun, 30s)
make fuzz

# Run a specific fuzz test with custom duration
make fuzz FUZZ_TEST=FuzzLuaScriptExecution FUZZ_TIME=1m

# Run all fuzz tests sequentially
make fuzz-all FUZZ_TIME=30s

Available fuzz tests:

Package Test Description
cli FuzzCLIRun Full CLI execution with arbitrary arguments
cli FuzzValidatePositionalArgs Argument validation
cli FuzzFindSubcommand Subcommand lookup
cli FuzzCollides Name collision detection
cli FuzzContextFunctions Context value operations
cli FuzzMiddlewareChain Middleware pipeline
lua FuzzLuaScriptExecution Lua sandbox escape attempts
lua FuzzDecodeCommand Lua table to Command struct
lua FuzzLuaHandler Handler callback execution
lua FuzzLuaMiddleware Middleware callback execution

Reporting Issues

Please report bugs and request features via GitHub Issues.

License

This project is licensed under the MIT License - see the LICENSE file for details.