diff --git a/getopt.go b/getopt.go new file mode 100644 index 0000000..5536a76 --- /dev/null +++ b/getopt.go @@ -0,0 +1,143 @@ +package getopt + +import ( + "fmt" + "os" + "strconv" +) + +type flag struct { + name string + shortname string + description string + required bool + value interface{} +} + +type GetoptInterface interface { + StringLong(name *string, shortname *rune, required bool, description string) **string + Usage() + BoolLong(name *string, shortname *rune, description string) *bool + NoOptionArgs() []string + Parse([]string) + ParseArgv() + UInt64Long(name *string, shortname *rune, required bool, description string) **uint64 + CheckRequiredOptions() (bool, string, string) +} + +type Getopt struct { + array_flags []*flag + flags map[string]*flag + flagsByShortname map[string]*flag + commonArgs []string +} + +func New() Getopt { + return Getopt{ + array_flags: make([]*flag, 0), + commonArgs: make([]string, 0), + flags: make(map[string]*flag), + flagsByShortname: make(map[string]*flag), + } +} + +func (self *Getopt) CheckRequiredOptions() (bool, string, string) { + for _, value := range self.array_flags { + switch value.value.(interface{}).(type) { + case *bool: + continue + case **string: + if value.required && *value.value.(**string) == nil { + return false, value.name, value.shortname + } + + case **uint64: + if value.required && *value.value.(**uint64) == nil { + return false, value.name, value.shortname + } + } + } + return true, "", "" +} + +func (self *Getopt) StringLong(name *string, shortname *rune, required bool, description string) **string { + var value *string = nil + flagToAssign := flag{self.nilToEmptyString(name), self.nilRuneToEmptyString(shortname), description, required, &value} + if name != nil { + self.flags[*name] = &flagToAssign + } + if shortname != nil { + self.flagsByShortname[string(*shortname)] = &flagToAssign + } + self.array_flags = append(self.array_flags, &flagToAssign) + return &value +} + +func (self *Getopt) Usage() { + fmt.Print(self.UsageString()) +} + +func (self *Getopt) UsageString() string { + var return_string string + var usage [][]string + for _, value := range self.array_flags { + usage = append(usage, self.usageOption(*value)) + } + max := self.maxLen(usage) + for _, value := range usage { + for j, j_value := range value { + return_string += fmt.Sprintf("\t%-"+strconv.FormatInt(max[j], 10)+"s", j_value) + } + return_string += fmt.Sprintf("\n") + } + return return_string +} + +func (self *Getopt) BoolLong(name *string, shortname *rune, description string) *bool { + value := false + flagToAssign := flag{self.nilToEmptyString(name), self.nilRuneToEmptyString(shortname), description, true, &value} + if name != nil { + self.flags[*name] = &flagToAssign + } + if shortname != nil { + self.flagsByShortname[string(*shortname)] = &flagToAssign + } + self.array_flags = append(self.array_flags, &flagToAssign) + return &value +} + +func (self *Getopt) UInt64Long(name *string, shortname *rune, required bool, description string) **uint64 { + var value *uint64 = nil + flagToAssign := flag{self.nilToEmptyString(name), self.nilRuneToEmptyString(shortname), description, required, &value} + if name != nil { + self.flags[*name] = &flagToAssign + } + if shortname != nil { + self.flagsByShortname[string(*shortname)] = &flagToAssign + } + self.array_flags = append(self.array_flags, &flagToAssign) + return &value +} + +func (self *Getopt) ParseArgv() { + args := os.Args[1:] + self.Parse(args) +} + +func (self *Getopt) Parse(args []string) { + expectingArgument := false + var expectingFor string + short := false + for _, value := range args { + if !expectingArgument { + self.parseNotOptionArgument(value, &expectingArgument, &expectingFor, &short) + } else { + self.parseOptionArgument(value, expectingFor, short) + expectingArgument = false + } + } +} + +func (self *Getopt) NoOptionArgs() []string { + return self.commonArgs +} diff --git a/getopt_test/getopt_test.go b/getopt_test/getopt_test.go new file mode 100644 index 0000000..5ce1f42 --- /dev/null +++ b/getopt_test/getopt_test.go @@ -0,0 +1,115 @@ +package getopt_test + +import ( + g "getopt" + "regexp" + "testing" + "reflect" +) + +func TestStringArgNull(t *testing.T) { + getopt := g.New() + hello := getopt.StringLong(s("hello"), nil, false, "Try") + getopt.Parse([]string{}) + if hello == nil { + t.Error("Unable to dereference hello") + } + if *hello != nil { + t.Error("Hola was not set to nil") + } +} + +func TestStringArgNotNull(t *testing.T) { + getopt := g.New() + hello := getopt.StringLong(s("hello"), nil, false, "Try") + getopt.Parse([]string{"--hello", "hello"}) + if hello == nil { + t.Error("Unable to dereference hello") + } + if *hello == nil { + t.Error("--hello is not received.") + } + if **hello != "hello" { + t.Error("Unexpected value in --hello " + **hello + ".") + } +} + +func TestStringArgNotNullWithRune(t *testing.T) { + getopt := g.New() + hello := getopt.StringLong(nil, r('h'), false, "Try") + getopt.Parse([]string{"-hhello"}) + if hello == nil { + t.Error("Unable to dereference hello") + } + if *hello == nil { + t.Error("-h is not received.") + } + if **hello != "hello" { + t.Error("Unexpected value in --hello " + **hello + ".") + } +} + +func TestStringArgNotNullWithRuneUsingExternalArgument(t *testing.T) { + getopt := g.New() + hello := getopt.StringLong(nil, r('h'), false, "Try") + getopt.Parse([]string{"-h", "hello"}) + if hello == nil { + t.Error("Unable to dereference hello") + } + if *hello == nil { + t.Error("-h is not received.") + } + if **hello != "hello" { + t.Error("Unexpected value in --hello " + **hello + ".") + } +} + +func TestParsingNonOptionAndNotOptionArgument(t *testing.T) { + getopt := g.New() + getopt.StringLong(nil, r('h'), false, "Try") + getopt.Parse([]string{"-h", "a", "hello", "world"}) + args := getopt.NoOptionArgs() + if args[0] != "hello" { + t.Error("Argument hello not received.") + } + if args[1] != "world" { + t.Error("Argument world not received.") + } +} + +func TestParsingBoolOptionFalse(t *testing.T) { + getopt := g.New() + h := getopt.BoolLong(nil, r('h'), "Try") + getopt.Parse([]string{""}) + if *h { + t.Error("Unexpected value of -h.") + } +} + +func TestParsingBoolOptionTrue(t *testing.T) { + getopt := g.New() + h := getopt.BoolLong(nil, r('h'), "Try") + getopt.Parse([]string{"-h"}) + if !*h { + t.Error("Unexpected value of -h.") + } +} + +func TestParsingBoolOptionUsage(t *testing.T) { + getopt := g.New() + getopt.BoolLong(s("help"), r('h'), "Try") + getopt.Parse([]string{""}) + result_string := getopt.UsageString() + expected := []string{"--help", "-h", "(required)", "Try"} + re := regexp.MustCompile(`\t(\S+)\t(\S+)\t\t(\S+)\t(\S+)`) + if reflect.DeepEqual(re.FindStringSubmatch(result_string), expected) { + t.Error("Unmatched expected and result.") + } +} +func s(a string) *string { + return &a +} + +func r(a rune) *rune { + return &a +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..9314a61 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module getopt + +go 1.15 diff --git a/private.go b/private.go new file mode 100644 index 0000000..203da9c --- /dev/null +++ b/private.go @@ -0,0 +1,156 @@ +package getopt + +import ( + "fmt" + "log" + "os" + "regexp" + "strconv" +) + +func (self *Getopt) usageOption(flagToStringify flag) []string { + var printArgument bool + switch flagToStringify.value.(interface{}).(type) { + case *bool: + printArgument = false + default: + printArgument = true + } + var returned []string + if flagToStringify.shortname != "" { + returned = append(returned, "-"+flagToStringify.shortname) + } else { + returned = append(returned, "") + } + if flagToStringify.name != "" { + returned = append(returned, "--"+flagToStringify.name) + } else { + returned = append(returned, "") + } + if printArgument { + returned = append(returned, "") + } else { + returned = append(returned, "") + } + + if flagToStringify.required { + returned = append(returned, "(required)") + } else { + returned = append(returned, "") + } + + returned = append(returned, flagToStringify.description) + return returned +} + +func (self *Getopt) maxLen(lol [][]string) []int64 { + var returned []int64 = make([]int64, 5) + for _, i_val := range lol { + for j, j_val := range i_val { + if returned[j] < int64(len(j_val)) { + returned[j] = int64(len(j_val)) + } + } + } + return returned +} + +func (self *Getopt) parseOptionArgument(value string, expectingFor string, short bool) { + var gotFlag *flag + var ok bool + if short { + gotFlag, ok = self.flagsByShortname[expectingFor] + } else { + gotFlag, ok = self.flags[expectingFor] + } + if !ok { + fmt.Printf("Error retrieving the key, this IS a bug. %s\n", expectingFor) + } + self.assignToFlag(gotFlag, value) + +} + +func (self *Getopt) parseNotOptionArgument(currentIteration string, expectingArgument *bool, expectingFor *string, short *bool) { + matchName, err := regexp.MatchString("^--", currentIteration) + if err != nil { + log.Panic(err) + } + matchShortname, err := regexp.MatchString("^-", currentIteration) + if err != nil { + log.Panic(err) + } + if matchName { + *short = false + self.parseOption(currentIteration, expectingFor, expectingArgument, `^--([^=]*)(?:=(.*))?$`, self.flags) + } else if matchShortname { + *short = true + self.parseOption(currentIteration, expectingFor, expectingArgument, `^-(.)(.+)?$`, self.flagsByShortname) + } else { + self.commonArgs = append(self.commonArgs, currentIteration) + } +} + +func (self *Getopt) parseOption(currentIteration string, expectingFor *string, expectingArgument *bool, option_regex string, flags map[string]*flag) { + re := regexp.MustCompile(option_regex) + listName := re.FindStringSubmatch(currentIteration) + name := listName[1] + gotFlag, ok := flags[name] + if !ok { + fmt.Printf("%s not in the option list.\n", name) + self.Usage() + os.Exit(1) + } + var nextParameter bool + switch gotFlag.value.(interface{}).(type) { + case *bool: + nextParameter = false + *(gotFlag.value.(*bool)) = true + default: + nextParameter = true + } + if listName[2] != "" { + if !nextParameter { + fmt.Printf("%s does not get any parameters.\n", name) + self.Usage() + os.Exit(1) + } + gotParameter := listName[2] + self.assignToFlag(gotFlag, gotParameter) + } else if nextParameter { + *expectingArgument = true + *expectingFor = name + } +} + +func (self *Getopt) assignToFlag(toAssignFlag *flag, value string) { + switch toAssignFlag.value.(interface{}).(type) { + case **string: + *(toAssignFlag.value.(**string)) = &value + case **uint64: + value_uint, err := strconv.ParseUint(value, 10, 64) + if err != nil { + fmt.Printf("%s is not parseable into a unsigned interger of 64 bits.\n", value) + self.Usage() + os.Exit(1) + } + *(toAssignFlag.value.(**uint64)) = &value_uint + default: + fmt.Printf("Unsupported type %v.\n", toAssignFlag.value) + self.Usage() + os.Exit(1) + } +} + +func (self *Getopt) nilRuneToEmptyString(input *rune) string { + if input == nil { + return "" + } + return string(*input) +} + +func (self *Getopt) nilToEmptyString(input *string) string { + if input == nil { + return "" + } + return *input +}