clino

package module
v0.0.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 20, 2021 License: MIT Imports: 12 Imported by: 1

README

clino

Go Reference Build Status Coverage Status Go Report Card

Package clino provides a simple way to create CLI (command-line interface) tools.

You can create commands to use with this package by implementing its interfaces. It supports the Unix -flag style.

asciicast

Implementing commands

With clino, you implement a command by fulfilling interfaces instead of initializing command structs. The main advantage of this approach is that it is easier to test and to avoid globals.

Tip: Go interfaces are fulfilled implicit, so if you don't see a command being exposed or failing (for example), one of the first things you want to do is to check if your methods signatures fulfill clino's interfaces.

Command interface

Name (or usage line) for the command.

type Command interface {
	Name() string
}
Shorter interface

Shorter description of a command to show in the "help" output on a list of commands.

type Shorter interface {
	Short() string
}
Runnable interface

You should implement this interface for any command that you want to run directly on the CLI.

  • It should receive a context and the command arguments, after parsing any flags.
  • A context is required as we want cancelation to be a first-class citizen.
  • You can rely on the context for canceling long tasks during tests.
type Runnable interface {
	Run(ctx context.Context, args ...string) error
}
FlagSet interface

You want to implement this interface to accept flags on your command.

type FlagSet interface {
	Flags(flags *flag.FlagSet)
}
PersistentFlagSet interface

Use the following PersistentFlagSet to define flags for a command and its children.

type PersistentFlagSet interface {
	PersistentFlags(flags *flag.FlagSet)
}
Longer interface

Description or help message for your command. The help command prints the returned value of the Long function as the "help" output of a command.

type Longer interface {
	Long() string
}

Footer of a command shown in the "help " output. It is useful for things like printing examples.

type Footer interface {
	Foot() string
}
Parent interface

Parent contains all subcommands of a given command. Your root command needs to implement it.

type Parent interface {
	Commands() []Command
}
Example code

You can see more examples in the example directory.

package main

import (
	"context"
	"flag"
	"fmt"
	"os"

	"github.com/henvic/clino"
)

func main() {
	p := clino.Program{
		Root: &RootCommand{},
	}
	if err := p.Run(context.Background(), os.Args[1:]...); err != nil {
		fmt.Fprintf(os.Stderr, "%+v\n", err)
		os.Exit(clino.ExitCode(err))
	}
}

// RootCommand is the entrypoint of the application.
type RootCommand struct {
	name string
}

// Name of the application.
func (rc *RootCommand) Name() string {
	return "app"
}

// Long description of the application.
func (rc *RootCommand) Long() string {
	return "Example application."
}

// Flags of the command.
func (rc *RootCommand) Flags(flags *flag.FlagSet) {
	flags.StringVar(&rc.name, "name", "World", "your name")
}

// Run command.
func (rc *RootCommand) Run(ctx context.Context, args ...string) error {
	fmt.Printf("Hello, %s!\n", rc.name)
	return nil
}

Documentation

Overview

Package clino provides a simple way to create CLI (command-line interface) tools.

You can create commands to use with this package by implementing its interfaces. It supports the Unix -flag style.

The Command interface contains only a name. However, if you try to run a command that doesn't implement any of the Runnable, Longer, Parent, or Footer interfaces, you are going to get a "missing implementation" error message.

For working with flags, you need to implement the FlagSet interface to a given command. If you need global flags, you can do so by defining Program.GlobalFlags. You can use it for a -verbose, -config, or other application-wide state flags. In example/complex you can see how to use global flags easily.

Example
package main

import (
	"context"
	"flag"
	"fmt"
	"os"

	"github.com/henvic/clino"
)

// RootCommand is the entrypoint of the application.
type RootCommand struct {
	name string
}

// Name of the application.
func (rc *RootCommand) Name() string {
	return "app"
}

// Long description of the application.
func (rc *RootCommand) Long() string {
	return "Example application."
}

// Flags of the command.
func (rc *RootCommand) Flags(flags *flag.FlagSet) {
	flags.StringVar(&rc.name, "name", "World", "your name")
}

// Run command.
func (rc *RootCommand) Run(ctx context.Context, args ...string) error {
	fmt.Printf("Hello, %s!\n", rc.name)
	return nil
}

func main() {
	p := clino.Program{
		Root: &RootCommand{},
	}
	if err := p.Run(context.Background(), "-name", "Gopher"); err != nil {
		fmt.Fprintf(os.Stderr, "%+v\n", err)
		os.Exit(clino.ExitCode(err))
	}
}
Output:

Hello, Gopher!

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func ExitCode

func ExitCode(err error) int

ExitCode from the command for the process to use when exiting. It returns 0 if the error is nil. If the error comes from *exec.Cmd Run, the same child process exit code is used. If the error is ExitError, it returns the Code field. Otherwise, return exit code 1.

func main() {
	p := clino.Program{
		Root: &RootCommand{},
	}
	if err := p.Run(context.Background(), os.Args[1:]...); err != nil {
		fmt.Fprintf(os.Stderr, "%+v\n", err)
		os.Exit(clino.ExitCode(err))
	}
}

Types

type Command

type Command interface {
	Name() string
}

Command contains the minimal interface for a command: its name (usage).

You usually want to implement the Runnable interface, except for help-only commands, or when your command has subcommands (implements Parent).

type ExitError

type ExitError struct {
	Code int
	Err  error
}

ExitError wraps the error, adding an exit code.

You can use it to exit the process gracefully with a specific exit code when something goes wrong. You should only wrap error when err != nil. You don't need to wrap it if the exit code is *exec.ExitError.

func (ExitError) Error

func (ee ExitError) Error() string

Error returns the original (wrapped) error message.

func (ExitError) Unwrap

func (ee ExitError) Unwrap() error

Unwrap error.

type FlagSet

type FlagSet interface {
	Flags(flags *flag.FlagSet)
}

FlagSet you want to use on your command.

// Flags of the "hello" command.
func (hc *HelloCommand) Flags(flags *flag.FlagSet) {
	flags.StringVar(&hc.name, "name", "World", "your name")
}

You need to implement a Flags function like shown and set any flags you want your commands to parse.

type Footer interface {
	Foot() string
}

Footer of a command shown in the "help <command>" output. It is useful for things like printing examples.

type Longer

type Longer interface {
	Long() string
}

Longer description or help message for your command. The help command prints the returned value of the Long function as the "help" output of a command.

type Parent

type Parent interface {
	Commands() []Command
}

Parent contains all subcommands of a given command.

type PersistentFlagSet added in v0.0.2

type PersistentFlagSet interface {
	PersistentFlags(flags *flag.FlagSet)
}

PersistentFlagSet is similar to FlagSet, but flags are inherited by the next commands.

// PersistentFlags of the "main" command.
func (mc *MainCommand) PersistentFlags(flags *flag.FlagSet) {
	flags.BoolVar(&hc.verbose, "verbose", false, "verbose mode")
}

You need to implement a Flags function like shown and set any flags you want your commands to parse.

type Program

type Program struct {
	// Root command is the entrypoint of the program.
	Root Command

	// GlobalFlags are flags available to all commands.
	//
	// Deprecated: Use PersistentFlags instead.
	GlobalFlags func(flags *flag.FlagSet)

	// Output is the default output function to the application.
	//
	// If not set when calling Run, os.Stdout is set.
	// You probably only want to set this for testing.
	Output io.Writer
	// contains filtered or unexported fields
}

Program you want to run.

You should call the Run function, passing the context, root command, and process arguments.

func (*Program) Run

func (p *Program) Run(ctx context.Context, args ...string) error

Run program by processing arguments and executing the invoked command.

Context is passed down to the command to simplify testing and cancelation. Arguments should be the process arguments (os.Args[1:]...) when you call it from main().

Example:

p := clino.Program{
	Root: &RootCommand{},
}
if err := p.Run(context.Background(), os.Args[1:]...); err != nil {
	fmt.Fprintf(os.Stderr, "%+v\n", err)
	os.Exit(clino.ExitCode(err))
}

type Runnable

type Runnable interface {
	Run(ctx context.Context, args ...string) error
}

Runnable commands are commands that implement the Run function, and you can run it from the command-line. It should receive a context and the command arguments, after parsing any flags. A context is required as we want cancelation to be a first-class citizen. You can rely on the context for canceling long tasks during tests.

type Shorter

type Shorter interface {
	Short() string
}

Shorter description of a command to show in the "help" output on a list of commands.

Directories

Path Synopsis
example

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL