Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v1.1.0rc #1028

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitattributes
@@ -0,0 +1 @@
* text=auto
58 changes: 58 additions & 0 deletions .github/workflows/push.yml
@@ -0,0 +1,58 @@
name: 'push'

on:
push:
pull_request:

env:
GO111MODULE: on

jobs:

test:
strategy:
fail-fast: false
matrix:
os: [ ubuntu, windows, macOS ]
go: [
1.12.x,
1.13.x,
1.14.x
]

runs-on: ${{ matrix.os }}-latest

steps:

- name: Setup go
uses: actions/setup-go@v1
with:
go-version: ${{ matrix.go }}


- run: git config --global core.autocrlf input
if: matrix.os == 'windows'
shell: bash

- uses: actions/checkout@v2

- name: Install golangci-lint, richgo and gox
run: |
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $HOME/go/bin/ latest
go install github.com/kyoh86/richgo
go install github.com/mitchellh/gox

- name: Run tests
shell: bash
run: |
export PATH=$PATH:$HOME/go/bin/
make test

- name: Check formatting
run: make fmt

- name: Build generator
shell: bash
run: |
export PATH=$PATH:$HOME/go/bin/
make cobra_generator
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -32,6 +32,7 @@ Session.vim
tags

*.exe
/cobra/cobra
cobra.test
bin

Expand Down
48 changes: 48 additions & 0 deletions .golangci.yml
@@ -0,0 +1,48 @@
run:
deadline: 5m

linters:
disable-all: true
enable:
#- bodyclose
- deadcode
#- depguard
#- dogsled
#- dupl
- errcheck
#- exhaustive
#- funlen
- gas
#- gochecknoinits
- goconst
#- gocritic
#- gocyclo
#- gofmt
- goimports
- golint
#- gomnd
#- goprintffuncname
#- gosec
#- gosimple
- govet
- ineffassign
- interfacer
#- lll
- maligned
- megacheck
#- misspell
#- nakedret
#- noctx
#- nolintlint
#- rowserrcheck
#- scopelint
#- staticcheck
- structcheck
#- stylecheck
#- typecheck
- unconvert
#- unparam
#- unused
- varcheck
#- whitespace
fast: false
12 changes: 7 additions & 5 deletions .travis.yml
@@ -1,7 +1,6 @@
language: go

stages:
- diff
- test
- build

Expand All @@ -10,20 +9,23 @@ go:
- 1.13.x
- tip

env: GO111MODULE=on

before_install:
- go get -u github.com/kyoh86/richgo
- go get -u github.com/mitchellh/gox
- curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin latest

env:
- GO111MODULE=on

matrix:
allow_failures:
- go: tip
include:
- stage: diff
go: 1.13.x
script: make fmt
- stage: build
go: 1.13.x
script: make cobra_generator

script:
script:
- make test
18 changes: 11 additions & 7 deletions Makefile
@@ -1,21 +1,29 @@
BIN="./bin"
SRC=$(shell find . -name "*.go")

ifeq (, $(shell which golangci-lint))
$(warning "could not find golangci-lint in $(PATH), run: curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh")
endif

ifeq (, $(shell which richgo))
$(warning "could not find richgo in $(PATH), run: go get github.com/kyoh86/richgo")
endif

.PHONY: fmt vet test cobra_generator install_deps clean
.PHONY: fmt lint test cobra_generator install_deps clean

default: all

all: fmt vet test cobra_generator
all: fmt test cobra_generator

fmt:
$(info ******************** checking formatting ********************)
@test -z $(shell gofmt -l $(SRC)) || (gofmt -d $(SRC); exit 1)

test: install_deps vet
lint:
$(info ******************** running lint tools ********************)
golangci-lint run -v

test: install_deps lint
$(info ******************** running tests ********************)
richgo test -v ./...

Expand All @@ -28,9 +36,5 @@ install_deps:
$(info ******************** downloading dependencies ********************)
go get -v ./...

vet:
$(info ******************** vetting ********************)
go vet ./...

clean:
rm -rf $(BIN)
46 changes: 21 additions & 25 deletions README.md
Expand Up @@ -194,7 +194,6 @@ import (
"fmt"
"os"

homedir "github.com/mitchellh/go-homedir"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
Expand Down Expand Up @@ -234,27 +233,19 @@ func init() {
rootCmd.AddCommand(initCmd)
}

func er(msg interface{}) {
fmt.Println("Error:", msg)
os.Exit(1)
}

func initConfig() {
if cfgFile != "" {
// Use config file from the flag.
viper.SetConfigFile(cfgFile)
} else {
// Find home directory.
home, err := homedir.Dir()
if err != nil {
er(err)
}
home, err := os.UserHomeDir()
cobra.CheckErr(err)

// Search config in home directory with name ".cobra" (without extension).
viper.AddConfigPath(home)
viper.SetConfigName(".cobra")
}

viper.AutomaticEnv()

if err := viper.ReadInConfig(); err == nil {
Expand Down Expand Up @@ -406,7 +397,7 @@ func init() {

In this example the persistent flag `author` is bound with `viper`.
**Note**, that the variable `author` will not be set to the value from config,
when the `--author` flag is not provided by user.
when the `--author` flag is provided by user.

More in [viper documentation](https://github.com/spf13/viper#working-with-flags).

Expand All @@ -422,28 +413,33 @@ rootCmd.MarkFlagRequired("region")
## Positional and Custom Arguments

Validation of positional arguments can be specified using the `Args` field
of `Command`.
of `Command`. The following validators are built in:

- `NoArgs` - report an error if there are any positional args.
- `ArbitraryArgs` - accept any args.
- `MinimumNArgs(int)` - report an error if less than N positional args are provided.
- `MaximumNArgs(int)` - report an error if more than N positional args are provided.
- `ExactArgs(int)` - report an error if there are not exactly N positional args.
- `RangeArgs(min, max)` - report an error if the number of args is not between `min` and `max`.
- `MatchAll(pargs ...PositionalArgs)` - enables combining existing checks with arbitrary other checks (e.g. you want to check the ExactArgs length along with other qualities).

If `Args` is undefined or `nil`, it defaults to `ArbitraryArgs`.

The following validators are built in:
Field `ValidArgs` of type `[]string` can be defined in `Command`, in order to report an error if there are any positional args that are not in the list. This validation is executed implicitly before the validator defined in `Args`.

- `NoArgs` - the command will report an error if there are any positional args.
- `ArbitraryArgs` - the command will accept any args.
- `OnlyValidArgs` - the command will report an error if there are any positional args that are not in the `ValidArgs` field of `Command`.
- `MinimumNArgs(int)` - the command will report an error if there are not at least N positional args.
- `MaximumNArgs(int)` - the command will report an error if there are more than N positional args.
- `ExactArgs(int)` - the command will report an error if there are not exactly N positional args.
- `ExactValidArgs(int)` - the command will report an error if there are not exactly N positional args OR if there are any positional args that are not in the `ValidArgs` field of `Command`
- `RangeArgs(min, max)` - the command will report an error if the number of args is not between the minimum and maximum number of expected args.
> NOTE: `OnlyValidArgs` and `ExactValidArgs(int)` are now deprecated. `ArbitraryArgs` and `ExactArgs(int)` provide the same functionality now.

An example of setting the custom validator:
Moreover, it is possible to set any custom validator that satisfies `func(cmd *cobra.Command, args []string) error`. For example:

```go
var cmd = &cobra.Command{
Short: "hello",
Args: func(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return errors.New("requires a color argument")
// Optionally run one of the validators provided by cobra
if err := cobra.MinimumNArgs(1)(cmd, args); err != nil {
return err
}
// Run the custom validation logic
if myapp.IsValidColor(args[0]) {
return nil
}
Expand Down
75 changes: 45 additions & 30 deletions args.go
Expand Up @@ -7,6 +7,25 @@ import (

type PositionalArgs func(cmd *Command, args []string) error

// validateArgs returns an error if there are any positional args that are not in
// the `ValidArgs` field of `Command`
func validateArgs(cmd *Command, args []string) error {
if len(cmd.ValidArgs) > 0 {
// Remove any description that may be included in ValidArgs.
// A description is following a tab character.
var validArgs []string
for _, v := range cmd.ValidArgs {
validArgs = append(validArgs, strings.Split(v, "\t")[0])
}
for _, v := range args {
if !stringInSlice(v, validArgs) {
return fmt.Errorf("invalid argument %q for %q%s", v, cmd.CommandPath(), cmd.findSuggestions(args[0]))
}
}
}
return nil
}

// Legacy arg validation has the following behaviour:
// - root commands with no subcommands can take arbitrary arguments
// - root commands with subcommands will do subcommand validity checking
Expand All @@ -32,25 +51,6 @@ func NoArgs(cmd *Command, args []string) error {
return nil
}

// OnlyValidArgs returns an error if any args are not in the list of ValidArgs.
func OnlyValidArgs(cmd *Command, args []string) error {
if len(cmd.ValidArgs) > 0 {
// Remove any description that may be included in ValidArgs.
// A description is following a tab character.
var validArgs []string
for _, v := range cmd.ValidArgs {
validArgs = append(validArgs, strings.Split(v, "\t")[0])
}

for _, v := range args {
if !stringInSlice(v, validArgs) {
return fmt.Errorf("invalid argument %q for %q%s", v, cmd.CommandPath(), cmd.findSuggestions(args[0]))
}
}
}
return nil
}

// ArbitraryArgs never returns an error.
func ArbitraryArgs(cmd *Command, args []string) error {
return nil
Expand Down Expand Up @@ -86,24 +86,39 @@ func ExactArgs(n int) PositionalArgs {
}
}

// ExactValidArgs returns an error if
// there are not exactly N positional args OR
// there are any positional args that are not in the `ValidArgs` field of `Command`
func ExactValidArgs(n int) PositionalArgs {
// RangeArgs returns an error if the number of args is not within the expected range.
func RangeArgs(min int, max int) PositionalArgs {
return func(cmd *Command, args []string) error {
if err := ExactArgs(n)(cmd, args); err != nil {
return err
if len(args) < min || len(args) > max {
return fmt.Errorf("accepts between %d and %d arg(s), received %d", min, max, len(args))
}
return OnlyValidArgs(cmd, args)
return nil
}
}

// RangeArgs returns an error if the number of args is not within the expected range.
func RangeArgs(min int, max int) PositionalArgs {
// MatchAll allows combining several PositionalArgs to work in concert.
func MatchAll(pargs ...PositionalArgs) PositionalArgs {
return func(cmd *Command, args []string) error {
if len(args) < min || len(args) > max {
return fmt.Errorf("accepts between %d and %d arg(s), received %d", min, max, len(args))
for _, parg := range pargs {
if err := parg(cmd, args); err != nil {
return err
}
}
return nil
}
}

// ExactValidArgs returns an error if there are not exactly N positional args OR
// there are any positional args that are not in the `ValidArgs` field of `Command`
//
// Deprecated: now `ExactArgs` honors `ValidArgs`, when defined and not empty
func ExactValidArgs(n int) PositionalArgs {
return ExactArgs(n)
}

// OnlyValidArgs returns an error if any args are not in the list of `ValidArgs`.
//
// Deprecated: now `ArbitraryArgs` honors `ValidArgs`, when defined and not empty
func OnlyValidArgs(cmd *Command, args []string) error {
return ArbitraryArgs(cmd, args)
}