GithubHelp home page GithubHelp logo

dolftax / cli-1 Goto Github PK

View Code? Open in Web Editor NEW

This project forked from segmentio/cli

0.0 2.0 0.0 106 KB

Go package providing high-level constructs for command-line tools.

License: MIT License

Go 100.00%

cli-1's Introduction

cli CircleCI Go Report Card GoDoc

Go package providing high-level constructs for command-line tools.

Motivation

The user interface of a program is a major contributor to its adoption and maintainability, however it is often overlooked as a second-class requirement. Developers often focus on the core functionalities of their programs and don't put as much time in designing and understanding how the program will be used.

The reality is that even when effort is spent on building powerful interfaces, the tooling available in Go can be a blocker to generalization of the practice.

The standard library does offer a package for parsing command line arguments, but it is limited to flags, and doesn't support loading configuration options from the environment, or building advanced UX with sub-commands.

Another popular package is spf13/cobra, which has been the to-go solution for most projects. This package is powerful but also very large, brings a lot of complexity to programs that use it, and can be very time consuming to navigate for developers.

We believed that creating powerful tools should be simple, that developers should be empowered to build programs that are safe to use and easy to evolve.

The segmentio/cli package was designed to have a minimal yet flexible API, making it easy to learn, and offering clear guidlines on how to build and evolve command line programs.

Command Line Interface

This section contains a couple of examples that showcase the features of the package.

Flags

This first example presents how to construct a command which accepts a --name flag:

package main

import (
	"fmt"

	"github.com/segmentio/cli"
)

func main() {
	type config struct {
		Name string `flag:"-n,--name" help:"Someone's name" default:"Luke"`
	}

	cli.Exec(cli.Command(func(config config) {
		fmt.Printf("hello %s!\n", config.Name)
	}))
}
$ ./example1 --help

Usage:
  example1 [options]

Options:
  -h, --help         Show this help message
  -n, --name string  Someone's name (default: Luke)

$ ./example1 --name Han
hello Han!

The key take away here is how flags are declared by the first argument of the function implementing the command. The segmentio/cli package implements a calling convention which maps the program arguments to the arguments of the function being called.

Positional Arguments

While the first argument of a command must always be a struct defining the set of accepted flags, the function may also define extra arguments which will be loaded from positional arguments:

package main

import (
    "fmt"

    "github.com/segmentio/cli"
)

func main() {
    type noflags struct{}

    cli.Exec(cli.Command(func(_ noflags, x, y int) {
        fmt.Println(x + y)
    }))
}
$ ./example2 --help

Usage:
  example2 [options] [int] [int]

Options:
  -h, --help  Show this help message
$ ./example2 1 2
3

The last function parameter may also be a slice which captures all remaining positional arguments:

package main

import (
	"fmt"

	"github.com/segmentio/cli"
)

func main() {
	type noflags struct{}

	cli.Exec(cli.Command(func(_ noflags, words []string) {
		for _, word := range words {
			fmt.Println(word)
		}
	}))
}
$ ./example3 --help

Usage:
  example3 [options] [string...]

Options:
  -h, --help  Show this help message

$ ./example3 hello world
hello
world

Child Commands

It is common for wrapper programs to accept an arbitrary command that they execute after performing some initializations. To reduce the risk of mixing the program's arguments and the arguments of its child-command, a "--" separator is employed as a delimiter between the two on the command line.

With the segmentio/cli package, this model is supported by adding a variadic list of string parameters to the command:

package main

import (
	"fmt"
	"strings"

	"github.com/segmentio/cli"
)

func main() {
	type noflags struct{}

	cli.Exec(cli.Command(func(_ noflags, args ...string) {
		fmt.Println("run:", strings.Join(args, " "))
	}))
}
$ ./example4 --help

Usage:
  example4 [options] -- [command]

Options:
  -h, --help  Show this help message

$ ./example4 -- echo hello world
run: echo hello world

Command Sets

Advanced tools often have a set of commands in a single program, each exposing a different feature of the tool (e.g. git checkout, git commit).

The segmentio/cli package supports constructing programs like these using the cli.CommandSet type. The next example showcases how to construct a program accepting three sub-commands:

package main

import (
	"fmt"

	"github.com/segmentio/cli"
)

func main() {
	one := cli.Command(func() {
		fmt.Println("1")
	})

	two := cli.Command(func() {
		fmt.Println("2")
	})

	three := cli.Command(func() {
		fmt.Println("3")
	})

	cli.Exec(cli.CommandSet{
		"one":   one,
		"two":   two,
		"three": three,
	})
}
$ ./example5 --help

Usage:
  example5 [command] [-h] [--help] ...

Commands:
  one
  three
  two

Options:
  -h, --help  Show this help message

$ ./example5 one
1

Environment Variables

While passing configuration options on the command line using flags and positional arguments provides great UX, it is also very common to use environment variables in configuration files like kubernetes templates.

Every long flag accepted by a command (flags starting with "--") can also be loaded from environment variables. The package maps environment variables to flags by prefixing it with the program name and converting the flag to upper-snake-case, for example:

> --verbose => ${PROGRAM}_VERBOSE

Testing Commands

Testing command line programs is often overlooked, because packages which facilitate loading program configurations often aren't designed with ease of testing in mind.

On the other hand, commands declared with the segmentio/cli package are easily testable using the cli.Call function, which combined with Go's support for testable examples, offer a great model for testing commands.

Using the first example, here is how we could write tests to validate the behavior of the command:

type config struct {
	Name string `flag:"-n,--name" help:"Someone's name" default:"Luke"`
}

var command = cli.Command(func(config config) {
	fmt.Printf("hello %s!\n", config.Name)
})
func Example_noArguments() {
    cli.Call(command)
    // Output: hello Luke!
}

func Example_withArgument() {
    cli.Call(command, "--name", "Han")
    // Output: hello Han!
}

Formatting Output

A lot of command line programs also output information to their caller, and often need to support multiple formats to be used in different conditions (called by an operator, used in a script for automation, etc...).

This formatting work is often tedious and redundant, so the segmentio/cli package exposes abstractions to help developers build tools which support multiple output formats:

type config struct {
	Output string `flag:"-o,--output" help:"Output format of the command" default:"text"`
}

type result struct {
    Name  string
    Value int
}

var command = cli.Command(func(config config) error {
    p, err := cli.Format(config.Output, os.Stdout)
    if err != nil {
        return err
    }
    defer p.Flush()

    ...

    // Call p.Print one or more times to output content to stdout
    //
    // p.Print(v)
})

The package supports three formats out-of-the-box: text, json, and yaml.

In the text format, struct and map values are printed as table representations with a header being the name of the struct fields or the keys of the map. Other value types are simply printed one value per line.

All formats support the json struct tag to configure the names of the fields and the behavior of the formatting operation.

cli-1's People

Contributors

pelletier avatar dominicbarnes avatar achille-roussel avatar

Watchers

James Cloos avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.