Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
  • master
  • v1.0.0
  • v1.1.0
  • v1.1.1
  • v1.10.0
  • v1.10.1
  • v1.10.2
  • v1.11.0
  • v1.12.0
  • v1.13.0
  • v1.13.1
  • v1.13.2
  • v1.14.0
  • v1.15.0
  • v1.16.0
  • v1.16.1
  • v1.16.2
  • v1.16.3
  • v1.16.4
  • v1.16.5
  • v1.2.0
  • v1.2.1
  • v1.2.2
  • v1.2.3
  • v1.3.0
  • v1.3.1
  • v1.4.0
  • v1.5.0
  • v1.6.0
  • v1.7.0
  • v1.8.0
  • v1.8.1
  • v1.8.2
  • v1.8.3
  • v1.9.0
35 results

Target

Select target project
  • oss/libraries/go/application/xflags
1 result
Select Git revision
  • master
  • v1.0.0
  • v1.1.0
  • v1.1.1
  • v1.10.0
  • v1.10.1
  • v1.10.2
  • v1.11.0
  • v1.12.0
  • v1.13.0
  • v1.13.1
  • v1.13.2
  • v1.14.0
  • v1.15.0
  • v1.16.0
  • v1.16.1
  • v1.16.2
  • v1.16.3
  • v1.16.4
  • v1.16.5
  • v1.2.0
  • v1.2.1
  • v1.2.2
  • v1.2.3
  • v1.3.0
  • v1.3.1
  • v1.4.0
  • v1.5.0
  • v1.6.0
  • v1.7.0
  • v1.8.0
  • v1.8.1
  • v1.8.2
  • v1.8.3
  • v1.9.0
35 results
Show changes
Commits on Source (4)
<a name="v1.5.0"></a>
## [v1.5.0] - 2022-10-13
### Add Features
- feat new opportunities for interaction with flags [#1](https://gitlab.schukai.com/oss/libraries/go/application/xflags/issues/1)
### Changes
- chore add license
<a name="v1.4.0"></a> <a name="v1.4.0"></a>
## [v1.4.0] - 2022-10-09 ## [v1.4.0] - 2022-10-09
### Add Features ### Add Features
...@@ -59,6 +68,7 @@ ...@@ -59,6 +68,7 @@
<a name="v1.0.0"></a> <a name="v1.0.0"></a>
## v1.0.0 - 2022-10-04 ## v1.0.0 - 2022-10-04
[v1.5.0]: https://gitlab.schukai.com/oss/libraries/go/application/xflags/compare/v1.4.0...v1.5.0
[v1.4.0]: https://gitlab.schukai.com/oss/libraries/go/application/xflags/compare/v1.3.1...v1.4.0 [v1.4.0]: https://gitlab.schukai.com/oss/libraries/go/application/xflags/compare/v1.3.1...v1.4.0
[v1.3.1]: https://gitlab.schukai.com/oss/libraries/go/application/xflags/compare/v1.3.0...v1.3.1 [v1.3.1]: https://gitlab.schukai.com/oss/libraries/go/application/xflags/compare/v1.3.0...v1.3.1
[v1.3.0]: https://gitlab.schukai.com/oss/libraries/go/application/xflags/compare/v1.2.3...v1.3.0 [v1.3.0]: https://gitlab.schukai.com/oss/libraries/go/application/xflags/compare/v1.2.3...v1.3.0
......
...@@ -63,9 +63,9 @@ The following tags are supported: ...@@ -63,9 +63,9 @@ The following tags are supported:
| `long` | Value | Long name of the flag. | | `long` | Value | Long name of the flag. |
| `description` | Value | Description of the flag. | | `description` | Value | Description of the flag. |
| `required` | Value | Flag is required. | | `required` | Value | Flag is required. |
| `shadow` | Value | Copy the value to the shadow structure. |
| `command` | Command | Flag is a command. | | `command` | Command | Flag is a command. |
| `call` | Command | Function to call when the command is used. | | `call` | Command | Function to call when the command is used. |
| `shadow` | Value | Copy the value to the shadow structure. |
| `ignore` | -/- | Property is ignored. | | `ignore` | -/- | Property is ignored. |
### Callbacks ### Callbacks
...@@ -170,6 +170,19 @@ func main() { ...@@ -170,6 +170,19 @@ func main() {
} }
``` ```
### Arguments
the free arguments can be fetched with the method `Args()`.
### Check Status
The execution result can be queried with the functions:
- `HelpRequested() bool`
- `WasExecuted() bool`
- `Error() error`
- `MissingCommand() bool`
## Contributing ## Contributing
Merge requests are welcome. For major changes, please open an issue first to discuss what Merge requests are welcome. For major changes, please open an issue first to discuss what
......
...@@ -12,25 +12,52 @@ import ( ...@@ -12,25 +12,52 @@ import (
"reflect" "reflect"
) )
// ExecuteWithShadow executes the command line arguments and calls the functions.
func ExecuteWithShadow[C any, D any](cmd C, cnf D) *Settings[C] {
s := execute(cmd, cnf, os.Args[0], os.Args[1:])
if s.HasErrors() {
for _, e := range s.Errors() {
fmt.Println(e)
}
}
return s
}
type noShadow struct{}
// Execute executes the command line arguments and calls the functions. // Execute executes the command line arguments and calls the functions.
func Execute[C any, D any](cmd C, cnf D) *Settings[C] { func Execute[C any](cmd C) *Settings[C] {
return execute(cmd, cnf, os.Args[0], os.Args[1:]) s := execute(cmd, noShadow{}, os.Args[0], os.Args[1:])
if s.HasErrors() {
for _, e := range s.Errors() {
fmt.Println(e)
}
}
if s.hint != "" {
fmt.Println(s.hint)
}
return s
} }
func (s *Settings[C]) PrintFlagOutput() { func (s *Settings[C]) PrintFlagOutput() {
fmt.Println(s.command.flagSet.Output()) fmt.Println(s.command.flagSet.Output())
} }
// execute is the internal implementation of Execute. // execute is the internal implementation of ExecuteWithShadow.
func execute[C any, D any](cmd C, cnf D, name string, args []string) *Settings[C] { func execute[C any, D any](cmd C, cnf D, name string, args []string) *Settings[C] {
instance := New(name, cmd) instance := New(name, cmd)
if instance.HasErrors() { if instance.HasErrors() {
return instance return instance
} }
instance.SetShadow(cnf) if (reflect.ValueOf(&cnf).Elem().Type() != reflect.TypeOf(noShadow{})) {
if instance.HasErrors() { instance.SetShadow(cnf)
return instance if instance.HasErrors() {
return instance
}
} }
instance.Parse(args) instance.Parse(args)
...@@ -40,7 +67,6 @@ func execute[C any, D any](cmd C, cnf D, name string, args []string) *Settings[C ...@@ -40,7 +67,6 @@ func execute[C any, D any](cmd C, cnf D, name string, args []string) *Settings[C
instance.errors = append(instance.errors[:i], instance.errors[i+1:]...) instance.errors = append(instance.errors[:i], instance.errors[i+1:]...)
} }
} }
instance.PrintFlagOutput()
return instance return instance
} }
...@@ -49,6 +75,10 @@ func execute[C any, D any](cmd C, cnf D, name string, args []string) *Settings[C ...@@ -49,6 +75,10 @@ func execute[C any, D any](cmd C, cnf D, name string, args []string) *Settings[C
} }
instance.Execute() instance.Execute()
if instance.HasErrors() {
return instance
}
return instance return instance
} }
......
...@@ -21,9 +21,16 @@ func TestUsage(t *testing.T) { ...@@ -21,9 +21,16 @@ func TestUsage(t *testing.T) {
assert.Equal(t, " -a\tMessage A\n -x int\n \tMessage X\n", usage) assert.Equal(t, " -a\tMessage A\n -x int\n \tMessage X\n", usage)
} }
func TestExecute(t *testing.T) { func TestExecuteTypeStringIsNotSupported(t *testing.T) {
instance := execute("root", CmdTest1{}, "test", []string{"-a", "hello", "-x", "1"}) instance := execute("root", CmdTest1{}, "test", []string{"-a", "hello", "-x", "1"})
assert.NotNil(t, instance)
err := instance.Errors()
assert.True(t, instance.HasErrors())
assert.Equal(t, 1, len(err))
e := err[0]
assert.Equal(t, "type string is not supported", e.Error())
} }
func TestExecuteHelp(t *testing.T) { func TestExecuteHelp(t *testing.T) {
......
...@@ -20,6 +20,14 @@ func (s *Settings[C]) HasErrors() bool { ...@@ -20,6 +20,14 @@ func (s *Settings[C]) HasErrors() bool {
return len(s.errors) > 0 return len(s.errors) > 0
} }
func (s *Settings[C]) HasHint() bool {
return s.hint != ""
}
func (s *Settings[C]) GetHint() string {
return s.hint
}
// Get all errors // Get all errors
func (s *Settings[C]) Errors() []error { func (s *Settings[C]) Errors() []error {
return s.errors return s.errors
...@@ -112,3 +120,9 @@ type StdoutError error ...@@ -112,3 +120,9 @@ type StdoutError error
func newStdoutError(message string) StdoutError { func newStdoutError(message string) StdoutError {
return StdoutError(errors.New(message)) return StdoutError(errors.New(message))
} }
type MissingFunctionError error
func newMissingFunctionError(missing string) MissingFunctionError {
return MissingFunctionError(errors.New("missing function " + missing))
}
...@@ -90,3 +90,9 @@ func TestStdoutError(t *testing.T) { ...@@ -90,3 +90,9 @@ func TestStdoutError(t *testing.T) {
_, ok := err.(StdoutError) _, ok := err.(StdoutError)
assert.True(t, ok) assert.True(t, ok)
} }
func TestMissingFunctionError(t *testing.T) {
err := newMissingFunctionError("test")
_, ok := err.(MissingFunctionError)
assert.True(t, ok)
}
...@@ -4,7 +4,10 @@ ...@@ -4,7 +4,10 @@
package xflags package xflags
import ( import (
"flag"
"fmt"
"reflect" "reflect"
"strings"
) )
func (s *Settings[C]) Execute() *Settings[C] { func (s *Settings[C]) Execute() *Settings[C] {
...@@ -20,25 +23,97 @@ func (s *Settings[C]) Execute() *Settings[C] { ...@@ -20,25 +23,97 @@ func (s *Settings[C]) Execute() *Settings[C] {
if !s.command.flagSet.Parsed() { if !s.command.flagSet.Parsed() {
s.errors = append(s.errors, NotParsedError) s.errors = append(s.errors, NotParsedError)
} else { } else {
callCmdFunctions(s.command.commands) s.wasExecuted = callCmdFunctions(s, s.command.commands)
} }
return s return s
} }
func callCmdFunctions[C any](commands []*cmd[C]) { func callCmdFunctions[C any](settings *Settings[C], commands []*cmd[C]) bool {
//result := false
wasExecuted := false
shouldExecute := false
var availableCommands []string
currentCommand := ""
for _, command := range commands { for _, command := range commands {
if command.flagSet.Parsed() { if command.flagSet.Parsed() {
f := reflect.ValueOf(&command.settings.definitions).MethodByName(command.functionName) shouldExecute = true
if f.IsValid() { currentCommand = command.name
m := command.settings
in := []reflect.Value{reflect.ValueOf(m)} if len(command.commands) > 0 {
f.Call(in) r := callCmdFunctions(settings, command.commands)
if r {
wasExecuted = true
}
} }
callCmdFunctions(command.commands) if !wasExecuted {
f := reflect.ValueOf(&command.settings.definitions).MethodByName(command.functionName)
if f.IsValid() {
m := command.settings
in := []reflect.Value{reflect.ValueOf(m)}
f.Call(in)
wasExecuted = true
}
}
break
} else {
availableCommands = append(availableCommands, command.name)
}
}
if shouldExecute {
if !wasExecuted {
settings.errors = append(settings.errors, newMissingFunctionError(currentCommand))
return false
}
return true
}
if len(availableCommands) > 0 {
if settings.hint == "" {
settings.hint = fmt.Sprintf("Did you mean: %v?", strings.Join(availableCommands, ", "))
}
}
settings.errors = append(settings.errors, MissingCommandError)
return false
}
// HelpRequested indicates if the help flag was set.
func (s *Settings[C]) HelpRequested() bool {
for _, err := range s.errors {
if err == flag.ErrHelp {
return true
} }
} }
return false
}
// MissingCommandError is returned if no command was found.
func (s *Settings[C]) MissingCommand() bool {
for _, err := range s.errors {
if err == MissingCommandError {
return true
}
}
return false
}
// WasExecuted returns true if the call function was executed
func (s *Settings[C]) WasExecuted() bool {
return s.wasExecuted
} }
...@@ -44,6 +44,7 @@ type testExecutionTestCases[C any] struct { ...@@ -44,6 +44,7 @@ type testExecutionTestCases[C any] struct {
name string name string
args []string args []string
targetValue int targetValue int
hasErrors bool
} }
func TestExec(t *testing.T) { func TestExec(t *testing.T) {
...@@ -53,19 +54,22 @@ func TestExec(t *testing.T) { ...@@ -53,19 +54,22 @@ func TestExec(t *testing.T) {
name: "test", name: "test",
args: []string{"command1", "-c"}, args: []string{"command1", "-c"},
targetValue: 1, targetValue: 1,
hasErrors: true,
}, },
{ {
name: "test", name: "test",
args: []string{"command1", "command2", "-e"}, args: []string{"command1", "command2", "-e"},
targetValue: 2, targetValue: 1,
hasErrors: true,
}, { }, {
name: "test", name: "test",
args: []string{"-a", "command3"}, args: []string{"-a", "command3"},
targetValue: 1, targetValue: 1,
hasErrors: true,
}, },
} }
for _, tt := range tests { for i, tt := range tests {
t.Run(tt.args[0], func(t *testing.T) { t.Run(tt.args[0], func(t *testing.T) {
settings := New(tt.name, testExecutionStruct{}) settings := New(tt.name, testExecutionStruct{})
assert.NotNil(t, settings) assert.NotNil(t, settings)
...@@ -77,9 +81,10 @@ func TestExec(t *testing.T) { ...@@ -77,9 +81,10 @@ func TestExec(t *testing.T) {
settings.Parse(tt.args) settings.Parse(tt.args)
settings.Execute() settings.Execute()
if settings.HasErrors() { if !tt.hasErrors && settings.HasErrors() {
t.Error("Should not have errors") t.Error("run " + strconv.Itoa(i) + ": should not have errors but has: " + strconv.Itoa(len(settings.Errors())))
t.Log(settings.Errors()) t.Log(settings.Errors())
return
} }
if settings.definitions.callbackCounter != tt.targetValue { if settings.definitions.callbackCounter != tt.targetValue {
......
...@@ -21,17 +21,20 @@ type testExecuteCommandStruct struct { ...@@ -21,17 +21,20 @@ type testExecuteCommandStruct struct {
} `command:"command1" description:"Command 1" callback:"command1Callback" ` } `command:"command1" description:"Command 1" callback:"command1Callback" `
Command2 struct { Command2 struct {
Command3 struct { Command3 struct {
} `command:"command3" description:"Command 3" callback:"command3Callback" ` } `command:"command3" description:"Command 3" callback:"command3Callback" call:"DoCmd3"`
} `command:"command2" description:"Command 2" callback:"command2Callback" ` } `command:"command2" description:"Command 2" callback:"command2Callback" `
} }
func (c *testExecuteCommandStruct) DoCmd3(s *Settings[testExecuteCommandStruct]) {
}
func (c *testExecuteCommandStruct) command1Callback(args []string) { func (c *testExecuteCommandStruct) command1Callback(args []string) {
fmt.Println("command1Callback", args) fmt.Println("command1Callback", args)
} }
func TestExecute1(t *testing.T) { func TestExecute1(t *testing.T) {
c := New("root", testExecuteCommandStruct{}) c := New("root", testExecuteCommandStruct{})
c.Parse([]string{"root", "command2", "command3", "commandX"}) c.Parse([]string{"command2", "command3", "commandX"})
c.Execute() c.Execute()
assert.False(t, c.HasErrors()) assert.False(t, c.HasErrors())
} }
...@@ -5,6 +5,7 @@ go 1.19 ...@@ -5,6 +5,7 @@ go 1.19
require github.com/stretchr/testify v1.8.0 require github.com/stretchr/testify v1.8.0
require ( require (
github.com/agnivade/levenshtein v1.1.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
......
// Copyright 2022 schukai GmbH
// SPDX-License-Identifier: AGPL-3.0
package xflags
import (
"strconv"
"strings"
"testing"
"github.com/stretchr/testify/assert"
)
type testCmdStructIssue1 struct {
Cmd1 struct {
Cmd2 struct {
Cmd3 struct {
V3 bool `short:"v3"`
} `command:"cmd3" call:"DoCmd3"`
} `command:"cmd2"`
Cmd4 struct {
Cmd5 struct {
} `command:"cmd5" call:"DoCmd5"`
} `command:"cmd4"`
Cmd6 struct {
Cmd7 struct {
} `command:"cmd7"`
} `command:"cmd6"`
} `command:"cmd1"`
}
func (c *testCmdStructIssue1) DoCmd3(s *Settings[testCmdStructIssue1]) {
}
func (c *testCmdStructIssue1) DoCmd5(s *Settings[testCmdStructIssue1]) {
}
type testStructIssue1Config struct {
}
// here it is tested whether the last and defined callback is called
func TestIssue1TestCallCMD4(tp *testing.T) {
testData := []struct {
args []string
isExecuted bool
hasErrors bool
hasHint bool
}{
{[]string{"cmd1"}, false, true, true},
{[]string{}, false, true, true},
{[]string{"cmd1", "cmd2"}, false, true, true},
{[]string{"cmd1", "cmd2", "cmd3"}, true, false, false},
{[]string{"cmd1", "cmd4", "cmd5"}, true, false, false},
{[]string{"cmd1"}, false, true, true},
{[]string{"cmd1", "cmd6"}, false, true, true},
{[]string{"cmd1", "cmd6", "cmd7"}, false, true, false},
}
for i, tt := range testData {
tp.Run(strconv.Itoa(i)+":"+strings.Join(tt.args, ","), func(t *testing.T) {
s := execute(testCmdStructIssue1{}, noShadow{}, "test", tt.args)
assert.Equal(t, tt.hasErrors, s.HasErrors())
assert.Equal(t, tt.hasHint, s.HasHint())
if !tt.hasErrors && s.HasErrors() {
t.Log(s.Errors())
}
assert.Equal(t, tt.isExecuted, s.WasExecuted())
})
}
}
func TestIssue1TestNoCallback(t *testing.T) {
s := execute(testCmdStructIssue1{}, noShadow{}, "test", []string{"cmd1", "cmd6", "cmd7"})
assert.Equal(t, 3, len(s.Errors()))
assert.False(t, s.WasExecuted())
}
func TestIssue1TestToMuchCommands(t *testing.T) {
s := execute(testCmdStructIssue1{}, noShadow{}, "test", []string{"cmd1", "cmd9"})
assert.True(t, s.MissingCommand())
assert.False(t, s.WasExecuted())
}
// here it is tested whether the last and defined callback is called
func TestIssue1TestCallCMD3(t *testing.T) {
s := execute(testCmdStructIssue1{}, noShadow{}, "test", []string{"cmd1", "cmd2", "cmd3"})
assert.Equal(t, 0, len(s.Errors()))
assert.True(t, s.WasExecuted())
}
// NoShadow is an internal Struct for testing
func TestIssue1MessageWithNoShadow(t *testing.T) {
s := execute(testCmdStructIssue1{}, noShadow{}, "test", []string{"cmd1", "cmd2", "cmd3", "-v3"})
assert.Equal(t, 0, len(s.Errors()))
}
func TestIssue1MRequestHelp(t *testing.T) {
s := New("test", testCmdStructIssue1{})
s.Parse([]string{"cmd1", "cmd2", "--help"})
assert.True(t, s.HelpRequested())
}
func TestIssue1A(t *testing.T) {
s := New("test", testCmdStructIssue1{})
s.Parse([]string{"cmd1", "cmd2", "cmd3", "-v3"})
assert.False(t, s.HelpRequested())
assert.Equal(t, 0, len(s.Errors()))
assert.True(t, s.GetValues().Cmd1.Cmd2.Cmd3.V3)
}
func TestIssue1HelpRequested(t *testing.T) {
s := New("test", testCmdStructIssue1{})
s.Parse([]string{"cmd1", "-h"})
assert.True(t, s.HelpRequested())
assert.Equal(t, 1, len(s.Errors()))
}
func TestIssue1Summary(tp *testing.T) {
data := []struct {
args []string
errorCount int
helpRequested bool
wasExecuted bool
missingCommand bool
}{
{[]string{"cmd1", "cmd2", "cmd3", "-v3"}, 0, false, true, false},
}
for _, tt := range data {
tp.Run(tt.args[0], func(t *testing.T) {
s := execute(testCmdStructIssue1{}, noShadow{}, "test", tt.args)
assert.Equal(t, tt.helpRequested, s.HelpRequested())
assert.Equal(t, tt.wasExecuted, s.WasExecuted())
assert.Equal(t, tt.missingCommand, s.MissingCommand())
assert.Equal(t, tt.errorCount, len(s.Errors()))
})
}
}
func TestIssue1B(t *testing.T) {
s := New("test", testCmdStructIssue1{})
s.Parse([]string{"cmd1", "cmd2", "cmd3"})
assert.False(t, s.HelpRequested())
assert.Equal(t, 0, len(s.Errors()))
assert.False(t, s.GetValues().Cmd1.Cmd2.Cmd3.V3)
}
...@@ -4,7 +4,6 @@ ...@@ -4,7 +4,6 @@
package xflags package xflags
import ( import (
"flag"
"os" "os"
) )
...@@ -41,14 +40,3 @@ func (s *Settings[C]) Parse(args []string) *Settings[C] { ...@@ -41,14 +40,3 @@ func (s *Settings[C]) Parse(args []string) *Settings[C] {
return s return s
} }
func (s *Settings[C]) HelpRequested() bool {
for _, err := range s.errors {
if err == flag.ErrHelp {
return true
}
}
return false
}
{"version":"1.4.0"} {"version":"1.5.0"}
...@@ -29,7 +29,10 @@ type Settings[C any] struct { ...@@ -29,7 +29,10 @@ type Settings[C any] struct {
config config config config
shadow any shadow any
wasExecuted bool
hint string
} }
func (s *Settings[C]) GetValues() C { func (s *Settings[C]) GetValues() C {
......