Skip to content
This repository was archived by the owner on Oct 17, 2021. It is now read-only.

Support POSIX flags #9

Merged
merged 1 commit into from
Aug 20, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
vendor
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ A minimal, command-oriented CLI package.
## Features

- Very small, simple API.
- Uses standard `flag` package as much as possible.
- No external dependencies.
- Support for POSIX flags.
- Only external dependency is [spf13/pflag](https://github.com/spf13/pflag).
- Subcommands.
- Auto-generated help.

Expand Down Expand Up @@ -37,7 +37,7 @@ type cmd struct {
verbose bool
}

func (c *cmd) Run(fl *flag.FlagSet) {
func (c *cmd) Run(fl *pflag.FlagSet) {
if c.verbose {
fmt.Println("verbose enabled")
}
Expand All @@ -52,7 +52,7 @@ func (c *cmd) Spec() cli.CommandSpec {
}
}

func (c *cmd) RegisterFlags(fl *flag.FlagSet) {
func (c *cmd) RegisterFlags(fl *pflag.FlagSet) {
fl.BoolVar(&c.verbose, "v", false, "sets verbose mode")
}

Expand Down Expand Up @@ -87,7 +87,7 @@ import (
type subcmd struct {
}

func (c *subcmd) Run(fl *flag.FlagSet) {
func (c *subcmd) Run(fl *pflag.FlagSet) {
fmt.Println("subcommand invoked")
}

Expand All @@ -102,7 +102,7 @@ func (c *subcmd) Spec() cli.CommandSpec {
type cmd struct {
}

func (c *cmd) Run(fl *flag.FlagSet) {
func (c *cmd) Run(fl *pflag.FlagSet) {
// This root command has no default action, so print the help.
fl.Usage()
}
Expand Down
7 changes: 4 additions & 3 deletions command.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package cli

import (
"flag"
"strings"

"github.com/spf13/pflag"
)

// CommandSpec describes a Command's usage.
Expand Down Expand Up @@ -38,7 +39,7 @@ type Command interface {
// Spec returns metadata about the command.
Spec() CommandSpec
// Run invokes the command's main routine with parsed flags.
Run(fl *flag.FlagSet)
Run(fl *pflag.FlagSet)
}

// ParentCommand is an optional interface for commands that have subcommands.
Expand All @@ -52,5 +53,5 @@ type ParentCommand interface {
// FlaggedCommand is an optional interface for commands that have flags.
type FlaggedCommand interface {
// RegisterFlags lets the command register flags which be sent to Handle.
RegisterFlags(fl *flag.FlagSet)
RegisterFlags(fl *pflag.FlagSet)
}
8 changes: 4 additions & 4 deletions examples/simple/main.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
package main

import (
"flag"
"fmt"

"github.com/spf13/pflag"
"go.coder.com/cli"
)

type cmd struct {
verbose bool
}

func (c *cmd) Run(fl *flag.FlagSet) {
func (c *cmd) Run(fl *pflag.FlagSet) {
if c.verbose {
fmt.Println("verbose enabled")
}
Expand All @@ -26,8 +26,8 @@ func (c *cmd) Spec() cli.CommandSpec {
}
}

func (c *cmd) RegisterFlags(fl *flag.FlagSet) {
fl.BoolVar(&c.verbose, "v", false, "sets verbose mode")
func (c *cmd) RegisterFlags(fl *pflag.FlagSet) {
fl.BoolVarP(&c.verbose, "verbose", "v", false, "sets verbose mode")
}

func main() {
Expand Down
6 changes: 3 additions & 3 deletions examples/subcommands/main.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
package main

import (
"flag"
"fmt"

"github.com/spf13/pflag"
"go.coder.com/cli"
)

type subcmd struct {
}

func (c *subcmd) Run(fl *flag.FlagSet) {
func (c *subcmd) Run(fl *pflag.FlagSet) {
fmt.Println("subcommand invoked")
}

Expand All @@ -25,7 +25,7 @@ func (c *subcmd) Spec() cli.CommandSpec {
type cmd struct {
}

func (c *cmd) Run(fl *flag.FlagSet) {
func (c *cmd) Run(fl *pflag.FlagSet) {
// This root command has no default action, so print the help.
fl.Usage()
}
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
module go.coder.com/cli

go 1.12

require github.com/spf13/pflag v1.0.3
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
28 changes: 10 additions & 18 deletions help.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
package cli

import (
"flag"
"fmt"
"io"
"strings"
"text/tabwriter"
"unicode"
"unicode/utf8"

"github.com/spf13/pflag"
)

func flagDashes(name string) string {
Expand Down Expand Up @@ -36,35 +37,26 @@ func fmtDefValue(value interface{}) string {
// \xFF is used to escape a \t so tabwriter ignores it.
const tabEscape = "\xFF"

func renderFlagHelp(fl *flag.FlagSet, w io.Writer) {
var count int
fl.VisitAll(func(f *flag.Flag) {
if count == 0 {
fmt.Fprintf(w, "\n%v flags:\n", fl.Name())
}

count++
if f.DefValue == "" {
fmt.Fprintf(w, tabEscape+"\t%v%v\t%v\n", flagDashes(f.Name), f.Name, f.Usage)
} else {
fmt.Fprintf(w, tabEscape+"\t%v%v=%v\t%v\n", flagDashes(f.Name), f.Name, fmtDefValue(f.DefValue), f.Usage)
}
})
func renderFlagHelp(fullName string, fl *pflag.FlagSet, w io.Writer) {
if fl.HasFlags() {
fmt.Fprintf(w, "\n%s flags:\n", fullName)
fmt.Fprint(w, fl.FlagUsages())
}
}

// renderHelp generates a command's help page.
func renderHelp(cmd Command, fl *flag.FlagSet, w io.Writer) {
func renderHelp(fullName string, cmd Command, fl *pflag.FlagSet, w io.Writer) {
// Render usage and description.
fmt.Fprintf(w, "Usage: %v %v\n\n",
fl.Name(), cmd.Spec().Usage,
fullName, cmd.Spec().Usage,
)
fmt.Fprintf(w, "%v\n", cmd.Spec().Desc)

tw := tabwriter.NewWriter(w, 0, 4, 2, ' ', tabwriter.StripEscape)
defer tw.Flush()

// Render flag help.
renderFlagHelp(fl, tw)
renderFlagHelp(fullName, fl, tw)

// Render subcommand summaries.
pc, ok := cmd.(ParentCommand)
Expand Down
79 changes: 56 additions & 23 deletions run.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,36 @@
package cli

import (
"flag"
"os"
"strings"

"github.com/spf13/pflag"
)

func appendParent(parent string, add string) string {
if parent == "" {
return add + " "
}
return parent + add + " "
}

// splitArgs tries to split the args between the parent command's flags/args, and the subcommand's
// flags/args. If a subcommand is found, the parent args, subcommand args, and the subcommand will
// be returned. If a subcommand isn't found, the args will be returned as is, the subcommand args
// will be empty, and the subcommand will be nil.
func splitArgs(subCmds []Command, args []string) (cmdArgs, subArgs []string, subCmd Command) {
for i, arg := range args {
if strings.HasPrefix(arg, "-") {
continue
}

for _, subCommand := range subCmds {
if subCommand.Spec().Name == arg {
return args[:i], args[i+1:], subCommand
}
}
}

return args, []string{}, nil
}

// Run sets up flags, helps, and executes the command with the provided
// arguments.
//
Expand All @@ -20,37 +39,51 @@ func appendParent(parent string, add string) string {
//
// Use RunRoot if this package is managing the entire CLI.
func Run(cmd Command, args []string, parent string) {
fl := flag.NewFlagSet(parent+""+cmd.Spec().Name, flag.ExitOnError)
name := parent + cmd.Spec().Name
fl := pflag.NewFlagSet(name, pflag.ContinueOnError)
// Ensure pflag library doesn't print usage for us automatically,
// we'll override this below.
fl.Usage = func() {}

if fc, ok := cmd.(FlaggedCommand); ok {
fc.RegisterFlags(fl)
}

fl.Usage = func() {
renderHelp(cmd, fl, os.Stderr)
}

if cmd.Spec().RawArgs {
// Use `--` to return immediately when parsing the flags.
args = append([]string{"--"}, args...)
}
_ = fl.Parse(args)

subcommandArg := fl.Arg(0)
var (
cmdArgs, subArgs []string
subCmd Command
)
pc, isParentCmd := cmd.(ParentCommand)
if isParentCmd {
cmdArgs, subArgs, subCmd = splitArgs(pc.Subcommands(), args)
if subCmd != nil {
args = cmdArgs
}
}

// Route to subcommand.
if pc, ok := cmd.(ParentCommand); ok && subcommandArg != "" {
for _, subcommand := range pc.Subcommands() {
if subcommand.Spec().Name != subcommandArg {
continue
}
err := fl.Parse(args)
// Reassign the usage now that we've parsed the args
// so that we can render it manually.
fl.Usage = func() {
renderHelp(name, cmd, fl, os.Stderr)
}
if err != nil {
fl.Usage()
os.Exit(2)
}

Run(
subcommand, fl.Args()[1:],
appendParent(parent, cmd.Spec().Name),
)
return
}
// Route to subcommand.
if isParentCmd && subCmd != nil {
Run(
subCmd, subArgs,
appendParent(parent, cmd.Spec().Name),
)
return
}

cmd.Run(fl)
Expand Down