diff --git a/modules/qualify/qualify.go b/modules/qualify/qualify.go new file mode 100644 index 0000000..6ec0171 --- /dev/null +++ b/modules/qualify/qualify.go @@ -0,0 +1,83 @@ +package qualify + +import ( + "strings" +) + +type Query map[string]Filter + +type Filter []string + +func (q Query) Query() string { + if query, ok := q["query"]; ok { + return strings.Join(query, " ") + } + return "" +} + +func (f Filter) Last() string { + return f[len(f)-1] +} + +func Parse(text string, qualifiers ...string) Query { + queries := make(map[string]Filter) + + for _, field := range fields(text) { + var key, value string + if k, v, ok := qualified(field, qualifiers...); ok { + key = k + value = trim(v) + } else { + key = "query" + value = trim(field) + } + + if queries[key] == nil { + queries[key] = make([]string, 0) + } + queries[key] = append(queries[key], value) + } + + return queries +} + +func qualified(text string, qualifiers ...string) (string, string, bool) { + if len(qualifiers) == 0 && strings.Contains(text, ":") { + kv := strings.SplitN(text, ":", 2) + return kv[0], kv[1], true + } + + for _, qualifier := range qualifiers { + if strings.HasPrefix(text, qualifier+":") { + return text[:len(qualifier)], text[len(qualifier)+1:], true + } + } + return "", "", false +} + +func fields(text string) []string { + var f []string + + var quotes bool + var last int + for idx, char := range text { + if char == '"' { + quotes = !quotes + continue + } + + if char == ' ' { + if quotes { + continue + } + f = append(f, text[last:idx]) + last = idx + 1 + } + } + f = append(f, strings.TrimSpace(text[last:])) + return f +} + +func trim(text string) string { + return strings.TrimPrefix(strings.TrimSuffix(strings.TrimSpace(text), `"`), `"`) +} diff --git a/modules/qualify/qualify_test.go b/modules/qualify/qualify_test.go new file mode 100644 index 0000000..73db39d --- /dev/null +++ b/modules/qualify/qualify_test.go @@ -0,0 +1,49 @@ +package qualify + +import ( + "fmt" + "os" + "strconv" + "strings" + "testing" +) + +func TestMain(m *testing.M) { + os.Exit(m.Run()) +} + +func TestParse(t *testing.T) { + tt := []struct { + Query string + Result string + Qualifiers []string + }{ + {"test", "query->test", nil}, + {`"test"`, "query->test", nil}, + {"author:jolheiser", "author->jolheiser", nil}, + {`"this is a test" author:"jolheiser"`, "query->this is a test,author->jolheiser", nil}, + {`"this is" "a test" query`, "query->this is|a test|query", nil}, + {`label:"a bug" label:"a" label:"bug"`, "label->a bug|a|bug", nil}, + {`author:"jolheiser""`, `author->jolheiser"`, nil}, + {`author:""jolheiser""`, `author->"jolheiser"`, nil}, + {"test:testing", "query->test:testing", []string{"label"}}, + } + + for idx, tc := range tt { + t.Run(strconv.Itoa(idx+1), func(t *testing.T) { + parsed := result(Parse(tc.Query, tc.Qualifiers...)) + if parsed != tc.Result { + t.Logf("\nwant: %s\n got: %s", tc.Result, parsed) + t.Fail() + } + }) + } +} + +func result(parsed Query) string { + res := make([]string, 0, len(parsed)) + for key, values := range parsed { + res = append(res, fmt.Sprintf("%s->%s", key, strings.Join(values, "|"))) + } + return strings.Join(res, ",") +} diff --git a/modules/sdk/issues.go b/modules/sdk/issues.go index b7aeb09..b22b98f 100644 --- a/modules/sdk/issues.go +++ b/modules/sdk/issues.go @@ -2,6 +2,7 @@ package sdk import ( "code.gitea.io/sdk/gitea" + "gitea.com/jolheiser/sip/modules/qualify" "strings" ) @@ -87,34 +88,34 @@ func (f *IssueFilter) Match(issue *gitea.Issue) bool { func NewIssueFilter(query string) *IssueFilter { filter := &IssueFilter{} - for _, q := range strings.Split(query, " ") { - kv := strings.Split(q, ":") - if len(kv) == 2 { - kv[0] = strings.ToLower(kv[0]) - kv[1] = strings.ToLower(kv[1]) - switch kv[0] { - case "is": - switch kv[1] { - case "open", "opened": - filter.State = Open - case "close", "closed": - filter.State = Closed - case "merge", "merged": - filter.State = Merged - default: - filter.State = None - } - case "author": - filter.Author = kv[1] - case "label": - filter.Labels = append(filter.Labels, kv[1]) - case "milestone": - filter.Milestone = kv[1] - } - continue + filters := qualify.Parse(query, "is", "author", "label", "milestone") + + if state, ok := filters["is"]; ok { + switch state.Last() { + case "open", "opened": + filter.State = Open + case "close", "closed": + filter.State = Closed + case "merge", "merged": + filter.State = Merged + default: + filter.State = None } - filter.Query += " " + q } - filter.Query = strings.TrimSpace(filter.Query) + + if author, ok := filters["author"]; ok { + filter.Author = author.Last() + } + + if label, ok := filters["label"]; ok { + filter.Labels = append(filter.Labels, label...) + } + + if milestone, ok := filters["milestone"]; ok { + filter.Milestone = milestone.Last() + } + + filter.Query = filters.Query() + return filter } diff --git a/modules/sdk/issues_test.go b/modules/sdk/issues_test.go index 9cf5768..4e4c09a 100644 --- a/modules/sdk/issues_test.go +++ b/modules/sdk/issues_test.go @@ -26,6 +26,7 @@ func TestIssueFilter(t *testing.T) { {"is:closed milestone:0.1.0", &IssueFilter{State: Closed, Milestone: "0.1.0"}}, {"milestone:v1.0.0 keyword", &IssueFilter{Query: "keyword", Milestone: "v1.0.0"}}, + {`label:"a bug" keyword`, &IssueFilter{Query: "keyword", Labels: []string{"a bug"}}}, } for idx, tc := range tt {