~samwhited/cli

0c9ad1988cf1b5a206c9f0a8b47e25deb2cc21c3 — Sam Whited 4 years ago b586c72 v0.0.1
Add initial Command implementation
7 files changed, 264 insertions(+), 0 deletions(-)

A .gitignore
A Gopkg.toml
A LICENSE
A README.md
A cmd.go
A cmd_test.go
A example_test.go
A .gitignore => .gitignore +6 -0
@@ 0,0 1,6 @@
*.sw[op]
*.svg
*.xml
*.out
vendor/
Gopkg.lock

A Gopkg.toml => Gopkg.toml +21 -0
@@ 0,0 1,21 @@
# Gopkg.toml example
#
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
# for detailed Gopkg.toml documentation.
#
# required = ["github.com/user/thing/cmd/thing"]
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
#
# [[constraint]]
#   name = "github.com/user/project"
#   version = "1.0.0"
#
# [[constraint]]
#   name = "github.com/user/project2"
#   branch = "dev"
#   source = "github.com/myfork/project2"
#
# [[override]]
#  name = "github.com/x/y"
#  version = "2.4.0"


A LICENSE => LICENSE +23 -0
@@ 0,0 1,23 @@
Copyright © 2017 Sam Whited
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

A README.md => README.md +23 -0
@@ 0,0 1,23 @@
# cli

[![GoDoc](https://godoc.org/mellium.im/cli?status.svg)](https://godoc.org/mellium.im/cli)
[![License](https://img.shields.io/badge/license-FreeBSD-blue.svg)](https://opensource.org/licenses/BSD-2-Clause)

A simple library for writing applications with a command line interface.

```go
import mellium.im/cli
```

## License

The package may be used under the terms of the BSD 2-Clause License a copy of
which may be found in the file [LICENSE.md][LICENSE].

### Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in the work by you shall be licensed as above, without any
additional terms or conditions.

[LICENSE]: ./LICENSE

A cmd.go => cmd.go +78 -0
@@ 0,0 1,78 @@
// Copyright 2017 The Mellium Authors.
// Use of this source code is governed by the BSD 2-clause license that can be
// found in the LICENSE file.

// Package cli can be used to create modern command line interfaces.
//
// To create a subcommand create and run an instance of the Command type.
package cli // import "mellium.im/cli"

import (
	"flag"
	"fmt"
	"io"
	"strings"
)

// Command represents a new subcommand.
type Command struct {
	// Usage always starts with the name of the command, followed by a description
	// of its usage. For more information, see the Name method.
	Usage string

	// Description starts with a short, one line description. It can optionally be
	// followed by a blank line and then a longer description or help info.
	Description string

	// Flags is a flag set that provides options that are specific to this
	// subcommand.
	Flags *flag.FlagSet

	// The action to take when this command is executed. The args will be the
	// remaining command line args after all flags have been parsed.
	Run func(c *Command, args ...string) error
}

// Help writes the usage line, flags, and description for the command to the
// provided io.Writer.
// If c.Flags is a valid flag set, calling Help sets the output of c.Flags.
func (c *Command) Help(w io.Writer) {
	fmt.Fprintf(w, "Usage: %s\n\n", c.Usage)
	if c.Flags != nil {
		fmt.Fprintln(w, "Options:\n")
		c.Flags.SetOutput(w)
		c.Flags.PrintDefaults()
	}
	fmt.Fprintln(w, "")
	fmt.Fprintln(w, c.Description)
}

// Name returns the first word of c.Usage which will be the name of the command.
// For example with a usage line of:
//
//     commit [options]
//
// Name returns "commit".
func (c *Command) Name() string {
	idx := strings.Index(c.Usage, " ")
	if idx == -1 {
		return c.Usage
	}
	return c.Usage[:idx]
}

// ShortDesc returns the first line of c.Description.
// For example, given the description:
//
//     This is a command with super.
//
//     The content to be added can be specified in several ways:
//
// ShortDescr returns "Stores the current contents of the index."
func (c *Command) ShortDesc() string {
	idx := strings.IndexByte(c.Description, '\n')
	if idx == -1 {
		return c.Description
	}
	return c.Description[:idx]
}

A cmd_test.go => cmd_test.go +64 -0
@@ 0,0 1,64 @@
// Copyright 2017 The Mellium Authors.
// Use of this source code is governed by the BSD 2-clause license that can be
// found in the LICENSE file.

package cli_test

import (
	"bytes"
	"flag"
	"fmt"
	"testing"

	"mellium.im/cli"
)

type testCase struct {
	cmd  cli.Command
	name string
	desc string
}

var testCases = [...]testCase{
	0: {},
	1: {cmd: cli.Command{Usage: "name", Description: "desc"}, name: "name", desc: "desc"},
	2: {cmd: cli.Command{Usage: "name [options]", Description: "desc\nlong description"}, name: "name", desc: "desc"},
}

func TestCommand(t *testing.T) {
	b := new(bytes.Buffer)
	for i, tc := range testCases {
		t.Run(fmt.Sprintf("Name/%d", i), func(t *testing.T) {
			if name := tc.cmd.Name(); name != tc.name {
				t.Errorf("Invalid name: want=`%s`, got=`%s`", tc.name, name)
			}
		})
		t.Run(fmt.Sprintf("Description/%d", i), func(t *testing.T) {
			if desc := tc.cmd.ShortDesc(); desc != tc.desc {
				t.Errorf("Invalid description: want=`%s`, got=`%s`", tc.desc, desc)
			}
		})
		t.Run(fmt.Sprintf("Help/%d", i), func(t *testing.T) {
			tc.cmd.Flags = flag.NewFlagSet("testflags", flag.ExitOnError)
			tc.cmd.Flags.String("testflag", "testflagvalue", "usage of a test flag")

			b.Reset()
			tc.cmd.Help(b)
			if !bytes.Contains(b.Bytes(), []byte(tc.cmd.Usage)) {
				t.Errorf("Expected cmd.Help() output to contain cmd.Usage")
			}
			if !bytes.Contains(b.Bytes(), []byte(tc.cmd.Description)) {
				t.Errorf("Expected cmd.Help() output to contain cmd.Description")
			}
			if !bytes.Contains(b.Bytes(), []byte("testflag")) {
				t.Errorf("Expected cmd.Help() output to contain flag names")
			}
			if !bytes.Contains(b.Bytes(), []byte("testflagvalue")) {
				t.Errorf("Expected cmd.Help() output to contain flag values")
			}
			if !bytes.Contains(b.Bytes(), []byte("usage of a test flag")) {
				t.Errorf("Expected cmd.Help() output to contain flag usage")
			}
		})
	}
}

A example_test.go => example_test.go +49 -0
@@ 0,0 1,49 @@
// Copyright 2017 The Mellium Authors.
// Use of this source code is governed by the BSD 2-clause license that can be
// found in the LICENSE file.

package cli_test

import (
	"flag"
	"fmt"
	"os"

	"mellium.im/cli"
)

func commitCmd() *cli.Command {
	commitFlags := flag.NewFlagSet("commit", flag.ExitOnError)
	all := commitFlags.Bool("a", false, "Tell the command to automatically stage files…")
	return &cli.Command{
		Usage: `commit [-a] …`,
		Description: `Records changes to the repository.

Stores the current contents of the index in a new commit…`,
		Flags: commitFlags,
		Run: func(c *cli.Command, args ...string) error {
			commitFlags.Parse(args)
			fmt.Println("Ran commit!")
			if *all {
				fmt.Println("-a flag was used")
			}
			return nil
		},
	}
}

func Example() {
	commit := commitCmd()
	commit.Help(os.Stdout)

	// Output:
	// Usage: commit [-a] …
	//
	// Options:
	//
	//   -a	Tell the command to automatically stage files…
	//
	// Records changes to the repository.
	//
	// Stores the current contents of the index in a new commit…
}