clir

repository·master·Indexed 41 days ago

https://github.com/leaanthony/clir

A lightweight, dependency-free Go library for building command-line interfaces. It leverages the standard Go flag package to provide features such as nested subcommands, struct-based flags, positional arguments, auto-generated help, and lifecycle hooks via PreRun and PostRun.

Tokens
11.4K
Snippets
49
Records
54
Agent score
51%

What's inside clir

  1. Overview of clir features

    master

    clir is a simple, clear, and dependency-free CLI library for Go. It is designed to be lightweight while providing robust command-line interface capabilities. Key features include:

    • Nested Subcommands: Organize complex CLI tools with hierarchical commands.
    • Standard Library Integration: Uses the standard Go flag package.
    • Struct-based Flags: Easily map flags to struct fields.
    • Positional Args: Support for arguments passed by position.
    • Auto-generated Help: Automatically creates help menus based on your configuration.
    • Custom Banners: Define custom headers for your CLI output.
    • Hidden Subcommands: Hide specific commands from the help menu.
    • Default Subcommand: Specify a command to run if none is provided.
    • Dependency Free: No external dependencies required.
  2. How nested SubCommands work

    master

    Since Command objects share a similar API to the Cli object, you can nest subcommands indefinitely. A subcommand can itself have its own subcommands, flags, and actions, creating a tree-like command structure.

    package main
    
    import (
      "fmt"
      "log"
    
      "github.com/leaanthony/clir"
    )
    
    func main() {
      cli := clir.NewCli("SubCommand", "A simple example", "v0.0.1")
    
      // Create subcommand
      initCmd := cli.NewSubCommand("init", "Initialise a component")
    
      // Create a new "project" command below the "init" command
      projectCmd := initCmd.NewSubCommand("project", "Creates a new project")
      projectCmd.Action(func() error {
        fmt.Println("Initialising Project!")
        return nil
      })
    
      err := cli.Run()
      if err != nil {
        log.Fatal(err)
      }
    }
  3. Use PostRun to execute cleanup logic after a command

    master

    The PostRun method allows you to specify a custom function that executes immediately after a command has finished running. The function receives a *clir.Cli parameter. Use this for tasks such as resource cleanup, closing database connections, or post-processing command output.

    func main() {
        cli := clir.NewCli("MyApp", "My CLI application")
    
        // Define a command
        cmd := cli.NewSubCommand("mycommand", "Description of mycommand")
    
        // Add flags, actions, and other configurations for the command
    
        // Define a PostRun function for the command
        cmd.PostRun(func(c *clir.Cli) error {
            // Perform cleanup or post-processing here
            fmt.Println("Running PostRun for 'mycommand'")
            return nil // Return an error if something goes wrong
        })
    
        // Run the CLI application
        if err := cli.Run(os.Args[1:]...); err != nil {
            fmt.Fprintf(os.Stderr, "Error: %v\n", err)
            os.Exit(1)
        }
    }
  4. Add SubCommands using AddCommand

    master

    To improve code organization, you can define commands in isolation using clir.NewCommand and then attach them to a parent command or the main CLI object later using the AddCommand method.

    package main
    
    import (
      "fmt"
      "log"
    
      "github.com/leaanthony/clir"
    )
    
    func newProjectCommand() *clir.Command {
      // Create a new Command
      result := clir.NewCommand("project", "Creates a new project")
    
      // Define the Action
      result.Action(func() error {
        fmt.Println("Initialising Project!")
        return nil
      })
      
      return result
    }
    
    func main() {
      cli := clir.NewCli("SubCommand", "A simple example", "v0.0.1")
    
      // Create subcommand
      initCmd := cli.NewSubCommand("init", "Initialise a component")
    
      // Create a new command and add it to the "init" command
      initCmd.AddCommand(newProjectCommand())
    
      err := cli.Run()
      if err != nil {
        log.Fatal(err)
      }
    }
  5. Set a Default Command

    master

    If you want your application to execute a specific subcommand when no arguments are provided, use the DefaultCommand(cmd *Command) method. The help text will automatically indicate that this command is the default using a [default] label.

    package main
    
    import (
    	"fmt"
    	"log"
    
    	"github.com/leaanthony/clir"
    )
    
    func main() {
    	cli := clir.NewCli("SubCommand", "A simple example", "v0.0.1")
    
    	initCmd := cli.NewSubCommand("init", "Initialise a component")
    	initCmd.Action(func() error {
    		fmt.Println("Initialising")
    		return nil
    	})
    
    	// Make init the default command
    	cli.DefaultCommand(initCmd)
    
      err := cli.Run()
      if err != nil {
        log.Fatal(err)
      }
    }
  6. Provide a custom banner for your CLI

    master

    You can replace the default application banner by providing a custom function to SetBannerFunction. This function is called by the application and must return a string representing the banner content. The function receives a pointer to the *clir.Cli instance, allowing you to dynamically include information like the application version or short description in your banner.

    The function must match the signature: func (*clir.Cli) string.

    package main
    
    import (
    	"log"
    
    	"github.com/leaanthony/clir"
    )
    
    // customBanner must match the signature: func (*clir.Cli) string
    func customBanner(cli *clir.Cli) string {
    	return `
     ______   __  __   ______   _________  ______   ___ __ __     
    /_____/\ /_\/_/\ /_____/\ /________\/\_____/\ /__//_//_/_/\    
    \:::__\/ \:\ \:\ \\::::_/\_\__.::.__\/\:::_ \\::\| \| \ \   
     \:\ \  __\:\ \:\ \\:\/___/\  \::\ \   \:\ \ \ \\:.      \ \  
      \:\ \/_/\\:\ \:\ \\_::._\:\  \::\ \   \:\ \ \ \\:.\-/:/  \ \ 
       \:\_\ \ \\:\_\:\ \ /____\:\  \::\ \   \:\_\ \ \\. \  \  \ \
        \_____\/ \_____\/ \_____\/   \__\/    \_____\/ \__\/ \__\/
         ` + cli.Version() + " - " + cli.ShortDescription()
    }
    
    func main() {
    	// Create new cli
    	cli := clir.NewCli("Banner", "A custom banner example", "v0.0.1")
    
    	// Set the custom banner
    	cli.SetBannerFunction(customBanner)
    
      // Run the application
      err := cli.Run()
      if err != nil {
        log.Fatal(err)
      }
    }
  7. Access flags within an Action

    master

    To use command-line flags inside your action, use scoping by passing a pointer to a variable to the flag definition method (e.g., cli.StringFlag). The action function can then access the updated value of that variable directly.

    package main
    
    import (
      "fmt"
      "log"
      
      "github.com/leaanthony/clir"
    )
    
    func main() {
      cli := clir.NewCli("Actions", "A simple example", "v0.0.1")
    
      // Set our default name to "Anonymous"
      name := "Anonymous"
      cli.StringFlag("name", "Your name", &name)
      
      // Define action for the command
      cli.Action(func() error {
        fmt.Printf("Hello %s!\n", name)
        return nil
      })
    
      err := cli.Run()
      if err != nil {
        log.Fatal(err)
      }
    }
  8. Use PreRun to execute setup logic before a command

    master
    The PreRun method allows you to specify a custom function that executes immediately before a command is run. The function receives a *clir.Cli parameter, which provides access to the CLI application instance. Use this for tasks like environment setup, configuration validation, or dependency checking.
  9. Define flags using individual method calls

    master

    You can define individual flags for your CLI application by calling specific type methods on the Cli instance. These methods take the flag name, a description, and a pointer to a variable where the parsed value will be stored.

    Supported types include:

    • StringFlag for string values.
    • IntFlag for int values.
    • BoolFlag for bool values (used as a toggle).

    Example usage:

    cli := clir.NewCli("Flags", "A simple example", "v0.0.1")
    
    var name string
    cli.StringFlag("name", "Your name", &name)
    
    var age int
    cli.IntFlag("age", "Your age", &age)
    
    var awesome bool
    cli.BoolFlag("awesome", "Are you awesome?", &awesome)
    
    err := cli.Run()
    package main
    
    import "github.com/leaanthony/clir"
    import "log"
    
    func main() {
      
      // Create the application
      cli := clir.NewCli("Flags", "A simple example", "v0.0.1")
    
      // Add a string flag
      var name string
      cli.StringFlag("name", "Your name", &name)
    
      // Add an int flag
      var age int
      cli.IntFlag("age", "Your age", &age)
    
      // Add a bool flag
      var awesome bool
      cli.BoolFlag("awesome", "Are you awesome?", &awesome)
    
      // Run the application
      err := cli.Run()
      if err != nil {
        // We had an error
        log.Fatal(err)
      }
    }
  10. Basic usage of clir

    master

    You can create a simple CLI application by initializing a new CLI instance with clir.NewCli, defining flags (such as StringFlag), setting an execution logic with cli.Action, and finally executing the application with cli.Run().

    In the example below, a string flag named name is defined. When the application runs, the provided action function is executed, accessing the value stored in the name variable.

    package main
    
    import (
    	"fmt"
    	"log"
    
    	"github.com/leaanthony/clir"
    )
    
    func main() {
    
        // Create new cli
        cli := clir.NewCli("Flags", "A simple example", "v0.0.1")
        
        // Name
        name := "Anonymous"
        cli.StringFlag("name", "Your name", &name)
        
        // Define action for the command
        cli.Action(func() error {
            fmt.Printf("Hello %s!\n", name)
            return nil
        })
        
        // Run the application
        err := cli.Run()
        if err != nil {
            // We had an error
            log.Fatal(err)
        }
    
    }
  11. Define flags using a struct

    master

    For cleaner code and easier management, you can define multiple flags at once using a struct. This is particularly useful for subcommands.

    Use the AddFlags method on a subcommand (or the main CLI) and pass a pointer to a struct. The struct fields must be decorated with the following tags:

    • name: The flag name (defaults to the field name if omitted).
    • description: The help text for the flag.
    • default: (Optional) The default value for the flag.
    • pos: (Optional) The positional argument index (e.g., "1", "2").

    Note: You must pass a pointer to the struct to AddFlags, otherwise the method will panic.

    type Flags struct {
    	Name string `name:"name" description:"The name of the person"`
    	Age  int    `name:"age" description:"The age of the person"`
    }
    
    // ... inside main ...
    cli := clir.NewCli("flagstruct", "An example", "v0.0.1")
    init := cli.NewSubCommand("create", "Create a person")
    person := &Flags{
    	Age: 20,
    }
    init.AddFlags(person)
    init.Action(func() error {
    	println("Name:", person.Name, "Age:", person.Age)
    	return nil
    })
    
    if err := cli.Run(); err != nil {
    	panic(err)
    }