diff --git a/validate/rules.go b/validate/rules.go index b88e0e9..d08ecfd 100644 --- a/validate/rules.go +++ b/validate/rules.go @@ -1,24 +1,30 @@ package validate import ( + "sort" "strings" + "sync" "github.com/vbatts/git-validation/git" ) var ( // RegisteredRules are the standard validation to perform on git commits - RegisteredRules = []Rule{} + RegisteredRules = []Rule{} + registerRuleLock = sync.Mutex{} ) // RegisterRule includes the Rule in the avaible set to use func RegisterRule(vr Rule) { + registerRuleLock.Lock() + defer registerRuleLock.Unlock() RegisteredRules = append(RegisteredRules, vr) } // Rule will operate over a provided git.CommitEntry, and return a result. type Rule struct { Name string // short name for reference in in the `-run=...` flag + Value string // value to configure for the rule (i.e. a regexp to check for in the commit message) Description string // longer Description for readability Run func(git.CommitEntry) Result } @@ -54,28 +60,74 @@ func (vr Results) PassFail() (pass int, fail int) { return pass, fail } -// SanitizeFilters takes a comma delimited list and returns the cleaned items in the list -func SanitizeFilters(filt string) (excludes []string) { - - for _, item := range strings.Split(filt, ",") { - excludes = append(excludes, strings.TrimSpace(item)) +// SanitizeFilters takes a comma delimited list and returns the trimmend and +// split (on ",") items in the list +func SanitizeFilters(filtStr string) (filters []string) { + for _, item := range strings.Split(filtStr, ",") { + filters = append(filters, strings.TrimSpace(item)) } - return } -// FilterRules takes a set of rules and a list of short names to exclude, and returns the reduced set. -// The comparison is case insensitive. -func FilterRules(rules []Rule, excludes []string) []Rule { +// FilterRules takes a set of rules and a list of short names to include, and +// returns the reduced set. The comparison is case insensitive. +// +// Some `includes` rules have values assigned to them. +// i.e. -run "dco,message_regexp='^JIRA-[0-9]+ [A-Z].*$'" +// +func FilterRules(rules []Rule, includes []string) []Rule { ret := []Rule{} for _, r := range rules { - for _, e := range excludes { - if strings.ToLower(r.Name) == strings.ToLower(e) { - ret = append(ret, r) + for i := range includes { + if strings.Contains(includes[i], "=") { + chunks := strings.SplitN(includes[i], "=", 2) + if strings.ToLower(r.Name) == strings.ToLower(chunks[0]) { + // for these rules, the Name won't be unique per se. There may be + // multiple "regexp=" with different values. We'll need to set the + // .Value = chunk[1] and ensure r is dup'ed so they don't clobber + // each other. + newR := Rule(r) + newR.Value = chunks[1] + ret = append(ret, newR) + } + } else { + if strings.ToLower(r.Name) == strings.ToLower(includes[i]) { + ret = append(ret, r) + } } } } return ret } + +// StringsSliceEqual compares two string arrays for equality +func StringsSliceEqual(a, b []string) bool { + if !sort.StringsAreSorted(a) { + sort.Strings(a) + } + if !sort.StringsAreSorted(b) { + sort.Strings(b) + } + for i := range b { + if !StringsSliceContains(a, b[i]) { + return false + } + } + for i := range a { + if !StringsSliceContains(b, a[i]) { + return false + } + } + return true +} + +// StringsSliceContains checks for the presence of a word in string array +func StringsSliceContains(a []string, b string) bool { + if !sort.StringsAreSorted(a) { + sort.Strings(a) + } + i := sort.SearchStrings(a, b) + return i < len(a) && a[i] == b +} diff --git a/validate/rules_test.go b/validate/rules_test.go new file mode 100644 index 0000000..290973f --- /dev/null +++ b/validate/rules_test.go @@ -0,0 +1,52 @@ +package validate + +import ( + "testing" +) + +func TestSanitizeRules(t *testing.T) { + set := []struct { + input string + output []string + }{ + { + input: "apples, oranges , bananas", + output: []string{"apples", "oranges", "bananas"}, + }, + { + input: "apples, oranges , bananas, peaches='with cream'", + output: []string{"apples", "oranges", "bananas", "peaches='with cream'"}, + }, + } + + for i := range set { + filt := SanitizeFilters(set[i].input) + if !StringsSliceEqual(filt, set[i].output) { + t.Errorf("expected output like %v, but got %v", set[i].output, filt) + } + } +} + +func TestSliceHelpers(t *testing.T) { + set := []struct { + A, B []string + Equal bool + }{ + { + A: []string{"apples", "bananas", "oranges", "mango"}, + B: []string{"oranges", "bananas", "apples", "mango"}, + Equal: true, + }, + { + A: []string{"apples", "bananas", "oranges", "mango"}, + B: []string{"waffles"}, + Equal: false, + }, + } + for i := range set { + got := StringsSliceEqual(set[i].A, set[i].B) + if got != set[i].Equal { + t.Errorf("expected %d A and B comparison to be %q, but got %q", i, set[i].Equal, got) + } + } +}