Skip to content

Commit

Permalink
Add flag.FlagSet wrapper (#5)
Browse files Browse the repository at this point in the history
* Add flag.FlagSet wrapper.

* Add verbose output to test on ci

* Add verbose output to test on ci

* Fix tests

* Add tests

* Add tests
  • Loading branch information
heartwilltell authored Jan 12, 2024
1 parent 58099ab commit 225ffb9
Show file tree
Hide file tree
Showing 10 changed files with 794 additions and 22 deletions.
10 changes: 4 additions & 6 deletions .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ on:
- main

pull_request:
paths-ignore:
- '.github/workflows/pr.yml'
branches:
- main

Expand All @@ -22,7 +20,7 @@ jobs:
- uses: actions/checkout@v3
- uses: actions/setup-go@v3
with:
go-version: '^1.19'
go-version: '^1.21'

- name: go test
run: go build
Expand All @@ -33,11 +31,11 @@ jobs:
- uses: actions/checkout@v3
- uses: actions/setup-go@v3
with:
go-version: '^1.19'
go-version: '^1.21'

- name: go test
run: |
go test -race -cover ./... -covermode=atomic -coverprofile coverage.out
go test -v -race -cover ./... -covermode=atomic -coverprofile coverage.out
- name: Upload coverage file
uses: codecov/codecov-action@v3
Expand All @@ -48,7 +46,7 @@ jobs:
- uses: actions/checkout@v3
- uses: actions/setup-go@v3
with:
go-version: '^1.19'
go-version: '^1.21'

- name: golangci-lint run
uses: golangci/golangci-lint-action@v2
Expand Down
6 changes: 3 additions & 3 deletions .golangci.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
run:
tests: false # include test files or not, default is true.
go: '1.19'
go: '1.21'

linters:
disable-all: true
Expand Down Expand Up @@ -148,7 +148,7 @@ linters-settings:
arguments:
- maxLitCount: '5'
allowStrs: '""'
allowInts: '0,1,2,3,4,5,6,7,8,9,10,24,30,31'
allowInts: '0,1,2,3,4,5,6,7,8,9,10,24,30,31,32,64'
allowFloats: '0.0,0.,1.0,1.,2.0,2.'

# Warns when a function receives more parameters than the maximum set by the rule's configuration.
Expand All @@ -157,7 +157,7 @@ linters-settings:
- name: argument-limit
severity: warning
disabled: false
arguments: [ 4 ]
arguments: [ 5 ]

# Check for commonly mistaken usages of the sync/atomic package
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#atomic
Expand Down
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ package main

import (
"fmt"
"log"
"os"

"github.com/heartwilltell/scotty"
Expand Down Expand Up @@ -67,6 +68,22 @@ func main() {
"set logging level: 'debug', 'info', 'warning', 'error'",
)

// Or use the SetFlags function.

subCmd2 := scotty.Command{
Name: "subcommand2",
Short: "Subcommands that does something",
SetFlags: func(flags *FlagSet) {
flags.StringVar(&logLVL, "loglevel", "info",
"set logging level: 'debug', 'info', 'warning', 'error'",
)
},
Run: func(cmd *scotty.Command, args []string) error {
// Do some your stuff here.
return nil
},
}

// Attach subcommand to the root command.
rootCmd.AddSubcommands(&subCmd)

Expand Down
23 changes: 19 additions & 4 deletions command.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,30 @@ import (
type Command struct {
// Name represents command name and argument by which command will be called.
Name string

// Short represents short description of the command.
Short string

// Long represents short description of the command.
Long string

// SetFlags represents function which can be used to set flags.
SetFlags func(flags *FlagSet)

// Run represents a function which wraps and executes the logic of the command.
Run func(cmd *Command, args []string) error

// flags holds set of commandline flags which are bind to this Command.
// To avoid nil pointer exception it is better to work with flags via
// Command.Flags method.
flags *flag.FlagSet
flags *FlagSet

// flagsState holds state of flags initialization.
flagsState sync.Once

// subcommands holds set of Command who are a subcommand to this Command.
subcommands map[string]*Command

// parent holds a pointer to a parent Command.
parent *Command
}
Expand All @@ -35,7 +45,7 @@ func (c *Command) Exec() error {
// If the binary has been named differently that root command.
if !c.IsSubcommand() {
c.Name = filepath.Base(os.Args[0])
flag.CommandLine = c.Flags()
flag.CommandLine = c.Flags().FlagSet
}

// Parse all the program arguments.
Expand Down Expand Up @@ -91,10 +101,15 @@ func (c *Command) TraverseToRoot() *Command {
}

// Flags returns internal *flag.FlagSet to bind flags to.
func (c *Command) Flags() *flag.FlagSet {
func (c *Command) Flags() *FlagSet {
c.flagsState.Do(func() {
c.flags = flag.NewFlagSet(c.Name, flag.ExitOnError)
c.flags = &FlagSet{
FlagSet: flag.NewFlagSet(c.Name, flag.ExitOnError),
}
c.flags.Usage = c.usage
if c.SetFlags != nil {
c.SetFlags(c.flags)
}
})

return c.flags
Expand Down
2 changes: 1 addition & 1 deletion command_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,6 @@ func TestCommand_AddSubcommands(t *testing.T) {

// Additional values for some test cases.
testCmd1 := &Command{Name: "panic-add-self"}

testCmd2 := &Command{Name: "panic-already-attached"}
testCmd3 := &Command{Name: "panic-already-attached_attached"}
testCmd2.AddSubcommands(testCmd3)
Expand Down Expand Up @@ -254,4 +253,5 @@ func helperSetTestingFlags(t *testing.T, cmd *Command) {
cmd.Flags().String("test.testlogfile", "", "")
cmd.Flags().String("test.timeout", "", "")
cmd.Flags().String("test.coverprofile", "", "")
cmd.Flags().String("test.gocoverdir", "", "")
}
94 changes: 94 additions & 0 deletions flagset.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package scotty

import (
"flag"
"fmt"
"os"
"strconv"
"time"
)

// FlagSet wraps flag.FlagSet and adds a few methods like
// StringVarE, BoolVarE and similar methods for other types.
type FlagSet struct{ *flag.FlagSet }

// StringVarE defines a string flag and environment variable with specified name, default value, and usage string.
// The argument p points to a string variable in which to store the value of the flag or environment variable.
// Flag has priority over environment variable. If flag not set the environment variable value will be used.
// If the value of environment variable can't be parsed to destination type the default value will be used.
func (f *FlagSet) StringVarE(p *string, flagName, envName, value, usage string) {
f.FlagSet.StringVar(p, flagName, tern[string](os.Getenv(envName) != "", os.Getenv(envName), value), usage)
}

// BoolVarE defines a bool flag and environment variable with specified name, default value, and usage string.
// The argument p points to a bool variable in which to store the value of the flag or environment variable.
// Flag has priority over environment variable. If flag not set the environment variable value will be used.
// If the value of environment variable can't be parsed to destination type the default value will be used.
func (f *FlagSet) BoolVarE(p *bool, flagName, envName string, value bool, usage string) {
parsed, err := strconv.ParseBool(os.Getenv(envName))
f.FlagSet.BoolVar(p, flagName, tern[bool](err == nil, parsed, value), usage)
}

// IntVarE defines an int flag and environment variable with specified name, default value, and usage string.
// The argument p points to an int variable in which to store the value of the flag or environment variable.
// Flag has priority over environment variable. If flag not set the environment variable value will be used.
// If the value of environment variable can't be parsed to destination type the default value will be used.
func (f *FlagSet) IntVarE(p *int, flagName, envName string, value int, usage string) {
parsed, err := strconv.Atoi(os.Getenv(envName))
f.FlagSet.IntVar(p, flagName, tern[int](err == nil, parsed, value), usage)
}

// Int64VarE defines an int64 flag and environment variable with specified name, default value, and usage string.
// The argument p points to an int64 variable in which to store the value of the flag or environment variable.
// Flag has priority over environment variable. If flag not set the environment variable value will be used.
// If the value of environment variable can't be parsed to destination type the default value will be used.
func (f *FlagSet) Int64VarE(p *int64, flagName, envName string, value int64, usage string) {
parsed, err := strconv.Atoi(os.Getenv(envName))
f.FlagSet.Int64Var(p, flagName, tern[int64](err == nil, int64(parsed), value), usage)
}

// Float64VarE defines a float64 flag and environment variable with specified name, default value, and usage string.
// The argument p points to a float64 variable in which to store the value of the flag or environment variable.
// Flag has priority over environment variable. If flag not set the environment variable value will be used.
// If the value of environment variable can't be parsed to destination type the default value will be used.
func (f *FlagSet) Float64VarE(p *float64, flagName, envName string, value float64, usage string) {
parsed, err := strconv.ParseFloat(os.Getenv(envName), 64)
f.FlagSet.Float64Var(p, flagName, tern[float64](err == nil, parsed, value), usage)
}

// UintVarE defines an uint flag and environment variable with specified name, default value, and usage string.
// The argument p points to an uint variable in which to store the value of the flag or environment variable.
// Flag has priority over environment variable. If flag not set the environment variable value will be used.
// If the value of environment variable can't be parsed to destination type the default value will be used.
func (f *FlagSet) UintVarE(p *uint, flagName, envName string, value uint, usage string) {
parsed, err := strconv.Atoi(os.Getenv(envName))
f.FlagSet.UintVar(p, flagName, tern[uint](err == nil, uint(parsed), value), usage)
}

// Uint64VarE defines an uint64 flag and environment variable with specified name, default value, and usage string.
// The argument p points to an uint64 variable in which to store the value of the flag or environment variable.
// Flag has priority over environment variable. If flag not set the environment variable value will be used.
// If the value of environment variable can't be parsed to destination type the default value will be used.
func (f *FlagSet) Uint64VarE(p *uint64, flagName, envName string, value uint64, usage string) {
parsed, err := strconv.Atoi(os.Getenv(envName))
f.FlagSet.Uint64Var(p, flagName, tern[uint64](err == nil, uint64(parsed), value), usage)
}

// DurationVarE defines a time.Duration flag and environment variable with specified name, default value, and usage string.
// The argument p points to a time.Duration variable in which to store the value of the flag or environment variable.
// Flag has priority over environment variable. If flag not set the environment variable value will be used.
// If the value of environment variable can't be parsed to destination type the default value will be used.
func (f *FlagSet) DurationVarE(p *time.Duration, flagName, envName string, value time.Duration, usage string) {
parsed, err := time.ParseDuration(os.Getenv(envName))
fmt.Println(parsed, err, tern[time.Duration](err == nil, parsed, value))
f.FlagSet.DurationVar(p, flagName, tern[time.Duration](err == nil, parsed, value), usage)
}

//nolint:revive // flag-parameter is ok here.
func tern[T any](cond bool, t, f T) T {
if cond {
return t
}

return f
}
Loading

0 comments on commit 225ffb9

Please sign in to comment.