From 69885375524c2f6550def22fdd3edd25f3657bc6 Mon Sep 17 00:00:00 2001 From: morganamilo Date: Fri, 9 Mar 2018 02:03:04 +0000 Subject: [PATCH] Add parseNumberMenu() This function is designed to replace the existing number menu in upcoming commits. --- parser.go | 105 +++++++++++++++++++++++++++++++++++++++++++++++++ parser_test.go | 99 ++++++++++++++++++++++++++++++++++++++++++++++ query.go | 7 ++++ vcs_test.go | 2 +- 4 files changed, 212 insertions(+), 1 deletion(-) create mode 100644 parser_test.go diff --git a/parser.go b/parser.go index 6c421b49..d824d545 100644 --- a/parser.go +++ b/parser.go @@ -4,6 +4,7 @@ import ( "fmt" "io" "os" + "strconv" "strings" ) @@ -38,6 +39,20 @@ func (set stringSet) toSlice() []string { return slice } +func SliceToStringSet(in []string) stringSet { + set := make(stringSet) + + for _, v := range in { + set.set(v) + } + + return set +} + +func makeStringSet(in ...string) stringSet { + return SliceToStringSet(in) +} + // Parses command line arguments in a way we can interact with programmatically but // also in a way that can easily be passed to pacman later on. type arguments struct { @@ -554,3 +569,93 @@ func (parser *arguments) parseCommandLine() (err error) { return } + +type intRange struct { + min int + max int +} + +func makeIntRange(min, max int) intRange { + return intRange{ + min, + max, + } +} + +func (r intRange) get(n int) bool { + return n >= r.min && n <= r.max +} + +type intRanges []intRange + +func (rs intRanges) get(n int) bool { + for _, r := range rs { + if r.get(n) { + return true + } + } + + return false +} + +//parses input for number menus +//supports individual selection: 1 2 3 4 +//supports range selections: 1-4 10-20 +//supports negation: ^1 ^1-4 +// +//include and excule holds numbers that should be added and should not be added +//respectively. other holds anythign that can't be parsed as an int. This is +//intended to allow words inside of number menus. e.g. 'all' 'none' 'abort' +//of course the implementation is up to the caller, this function mearley parses +//the input and organizes it +func parseNumberMenu(input string) (intRanges, intRanges, stringSet, stringSet) { + include := make(intRanges, 0, 0) + exclude := make(intRanges, 0, 0) + otherInclude := make(stringSet) + otherExclude := make(stringSet) + + words := strings.Fields(input) + + for _, word := range words { + var num1 int + var num2 int + var err error + invert := false + other := otherInclude + + if word[0] == '^' { + invert = true + other = otherExclude + word = word[1:] + } + + ranges := strings.SplitN(word, "-", 2) + + num1, err = strconv.Atoi(ranges[0]) + if err != nil { + other.set(strings.ToLower(word)) + continue + } + + if len(ranges) == 2 { + num2, err = strconv.Atoi(ranges[1]) + if err != nil { + other.set(strings.ToLower(word)) + continue + } + } else { + num2 = num1 + } + + mi := min(num1, num2) + ma := max(num1, num2) + + if !invert { + include = append(include, makeIntRange(mi, ma)) + } else { + exclude = append(exclude, makeIntRange(mi, ma)) + } + } + + return include, exclude, otherInclude, otherExclude +} diff --git a/parser_test.go b/parser_test.go new file mode 100644 index 00000000..47c05177 --- /dev/null +++ b/parser_test.go @@ -0,0 +1,99 @@ +package main + +import "testing" + +func intRangesEqual(a, b intRanges) bool { + if a == nil && b == nil { + return true + } + + if a == nil || b == nil { + return false + } + + if len(a) != len(b) { + return false + } + + for n := range a { + r1 := a[n] + r2 := b[n] + + if r1.min != r1.min || r1.max != r2.max { + return false + } + } + + return true +} + +func stringSetEqual(a, b stringSet) bool { + if a == nil && b == nil { + return true + } + + if a == nil || b == nil { + return false + } + + if len(a) != len(b) { + return false + } + + for n := range a { + if !b.get(n) { + return false + } + } + + return true +} + +func TestParseNumberMenu(t *testing.T) { + type result struct { + Include intRanges + Exclude intRanges + OtherInclude stringSet + OtherExclude stringSet + } + + inputs := []string{ + "1 2 3 4 5", + "1-10 5-15", + "10-5 90-85", + "1 ^2 ^10-5 99 ^40-38 ^123 60-62", + "abort all none", + "a-b ^a-b ^abort", + "1\t2 3 4\t\t \t 5", + "", + " \t ", + "A B C D E", + } + + expected := []result{ + {intRanges{makeIntRange(1, 1), makeIntRange(2, 2), makeIntRange(3, 3), makeIntRange(4, 4), makeIntRange(5, 5)}, intRanges{}, make(stringSet), make(stringSet)}, + {intRanges{makeIntRange(1, 10), makeIntRange(5, 15)}, intRanges{}, make(stringSet), make(stringSet)}, + {intRanges{makeIntRange(5, 10), makeIntRange(85, 90)}, intRanges{}, make(stringSet), make(stringSet)}, + {intRanges{makeIntRange(1, 1), makeIntRange(99, 99), makeIntRange(60, 62)}, intRanges{makeIntRange(2, 2), makeIntRange(5, 10), makeIntRange(38, 40), makeIntRange(123, 123)}, make(stringSet), make(stringSet)}, + {intRanges{}, intRanges{}, makeStringSet("abort", "all", "none"), make(stringSet)}, + {intRanges{}, intRanges{}, makeStringSet("a-b"), makeStringSet("abort", "a-b")}, + {intRanges{makeIntRange(1, 1), makeIntRange(2, 2), makeIntRange(3, 3), makeIntRange(4, 4), makeIntRange(5, 5)}, intRanges{}, make(stringSet), make(stringSet)}, + {intRanges{}, intRanges{}, make(stringSet), make(stringSet)}, + {intRanges{}, intRanges{}, make(stringSet), make(stringSet)}, + {intRanges{}, intRanges{}, makeStringSet("a", "b", "c", "d", "e"), make(stringSet)}, + } + + for n, in := range inputs { + res := expected[n] + include, exclude, otherInclude, otherExclude := parseNumberMenu(in) + + if !intRangesEqual(include, res.Include) || + !intRangesEqual(exclude, res.Exclude) || + !stringSetEqual(otherInclude, res.OtherInclude) || + !stringSetEqual(otherExclude, res.OtherExclude) { + + t.Fatalf("Test %d Failed: Expected: include=%+v exclude=%+v otherInclude=%+v otherExclude=%+v got include=%+v excluive=%+v otherInclude=%+v otherExclude=%+v", + n+1, res.Include, res.Exclude, res.OtherInclude, res.OtherExclude, include, exclude, otherInclude, otherExclude) + } + } +} diff --git a/query.go b/query.go index 85e182ad..31f7f4b3 100644 --- a/query.go +++ b/query.go @@ -317,6 +317,13 @@ func min(a, b int) int { return b } +func max(a, b int) int { + if a < b { + return b + } + return a +} + // Queries the aur for information about specified packages. // All packages should be queried in a single rpc request except when the number // of packages exceeds the number set in config.RequestSplitN. diff --git a/vcs_test.go b/vcs_test.go index afe401be..5354b40b 100644 --- a/vcs_test.go +++ b/vcs_test.go @@ -60,7 +60,7 @@ func TestParsing(t *testing.T) { branch != compare.Branch || !isEqual(protocols, compare.Protocols) { - t.Fatalf("Expected url=%+v branch=%+v protocols=%+v\ngot url=%+v branch=%+v protocols=%+v", url, branch, protocols, compare.URL, compare.Branch, compare.Protocols) + t.Fatalf("Test %d failed: Expected: url=%+v branch=%+v protocols=%+v\ngot url=%+v branch=%+v protocols=%+v", n+1, url, branch, protocols, compare.URL, compare.Branch, compare.Protocols) } }