Skip to content

Commit

Permalink
Merge pull request #453 from essentialkaos/develop
Browse files Browse the repository at this point in the history
Version 12.113.0
  • Loading branch information
andyone authored Mar 30, 2024
2 parents cd01d4a + a2f5786 commit 685e7ea
Show file tree
Hide file tree
Showing 8 changed files with 1,217 additions and 1,113 deletions.
2,046 changes: 1,026 additions & 1,020 deletions CHANGELOG.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion ek.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import (
// ////////////////////////////////////////////////////////////////////////////////// //

// VERSION is current ek package version
const VERSION = "12.112.1"
const VERSION = "12.113.0"

// ////////////////////////////////////////////////////////////////////////////////// //

Expand Down
28 changes: 14 additions & 14 deletions options/example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,17 +62,17 @@ func ExampleParse() {
// Key is option in format "short-name:long-name" or "long-name"
// We highly recommend defining options names as constants
optMap := Map{
"s:string": {}, // By default, argument has string type
"S:string2": {Type: STRING, Value: "Default value"}, // You can predefine default values
"int": {Type: INT}, // Integer without short name
"I:int2": {Type: INT, Min: 1, Max: 10}, // Integer with limits
"f:float": {Type: FLOAT, Value: 10.0}, // Float
"b:boolean": {Type: BOOL}, // Boolean
"r:required": {Type: INT, Required: true}, // Some options can be marked as required
"m:merg": {Type: STRING, Mergeble: true}, // Mergeble options can be defined more than one time
"h:help": {Type: BOOL, Alias: "u:usage about"}, // You can define argument aliases
"e:example": {Conflicts: "s:string S:string2"}, // Option conflicts with string and string2 (options can't be set at same time)
"E:example2": {Bound: "int I:int2"}, // Option bound with int and int2 (options must be set at same time)
"s:string": {}, // By default, argument has string type
"S:string2": {Type: STRING, Value: "Default value"}, // You can predefine default values
"int": {Type: INT}, // Integer without short name
"I:int2": {Type: INT, Min: 1, Max: 10}, // Integer with limits
"f:float": {Type: FLOAT, Value: 10.0}, // Float
"b:boolean": {Type: BOOL}, // Boolean
"r:required": {Type: INT, Required: true}, // Some options can be marked as required
"m:merg": {Type: STRING, Mergeble: true}, // Mergeble options can be defined more than one time
"h:help": {Type: BOOL, Alias: "u:usage about"}, // You can define argument aliases
"e:example": {Conflicts: []string{"s:string", "S:string2"}}, // Option conflicts with string and string2 (options can't be set at same time)
"E:example2": {Bound: "int I:int2"}, // Option bound with int and int2 (options must be set at same time)
}

// args is a slice with command arguments
Expand Down Expand Up @@ -231,15 +231,15 @@ func ExampleV_String() {
fmt.Println(v.String())

// Output:
// Int{Value:25 Min:10 Max:1000 Alias:items Conflicts:E:empty Bound:c:create}
// Int{Value:25 Min:10 Max:1000 Alias:--items Conflicts:-E/--empty Bound:-c/--create}
}

func ExampleMap_String() {
m := Map{
"s:size": &V{
Type: INT,
Value: 25,
Alias: "items",
Alias: []string{"items", "objects"},
Conflicts: "E:empty",
Bound: "c:create",
Min: 10,
Expand All @@ -250,7 +250,7 @@ func ExampleMap_String() {
fmt.Println(m.String())

// Output:
// options.Map[size:Int{Value:25 Min:10 Max:1000 Alias:items Conflicts:E:empty Bound:c:create}]
// options.Map[size:Int{Value:25 Min:10 Max:1000 Alias:[--items --objects] Conflicts:-E/--empty Bound:-c/--create}]
}

// ////////////////////////////////////////////////////////////////////////////////// //
Expand Down
145 changes: 101 additions & 44 deletions options/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ const (
ERROR_CONFLICT
ERROR_BOUND_NOT_SET
ERROR_UNSUPPORTED_VALUE
ERROR_UNSUPPORTED_ALIAS_LIST_FORMAT
ERROR_UNSUPPORTED_CONFLICT_LIST_FORMAT
ERROR_UNSUPPORTED_BOUND_LIST_FORMAT
)

// ////////////////////////////////////////////////////////////////////////////////// //
Expand All @@ -47,9 +50,9 @@ type V struct {
Type uint8 // option type
Max float64 // maximum integer option value
Min float64 // minimum integer option value
Alias string // list of aliases
Conflicts string // list of conflicts options
Bound string // list of bound options
Alias any // string or slice of strings with aliases
Conflicts any // string or slice of strings with conflicts options
Bound any // string or slice of strings with bound options
Mergeble bool // option supports options value merging
Required bool // option is required

Expand Down Expand Up @@ -137,7 +140,11 @@ func (opts *Options) Add(name string, option *V) error {
}

if option.Alias != "" {
aliases := parseOptionsList(option.Alias)
aliases, ok := parseOptionsList(option.Alias)

if !ok {
return OptionError{"--" + optName.Long, "", ERROR_UNSUPPORTED_ALIAS_LIST_FORMAT}
}

for _, l := range aliases {
opts.full[l.Long] = option
Expand Down Expand Up @@ -524,16 +531,7 @@ func ParseOptionName(opt string) (string, string) {

// Format formats option name
func Format(opt string) string {
a := parseName(opt)

switch {
case a.Long == "":
return ""
case a.Short == "":
return "--" + a.Long
default:
return fmt.Sprintf("-%s/--%s", a.Short, a.Long)
}
return parseName(opt).String()
}

// Merge merges several options into string
Expand Down Expand Up @@ -607,16 +605,16 @@ func (v *V) String() string {
result += fmt.Sprintf("Max:%g ", v.Max)
}

if v.Alias != "" {
result += fmt.Sprintf("Alias:%s ", v.Alias)
if v.Alias != nil {
result += fmt.Sprintf("Alias:%v ", formatOptionsList(v.Alias))
}

if v.Conflicts != "" {
result += fmt.Sprintf("Conflicts:%s ", v.Conflicts)
if v.Conflicts != nil {
result += fmt.Sprintf("Conflicts:%v ", formatOptionsList(v.Conflicts))
}

if v.Bound != "" {
result += fmt.Sprintf("Bound:%s ", v.Bound)
if v.Bound != nil {
result += fmt.Sprintf("Bound:%v ", formatOptionsList(v.Bound))
}

if v.Mergeble {
Expand All @@ -632,6 +630,20 @@ func (v *V) String() string {

// ////////////////////////////////////////////////////////////////////////////////// //

// String returns string representation of optionName
func (o optionName) String() string {
switch {
case o.Long == "":
return ""
case o.Short == "":
return "--" + o.Long
}

return fmt.Sprintf("-%s/--%s", o.Short, o.Long)
}

// ////////////////////////////////////////////////////////////////////////////////// //

// I think it is okay to have such a long and complicated method for parsing data
// because it has a lot of logic which can't be separated into different methods
// without losing code readability
Expand Down Expand Up @@ -813,29 +825,37 @@ func (opts *Options) validate() []error {

for n, v := range opts.full {
if !isSupportedType(v.Value) {
errorList = append(errorList, OptionError{n, "", ERROR_UNSUPPORTED_VALUE})
errorList = append(errorList, OptionError{F(n), "", ERROR_UNSUPPORTED_VALUE})
}

if v.Required && v.Value == nil {
errorList = append(errorList, OptionError{n, "", ERROR_REQUIRED_NOT_SET})
errorList = append(errorList, OptionError{F(n), "", ERROR_REQUIRED_NOT_SET})
}

if v.Conflicts != "" {
conflicts := parseOptionsList(v.Conflicts)
conflicts, ok := parseOptionsList(v.Conflicts)

for _, c := range conflicts {
if opts.Has(c.Long) && opts.Has(n) {
errorList = append(errorList, OptionError{n, c.Long, ERROR_CONFLICT})
if !ok {
errorList = append(errorList, OptionError{F(n), "", ERROR_UNSUPPORTED_CONFLICT_LIST_FORMAT})
} else {
for _, c := range conflicts {
if opts.Has(c.Long) && opts.Has(n) {
errorList = append(errorList, OptionError{F(n), F(c.Long), ERROR_CONFLICT})
}
}
}
}

if v.Bound != "" {
bound := parseOptionsList(v.Bound)
bound, ok := parseOptionsList(v.Bound)

for _, b := range bound {
if !opts.Has(b.Long) && opts.Has(n) {
errorList = append(errorList, OptionError{n, b.Long, ERROR_BOUND_NOT_SET})
if !ok {
errorList = append(errorList, OptionError{F(n), "", ERROR_UNSUPPORTED_BOUND_LIST_FORMAT})
} else {
for _, b := range bound {
if !opts.Has(b.Long) && opts.Has(n) {
errorList = append(errorList, OptionError{F(n), F(b.Long), ERROR_BOUND_NOT_SET})
}
}
}
}
Expand All @@ -862,14 +882,45 @@ func parseName(name string) optionName {
return optionName{long, short}
}

func parseOptionsList(list string) []optionName {
func parseOptionsList(list any) ([]optionName, bool) {
var result []optionName

for _, a := range strings.Split(list, " ") {
result = append(result, parseName(a))
switch t := list.(type) {
case nil:
return nil, true

case string:
for _, a := range strings.Split(t, MergeSymbol) {
result = append(result, parseName(a))
}

case []string:
for _, a := range t {
result = append(result, parseName(a))
}

default:
return nil, false
}

return result, true
}

func formatOptionsList(list any) string {
opts, ok := parseOptionsList(list)

if !ok {
return "{InvalidList}"
}

switch len(opts) {
case 0:
return "{Empty}"
case 1:
return opts[0].String()
}

return result
return fmt.Sprintf("%v", opts)
}

func updateOption(opt *V, name, value string) error {
Expand All @@ -887,7 +938,7 @@ func updateOption(opt *V, name, value string) error {
return updateIntOption(name, opt, value)
}

return fmt.Errorf("Option %s has unsupported type", Format(name))
return fmt.Errorf("Option %q has unsupported type", Format(name))
}

func updateStringOption(opt *V, value string) error {
Expand Down Expand Up @@ -1017,23 +1068,29 @@ func guessType(v any) uint8 {
func (e OptionError) Error() string {
switch e.Type {
default:
return fmt.Sprintf("Option %s is not supported", e.Option)
return fmt.Sprintf("Option %q is not supported", e.Option)
case ERROR_EMPTY_VALUE:
return fmt.Sprintf("Non-boolean option %s is empty", e.Option)
return fmt.Sprintf("Non-boolean option %q is empty", e.Option)
case ERROR_REQUIRED_NOT_SET:
return fmt.Sprintf("Required option %s is not set", e.Option)
return fmt.Sprintf("Required option %q is not set", e.Option)
case ERROR_WRONG_FORMAT:
return fmt.Sprintf("Option %s has wrong format", e.Option)
return fmt.Sprintf("Option %q has wrong format", e.Option)
case ERROR_OPTION_IS_NIL:
return fmt.Sprintf("Struct for option %s is nil", e.Option)
return fmt.Sprintf("Struct for option %q is nil", e.Option)
case ERROR_DUPLICATE_LONGNAME, ERROR_DUPLICATE_SHORTNAME:
return fmt.Sprintf("Option %s defined 2 or more times", e.Option)
return fmt.Sprintf("Option %q defined 2 or more times", e.Option)
case ERROR_CONFLICT:
return fmt.Sprintf("Option %s conflicts with option %s", e.Option, e.BoundOption)
return fmt.Sprintf("Option %q conflicts with option %q", e.Option, e.BoundOption)
case ERROR_BOUND_NOT_SET:
return fmt.Sprintf("Option %s must be defined with option %s", e.BoundOption, e.Option)
return fmt.Sprintf("Option %q must be defined with option %q", e.BoundOption, e.Option)
case ERROR_UNSUPPORTED_VALUE:
return fmt.Sprintf("Option %s contains unsupported default value", e.Option)
return fmt.Sprintf("Option %q contains unsupported default value", e.Option)
case ERROR_UNSUPPORTED_ALIAS_LIST_FORMAT:
return fmt.Sprintf("Option %q contains unsupported list format of aliases", e.Option)
case ERROR_UNSUPPORTED_CONFLICT_LIST_FORMAT:
return fmt.Sprintf("Option %q contains unsupported list format of conflicting options", e.Option)
case ERROR_UNSUPPORTED_BOUND_LIST_FORMAT:
return fmt.Sprintf("Option %q contains unsupported list format of bound options", e.Option)
}
}

Expand Down
Loading

0 comments on commit 685e7ea

Please sign in to comment.