Skip to content
/ lieut Public

An opinionated, feature-limited, no external dependency, "micro-framework" for building command line applications in Go.

License

Notifications You must be signed in to change notification settings

Rican7/lieut

Repository files navigation

lieut

Build Status Coverage Status Go Report Card Go Reference Latest Stable Version

Lieut, short for lieutenant, or "second-in-command" to a commander.

An opinionated, feature-limited, no external dependency, "micro-framework" for building command line applications in Go.

But why though?

In general, I personally don't like using frameworks... especially macro frameworks, and especially in Go.

I prefer using Go's extensive standard library, and when that doesn't quite provide what I'm looking for, I look towards smaller libraries that carry few (if any) other external dependencies and that integrate well with the standard library and its interfaces.

That being said, the flag package in the standard library leaves a lot to be desired, and unfortunately acts far less like a library and much more like a framework (it's library code that calls os.Exit()... 😕😖). It defines a lot of higher-level application behaviors than typically expected, and those can be a little surprising.

The goal of this project is to get some of those quirks out of the way (or at least work WITH them in ways that reduce the surprises in behavior) and reduce the typical "setup" code that a command line application needs, all while working with the standard library, to expand upon it's capabilities rather than locking your application into a tree of external/third-party dependencies.

"Wait, what? Why not just use 'x' or 'y'?" Don't worry, I've got you covered.

Project Status

This project is currently in "pre-release". The API may change. Use a tagged version or vendor this dependency if you plan on using it.

Features

  • Relies solely on the standard library.
  • Sub-command applications (app command, app othercommand).
  • Automatic handling of typical error paths.
  • Standardized output handling of application (and command) usage, description, help, and version..
  • Help flag (--help) handling, with actual user-facing notice (it shows up as a flag in the options list), rather than just handling it silently..
  • Version flag (--version) handling with a standardized output.
  • Global and sub-command flags with automatic merging.
  • Built-in signal handling (interrupt) with context cancellation.
  • Smart defaults, so there's less to configure.

Example

package main

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

	"github.com/Rican7/lieut"
)

func main() {
	do := func(ctx context.Context, arguments []string) error {
		_, err := fmt.Println(arguments)

		return err
	}

	app := lieut.NewSingleCommandApp(
		lieut.AppInfo{Name: "example"},
		do,
		flag.CommandLine,
		os.Stdout,
		os.Stderr,
	)

	exitCode := app.Run(context.Background(), os.Args[1:])

	os.Exit(exitCode)
}

For more examples, see the documentation.

How can I use this with another flag package?

pflag

If you want to use the github.com/spf13/pflag package (or one of its many forks), you'll just need to "merge" the global and sub-command flag-sets yourself... Like this:

globalFlags := flag.NewFlagSet("app", flag.ContinueOnError)
globalFlags.String("someglobalflag", "example", "an example global flag")

subCommandFlags := flag.NewFlagSet("subcommand", flag.ContinueOnError)
subCommandFlags.AddFlagSet(globalFlags) // Merge the globals into this command's flags

Other

Honestly, I haven't tried, but I'd imagine you just handle and parse the flags yourself before running the lieut app.

Wait, what? Why not just use "x" or "y"?

If you're reading this, you're probably thinking of an alternative solution or wondering why someone would choose this library over another. Well, I'm not here to convince you, but if you're curious, read on.

I've tried using many of the popular libraries (cobra, kingpin, urfave/cli, etc), and they all suffer from one of the following problems:

  • They're large in scope and attempt to solve too many problems, generically.
  • They use external/third-party dependencies.
  • They don't work well directly with the standard library.
  • They've been abandoned (practically, if not directly).
    • (This would be less of a concern if it weren't for the fact that they're LARGE in scope, so it's harder to just own yourself if the source rots).

If none of that matters to you, then that's fine, but they were enough of a concern for me to spend a few days extracting this package's behaviors from another project and making it reusable. 🙂