aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--bridge/github/import.go5
-rw-r--r--bridge/gitlab/import.go7
-rw-r--r--bridge/gitlab/import_test.go8
-rw-r--r--bridge/jira/import.go12
-rw-r--r--bridge/jira/jira.go1
-rw-r--r--bridge/launchpad/import.go5
-rw-r--r--cache/filter.go47
-rw-r--r--cache/query.go172
-rw-r--r--cache/query_test.go41
-rw-r--r--cache/repo_cache.go24
-rw-r--r--cache/repo_cache_test.go5
-rw-r--r--cache/sorting.go18
-rw-r--r--commands/ls.go95
-rw-r--r--go.mod6
-rw-r--r--go.sum13
-rw-r--r--graphql/resolvers/repo.go12
-rw-r--r--query/lexer.go71
-rw-r--r--query/lexer_test.go45
-rw-r--r--query/parser.go99
-rw-r--r--query/parser_test.go97
-rw-r--r--query/query.go49
-rw-r--r--termui/bug_table.go7
-rw-r--r--termui/termui.go5
23 files changed, 508 insertions, 336 deletions
diff --git a/bridge/github/import.go b/bridge/github/import.go
index a74c49c5..78e93436 100644
--- a/bridge/github/import.go
+++ b/bridge/github/import.go
@@ -108,7 +108,10 @@ func (gi *githubImporter) ensureIssue(repo *cache.RepoCache, issue issueTimeline
}
// resolve bug
- b, err := repo.ResolveBugCreateMetadata(metaKeyGithubUrl, issue.Url.String())
+ b, err := repo.ResolveBugMatcher(func(excerpt *cache.BugExcerpt) bool {
+ return excerpt.CreateMetadata[core.MetaKeyOrigin] == target &&
+ excerpt.CreateMetadata[metaKeyGithubId] == parseId(issue.Id)
+ })
if err != nil && err != bug.ErrBugNotExist {
return nil, err
}
diff --git a/bridge/gitlab/import.go b/bridge/gitlab/import.go
index 5ed5f0e3..0a47a783 100644
--- a/bridge/gitlab/import.go
+++ b/bridge/gitlab/import.go
@@ -123,7 +123,12 @@ func (gi *gitlabImporter) ensureIssue(repo *cache.RepoCache, issue *gitlab.Issue
}
// resolve bug
- b, err := repo.ResolveBugCreateMetadata(metaKeyGitlabUrl, issue.WebURL)
+ b, err := repo.ResolveBugMatcher(func(excerpt *cache.BugExcerpt) bool {
+ return excerpt.CreateMetadata[core.MetaKeyOrigin] == target &&
+ excerpt.CreateMetadata[metaKeyGitlabId] == parseID(issue.IID) &&
+ excerpt.CreateMetadata[metaKeyGitlabBaseUrl] == gi.conf[confKeyProjectID] &&
+ excerpt.CreateMetadata[metaKeyGitlabProject] == gi.conf[confKeyGitlabBaseUrl]
+ })
if err == nil {
return b, nil
}
diff --git a/bridge/gitlab/import_test.go b/bridge/gitlab/import_test.go
index f916d20c..42a37cda 100644
--- a/bridge/gitlab/import_test.go
+++ b/bridge/gitlab/import_test.go
@@ -29,7 +29,7 @@ func TestImport(t *testing.T) {
}{
{
name: "simple issue",
- url: "https://gitlab.com/git-bug/test/issues/1",
+ url: "https://gitlab.com/git-bug/test/-/issues/1",
bug: &bug.Snapshot{
Operations: []bug.Operation{
bug.NewCreateOp(author, 0, "simple issue", "initial comment", nil),
@@ -40,7 +40,7 @@ func TestImport(t *testing.T) {
},
{
name: "empty issue",
- url: "https://gitlab.com/git-bug/test/issues/2",
+ url: "https://gitlab.com/git-bug/test/-/issues/2",
bug: &bug.Snapshot{
Operations: []bug.Operation{
bug.NewCreateOp(author, 0, "empty issue", "", nil),
@@ -49,7 +49,7 @@ func TestImport(t *testing.T) {
},
{
name: "complex issue",
- url: "https://gitlab.com/git-bug/test/issues/3",
+ url: "https://gitlab.com/git-bug/test/-/issues/3",
bug: &bug.Snapshot{
Operations: []bug.Operation{
bug.NewCreateOp(author, 0, "complex issue", "initial comment", nil),
@@ -66,7 +66,7 @@ func TestImport(t *testing.T) {
},
{
name: "editions",
- url: "https://gitlab.com/git-bug/test/issues/4",
+ url: "https://gitlab.com/git-bug/test/-/issues/4",
bug: &bug.Snapshot{
Operations: []bug.Operation{
bug.NewCreateOp(author, 0, "editions", "initial comment edited", nil),
diff --git a/bridge/jira/import.go b/bridge/jira/import.go
index 3d6d5414..b66b0fa3 100644
--- a/bridge/jira/import.go
+++ b/bridge/jira/import.go
@@ -216,7 +216,16 @@ func (ji *jiraImporter) ensureIssue(repo *cache.RepoCache, issue Issue) (*cache.
return nil, err
}
- b, err := repo.ResolveBugCreateMetadata(metaKeyJiraId, issue.ID)
+ b, err := repo.ResolveBugMatcher(func(excerpt *cache.BugExcerpt) bool {
+ if _, ok := excerpt.CreateMetadata[metaKeyJiraBaseUrl]; ok &&
+ excerpt.CreateMetadata[metaKeyJiraBaseUrl] != ji.conf[confKeyBaseUrl] {
+ return false
+ }
+
+ return excerpt.CreateMetadata[core.MetaKeyOrigin] == target &&
+ excerpt.CreateMetadata[metaKeyJiraId] == issue.ID &&
+ excerpt.CreateMetadata[metaKeyJiraProject] == ji.conf[confKeyProject]
+ })
if err != nil && err != bug.ErrBugNotExist {
return nil, err
}
@@ -241,6 +250,7 @@ func (ji *jiraImporter) ensureIssue(repo *cache.RepoCache, issue Issue) (*cache.
metaKeyJiraId: issue.ID,
metaKeyJiraKey: issue.Key,
metaKeyJiraProject: ji.conf[confKeyProject],
+ metaKeyJiraBaseUrl: ji.conf[confKeyBaseUrl],
})
if err != nil {
return nil, err
diff --git a/bridge/jira/jira.go b/bridge/jira/jira.go
index 066c6597..6423843c 100644
--- a/bridge/jira/jira.go
+++ b/bridge/jira/jira.go
@@ -20,6 +20,7 @@ const (
metaKeyJiraKey = "jira-key"
metaKeyJiraUser = "jira-user"
metaKeyJiraProject = "jira-project"
+ metaKeyJiraBaseUrl = "jira-base-url"
metaKeyJiraExportTime = "jira-export-time"
metaKeyJiraLogin = "jira-login"
diff --git a/bridge/launchpad/import.go b/bridge/launchpad/import.go
index 3b6d7fe0..7f528c7d 100644
--- a/bridge/launchpad/import.go
+++ b/bridge/launchpad/import.go
@@ -62,7 +62,10 @@ func (li *launchpadImporter) ImportAll(ctx context.Context, repo *cache.RepoCach
return
default:
lpBugID := fmt.Sprintf("%d", lpBug.ID)
- b, err := repo.ResolveBugCreateMetadata(metaKeyLaunchpadID, lpBugID)
+ b, err := repo.ResolveBugMatcher(func(excerpt *cache.BugExcerpt) bool {
+ return excerpt.CreateMetadata[core.MetaKeyOrigin] == target &&
+ excerpt.CreateMetadata[metaKeyLaunchpadID] == lpBugID
+ })
if err != nil && err != bug.ErrBugNotExist {
out <- core.NewImportError(err, entity.Id(lpBugID))
return
diff --git a/cache/filter.go b/cache/filter.go
index ebe774ac..166cd48d 100644
--- a/cache/filter.go
+++ b/cache/filter.go
@@ -5,6 +5,7 @@ import (
"github.com/MichaelMure/git-bug/bug"
"github.com/MichaelMure/git-bug/entity"
+ "github.com/MichaelMure/git-bug/query"
)
// resolver has the resolving functions needed by filters.
@@ -17,15 +18,10 @@ type resolver interface {
type Filter func(excerpt *BugExcerpt, resolver resolver) bool
// StatusFilter return a Filter that match a bug status
-func StatusFilter(query string) (Filter, error) {
- status, err := bug.StatusFromString(query)
- if err != nil {
- return nil, err
- }
-
+func StatusFilter(status bug.Status) Filter {
return func(excerpt *BugExcerpt, resolver resolver) bool {
return excerpt.Status == status
- }, nil
+ }
}
// AuthorFilter return a Filter that match a bug author
@@ -116,8 +112,8 @@ func NoLabelFilter() Filter {
}
}
-// Filters is a collection of Filter that implement a complex filter
-type Filters struct {
+// Matcher is a collection of Filter that implement a complex filter
+type Matcher struct {
Status []Filter
Author []Filter
Actor []Filter
@@ -127,8 +123,35 @@ type Filters struct {
NoFilters []Filter
}
+// compileMatcher transform a query.Filters into a specialized matcher
+// for the cache.
+func compileMatcher(filters query.Filters) *Matcher {
+ result := &Matcher{}
+
+ for _, value := range filters.Status {
+ result.Status = append(result.Status, StatusFilter(value))
+ }
+ for _, value := range filters.Author {
+ result.Author = append(result.Author, AuthorFilter(value))
+ }
+ for _, value := range filters.Actor {
+ result.Actor = append(result.Actor, ActorFilter(value))
+ }
+ for _, value := range filters.Participant {
+ result.Participant = append(result.Participant, ParticipantFilter(value))
+ }
+ for _, value := range filters.Label {
+ result.Label = append(result.Label, LabelFilter(value))
+ }
+ for _, value := range filters.Title {
+ result.Title = append(result.Title, TitleFilter(value))
+ }
+
+ return result
+}
+
// Match check if a bug match the set of filters
-func (f *Filters) Match(excerpt *BugExcerpt, resolver resolver) bool {
+func (f *Matcher) Match(excerpt *BugExcerpt, resolver resolver) bool {
if match := f.orMatch(f.Status, excerpt, resolver); !match {
return false
}
@@ -161,7 +184,7 @@ func (f *Filters) Match(excerpt *BugExcerpt, resolver resolver) bool {
}
// Check if any of the filters provided match the bug
-func (*Filters) orMatch(filters []Filter, excerpt *BugExcerpt, resolver resolver) bool {
+func (*Matcher) orMatch(filters []Filter, excerpt *BugExcerpt, resolver resolver) bool {
if len(filters) == 0 {
return true
}
@@ -175,7 +198,7 @@ func (*Filters) orMatch(filters []Filter, excerpt *BugExcerpt, resolver resolver
}
// Check if all of the filters provided match the bug
-func (*Filters) andMatch(filters []Filter, excerpt *BugExcerpt, resolver resolver) bool {
+func (*Matcher) andMatch(filters []Filter, excerpt *BugExcerpt, resolver resolver) bool {
if len(filters) == 0 {
return true
}
diff --git a/cache/query.go b/cache/query.go
deleted file mode 100644
index 967c18d6..00000000
--- a/cache/query.go
+++ /dev/null
@@ -1,172 +0,0 @@
-package cache
-
-import (
- "fmt"
- "strings"
- "unicode"
-)
-
-type Query struct {
- Filters
- OrderBy
- OrderDirection
-}
-
-// Return an identity query with default sorting (creation-desc)
-func NewQuery() *Query {
- return &Query{
- OrderBy: OrderByCreation,
- OrderDirection: OrderDescending,
- }
-}
-
-// ParseQuery parse a query DSL
-//
-// Ex: "status:open author:descartes sort:edit-asc"
-//
-// Supported filter qualifiers and syntax are described in docs/queries.md
-func ParseQuery(query string) (*Query, error) {
- fields := splitQuery(query)
-
- result := &Query{
- OrderBy: OrderByCreation,
- OrderDirection: OrderDescending,
- }
-
- sortingDone := false
-
- for _, field := range fields {
- split := strings.Split(field, ":")
- if len(split) != 2 {
- return nil, fmt.Errorf("can't parse \"%s\"", field)
- }
-
- qualifierName := split[0]
- qualifierQuery := removeQuote(split[1])
-
- switch qualifierName {
- case "status", "state":
- f, err := StatusFilter(qualifierQuery)
- if err != nil {
- return nil, err
- }
- result.Status = append(result.Status, f)
-
- case "author":
- f := AuthorFilter(qualifierQuery)
- result.Author = append(result.Author, f)
-
- case "actor":
- f := ActorFilter(qualifierQuery)
- result.Actor = append(result.Actor, f)
-
- case "participant":
- f := ParticipantFilter(qualifierQuery)
- result.Participant = append(result.Participant, f)
-
- case "label":
- f := LabelFilter(qualifierQuery)
- result.Label = append(result.Label, f)
-
- case "title":
- f := TitleFilter(qualifierQuery)
- result.Title = append(result.Title, f)
-
- case "no":
- err := result.parseNoFilter(qualifierQuery)
- if err != nil {
- return nil, err
- }
-
- case "sort":
- if sortingDone {
- return nil, fmt.Errorf("multiple sorting")
- }
-
- err := result.parseSorting(qualifierQuery)
- if err != nil {
- return nil, err
- }
-
- sortingDone = true
-
- default:
- return nil, fmt.Errorf("unknown qualifier name %s", qualifierName)
- }
- }
-
- return result, nil
-}
-
-func splitQuery(query string) []string {
- lastQuote := rune(0)
- f := func(c rune) bool {
- switch {
- case c == lastQuote:
- lastQuote = rune(0)
- return false
- case lastQuote != rune(0):
- return false
- case unicode.In(c, unicode.Quotation_Mark):
- lastQuote = c
- return false
- default:
- return unicode.IsSpace(c)
- }
- }
-
- return strings.FieldsFunc(query, f)
-}
-
-func removeQuote(field string) string {
- if len(field) >= 2 {
- if field[0] == '"' && field[len(field)-1] == '"' {
- return field[1 : len(field)-1]
- }
- }
- return field
-}
-
-func (q *Query) parseNoFilter(query string) error {
- switch query {
- case "label":
- q.NoFilters = append(q.NoFilters, NoLabelFilter())
- default:
- return fmt.Errorf("unknown \"no\" filter %s", query)
- }
-
- return nil
-}
-
-func (q *Query) parseSorting(query string) error {
- switch query {
- // default ASC
- case "id-desc":
- q.OrderBy = OrderById
- q.OrderDirection = OrderDescending
- case "id", "id-asc":
- q.OrderBy = OrderById
- q.OrderDirection = OrderAscending
-
- // default DESC
- case "creation", "creation-desc":
- q.OrderBy = OrderByCreation
- q.OrderDirection = OrderDescending
- case "creation-asc":
- q.OrderBy = OrderByCreation
- q.OrderDirection = OrderAscending
-
- // default DESC
- case "edit", "edit-desc":
- q.OrderBy = OrderByEdit
- q.OrderDirection = OrderDescending
- case "edit-asc":
- q.OrderBy = OrderByEdit
- q.OrderDirection = OrderAscending
-
- default:
- return fmt.Errorf("unknown sorting %s", query)
- }
-
- return nil
-}
diff --git a/cache/query_test.go b/cache/query_test.go
deleted file mode 100644
index 9ae62ac4..00000000
--- a/cache/query_test.go
+++ /dev/null
@@ -1,41 +0,0 @@
-package cache
-
-import "testing"
-
-func TestQueryParse(t *testing.T) {
-
- var tests = []struct {
- input string
- ok bool
- }{
- {"gibberish", false},
-
- {"status:", false},
-
- {"status:open", true},
- {"status:closed", true},
- {"status:unknown", false},
-
- {"author:rene", true},
- {`author:"René Descartes"`, true},
-
- {"actor:bernhard", true},
- {"participant:leonhard", true},
-
- {"label:hello", true},
- {`label:"Good first issue"`, true},
-
- {"title:titleOne", true},
- {`title:"Bug titleTwo"`, true},
-
- {"sort:edit", true},
- {"sort:unknown", false},
- }
-
- for _, test := range tests {
- _, err := ParseQuery(test.input)
- if (err == nil) != test.ok {
- t.Fatalf("Unexpected parse result, expected: %v, err: %v", test.ok, err)
- }
- }
-}
diff --git a/cache/repo_cache.go b/cache/repo_cache.go
index d859a966..f2e1c7d0 100644
--- a/cache/repo_cache.go
+++ b/cache/repo_cache.go
@@ -18,6 +18,7 @@ import (
"github.com/MichaelMure/git-bug/bug"
"github.com/MichaelMure/git-bug/entity"
"github.com/MichaelMure/git-bug/identity"
+ "github.com/MichaelMure/git-bug/query"
"github.com/MichaelMure/git-bug/repository"
"github.com/MichaelMure/git-bug/util/git"
"github.com/MichaelMure/git-bug/util/process"
@@ -525,37 +526,44 @@ func (c *RepoCache) resolveBugMatcher(f func(*BugExcerpt) bool) (entity.Id, erro
}
// QueryBugs return the id of all Bug matching the given Query
-func (c *RepoCache) QueryBugs(query *Query) []entity.Id {
+func (c *RepoCache) QueryBugs(q *query.Query) []entity.Id {
c.muBug.RLock()
defer c.muBug.RUnlock()
- if query == nil {
+ if q == nil {
return c.AllBugsIds()
}
+ matcher := compileMatcher(q.Filters)
+
var filtered []*BugExcerpt
for _, excerpt := range c.bugExcerpts {
- if query.Match(excerpt, c) {
+ if matcher.Match(excerpt, c) {
filtered = append(filtered, excerpt)
}
}
var sorter sort.Interface
- switch query.OrderBy {
- case OrderById:
+ switch q.OrderBy {
+ case query.OrderById:
sorter = BugsById(filtered)
- case OrderByCreation:
+ case query.OrderByCreation:
sorter = BugsByCreationTime(filtered)
- case OrderByEdit:
+ case query.OrderByEdit:
sorter = BugsByEditTime(filtered)
default:
panic("missing sort type")
}
- if query.OrderDirection == OrderDescending {
+ switch q.OrderDirection {
+ case query.OrderAscending:
+ // Nothing to do
+ case query.OrderDescending:
sorter = sort.Reverse(sorter)
+ default:
+ panic("missing sort direction")
}
sort.Sort(sorter)
diff --git a/cache/repo_cache_test.go b/cache/repo_cache_test.go
index c3bd3cc4..51393dfd 100644
--- a/cache/repo_cache_test.go
+++ b/cache/repo_cache_test.go
@@ -5,6 +5,7 @@ import (
"github.com/stretchr/testify/require"
+ "github.com/MichaelMure/git-bug/query"
"github.com/MichaelMure/git-bug/repository"
)
@@ -68,9 +69,9 @@ func TestCache(t *testing.T) {
require.NoError(t, err)
// Querying
- query, err := ParseQuery("status:open author:descartes sort:edit-asc")
+ q, err := query.Parse("status:open author:descartes sort:edit-asc")
require.NoError(t, err)
- require.Len(t, cache.QueryBugs(query), 2)
+ require.Len(t, cache.QueryBugs(q), 2)
// Close
require.NoError(t, cache.Close())
diff --git a/cache/sorting.go b/cache/sorting.go
deleted file mode 100644
index 19034a9d..00000000
--- a/cache/sorting.go
+++ /dev/null
@@ -1,18 +0,0 @@
-package cache
-
-type OrderBy int
-
-const (
- _ OrderBy = iota
- OrderById
- OrderByCreation
- OrderByEdit
-)
-
-type OrderDirection int
-
-const (
- _ OrderDirection = iota
- OrderAscending
- OrderDescending
-)
diff --git a/commands/ls.go b/commands/ls.go
index 70a948e6..f125d916 100644
--- a/commands/ls.go
+++ b/commands/ls.go
@@ -7,21 +7,20 @@ import (
text "github.com/MichaelMure/go-term-text"
"github.com/spf13/cobra"
+ "github.com/MichaelMure/git-bug/bug"
"github.com/MichaelMure/git-bug/cache"
+ "github.com/MichaelMure/git-bug/query"
"github.com/MichaelMure/git-bug/util/colors"
"github.com/MichaelMure/git-bug/util/interrupt"
)
var (
- lsStatusQuery []string
- lsAuthorQuery []string
- lsParticipantQuery []string
- lsLabelQuery []string
- lsTitleQuery []string
- lsActorQuery []string
- lsNoQuery []string
- lsSortBy string
- lsSortDirection string
+ lsQuery query.Query
+
+ lsStatusQuery []string
+ lsNoQuery []string
+ lsSortBy string
+ lsSortDirection string
)
func runLsBug(cmd *cobra.Command, args []string) error {
@@ -32,21 +31,22 @@ func runLsBug(cmd *cobra.Command, args []string) error {
defer backend.Close()
interrupt.RegisterCleaner(backend.Close)
- var query *cache.Query
+ var q *query.Query
if len(args) >= 1 {
- query, err = cache.ParseQuery(strings.Join(args, " "))
+ q, err = query.Parse(strings.Join(args, " "))
if err != nil {
return err
}
} else {
- query, err = lsQueryFromFlags()
+ err = completeQuery()
if err != nil {
return err
}
+ q = &lsQuery
}
- allIds := backend.QueryBugs(query)
+ allIds := backend.QueryBugs(q)
for _, id := range allIds {
b, err := backend.ResolveBugExcerpt(id)
@@ -96,73 +96,46 @@ func runLsBug(cmd *cobra.Command, args []string) error {
return nil
}
-// Transform the command flags into a query
-func lsQueryFromFlags() (*cache.Query, error) {
- query := cache.NewQuery()
-
- for _, status := range lsStatusQuery {
- f, err := cache.StatusFilter(status)
+// Finish the command flags transformation into the query.Query
+func completeQuery() error {
+ for _, str := range lsStatusQuery {
+ status, err := bug.StatusFromString(str)
if err != nil {
- return nil, err
+ return err
}
- query.Status = append(query.Status, f)
- }
-
- for _, title := range lsTitleQuery {
- f := cache.TitleFilter(title)
- query.Title = append(query.Title, f)
- }
-
- for _, author := range lsAuthorQuery {
- f := cache.AuthorFilter(author)
- query.Author = append(query.Author, f)
- }
-
- for _, actor := range lsActorQuery {
- f := cache.ActorFilter(actor)
- query.Actor = append(query.Actor, f)
- }
-
- for _, participant := range lsParticipantQuery {
- f := cache.ParticipantFilter(participant)
- query.Participant = append(query.Participant, f)
- }
-
- for _, label := range lsLabelQuery {
- f := cache.LabelFilter(label)
- query.Label = append(query.Label, f)
+ lsQuery.Status = append(lsQuery.Status, status)
}
for _, no := range lsNoQuery {
switch no {
case "label":
- query.NoFilters = append(query.NoFilters, cache.NoLabelFilter())
+ lsQuery.NoLabel = true
default:
- return nil, fmt.Errorf("unknown \"no\" filter %s", no)
+ return fmt.Errorf("unknown \"no\" filter %s", no)
}
}
switch lsSortBy {
case "id":
- query.OrderBy = cache.OrderById
+ lsQuery.OrderBy = query.OrderById
case "creation":
- query.OrderBy = cache.OrderByCreation
+ lsQuery.OrderBy = query.OrderByCreation
case "edit":
- query.OrderBy = cache.OrderByEdit
+ lsQuery.OrderBy = query.OrderByEdit
default:
- return nil, fmt.Errorf("unknown sort flag %s", lsSortBy)
+ return fmt.Errorf("unknown sort flag %s", lsSortBy)
}
switch lsSortDirection {
case "asc":
- query.OrderDirection = cache.OrderAscending
+ lsQuery.OrderDirection = query.OrderAscending
case "desc":
- query.OrderDirection = cache.OrderDescending
+ lsQuery.OrderDirection = query.OrderDescending
default:
- return nil, fmt.Errorf("unknown sort direction %s", lsSortDirection)
+ return fmt.Errorf("unknown sort direction %s", lsSortDirection)
}
- return query, nil
+ return nil
}
var lsCmd = &cobra.Command{
@@ -188,15 +161,15 @@ func init() {
lsCmd.Flags().StringSliceVarP(&lsStatusQuery, "status", "s", nil,
"Filter by status. Valid values are [open,closed]")
- lsCmd.Flags().StringSliceVarP(&lsAuthorQuery, "author", "a", nil,
+ lsCmd.Flags().StringSliceVarP(&lsQuery.Author, "author", "a", nil,
"Filter by author")
- lsCmd.Flags().StringSliceVarP(&lsParticipantQuery, "participant", "p", nil,
+ lsCmd.Flags().StringSliceVarP(&lsQuery.Participant, "participant", "p", nil,
"Filter by participant")
- lsCmd.Flags().StringSliceVarP(&lsActorQuery, "actor", "A", nil,
+ lsCmd.Flags().StringSliceVarP(&lsQuery.Actor, "actor", "A", nil,
"Filter by actor")
- lsCmd.Flags().StringSliceVarP(&lsLabelQuery, "label", "l", nil,
+ lsCmd.Flags().StringSliceVarP(&lsQuery.Label, "label", "l", nil,
"Filter by label")
- lsCmd.Flags().StringSliceVarP(&lsTitleQuery, "title", "t", nil,
+ lsCmd.Flags().StringSliceVarP(&lsQuery.Title, "title", "t", nil,
"Filter by title")
lsCmd.Flags().StringSliceVarP(&lsNoQuery, "no", "n", nil,
"Filter by absence of something. Valid values are [label]")
diff --git a/go.mod b/go.mod
index 8082b41b..d8d1e632 100644
--- a/go.mod
+++ b/go.mod
@@ -4,7 +4,7 @@ go 1.11
require (
github.com/99designs/gqlgen v0.10.3-0.20200209012558-b7a58a1c0e4b
- github.com/MichaelMure/go-term-text v0.2.7
+ github.com/MichaelMure/go-term-text v0.2.8
github.com/araddon/dateparse v0.0.0-20190622164848-0fb0a474d195
github.com/awesome-gocui/gocui v0.6.1-0.20191115151952-a34ffb055986
github.com/blang/semver v3.5.1+incompatible
@@ -17,7 +17,7 @@ require (
github.com/hashicorp/golang-lru v0.5.4 // indirect
github.com/icrowley/fake v0.0.0-20180203215853-4178557ae428
github.com/mattn/go-isatty v0.0.12
- github.com/mattn/go-runewidth v0.0.8
+ github.com/mattn/go-runewidth v0.0.9
github.com/phayes/freeport v0.0.0-20171002181615-b8543db493a5
github.com/pkg/errors v0.9.1
github.com/shurcooL/githubv4 v0.0.0-20190601194912-068505affed7
@@ -27,7 +27,7 @@ require (
github.com/stretchr/testify v1.5.1
github.com/theckman/goconstraint v1.11.0
github.com/vektah/gqlparser v1.3.1
- github.com/xanzy/go-gitlab v0.27.0
+ github.com/xanzy/go-gitlab v0.29.0
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550
golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288
golang.org/x/sync v0.0.0-20190423024810-112230192c58
diff --git a/go.sum b/go.sum
index 1e70ffc4..090bb6e6 100644
--- a/go.sum
+++ b/go.sum
@@ -11,6 +11,8 @@ github.com/MichaelMure/go-term-text v0.2.6 h1:dSmJSzk2iI5xWymSMrMbdVM1bxYWu3DjDF
github.com/MichaelMure/go-term-text v0.2.6/go.mod h1:o2Z5T3b28F4kwAojGvvNdbzjHf9t18vbQ7E2pmTe2Ww=
github.com/MichaelMure/go-term-text v0.2.7 h1:nSYvYGwXxJoiQu6kdGSErpxZ6ah/4WlJyp/niqQor6g=
github.com/MichaelMure/go-term-text v0.2.7/go.mod h1:6z+q5b/nP1V8I9KkWQcUi5QpmF8DVrz9vLJ4hdoxHnM=
+github.com/MichaelMure/go-term-text v0.2.8 h1:daXIVPjPkAhcLhA+tfjQBHYjatb1D42/LY1Nw2PXYlU=
+github.com/MichaelMure/go-term-text v0.2.8/go.mod h1:6z+q5b/nP1V8I9KkWQcUi5QpmF8DVrz9vLJ4hdoxHnM=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/agnivade/levenshtein v1.0.1 h1:3oJU7J3FGFmyhn8KHjmVaZCN5hxTr7GxgRue+sxIXdQ=
github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM=
@@ -91,6 +93,11 @@ github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoA
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
+github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM=
+github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
+github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
+github.com/hashicorp/go-retryablehttp v0.6.4 h1:BbgctKO892xEyOXnGiaAwIoSq1QZ/SS4AhjoAh9DnfY=
+github.com/hashicorp/go-retryablehttp v0.6.4/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY=
github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
@@ -126,6 +133,8 @@ github.com/mattn/go-runewidth v0.0.6 h1:V2iyH+aX9C5fsYCpK60U8BYIvmhqxuOL3JZcqc1N
github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.8 h1:3tS41NlGYSmhhe/8fhGRzc+z3AYCw1Fe1WAyLuujKs0=
github.com/mattn/go-runewidth v0.0.8/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
+github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
+github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v0.0.0-20180203102830-a4e142e9c047/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
@@ -222,6 +231,8 @@ github.com/xanzy/go-gitlab v0.26.0 h1:eAnJRBUC+GDJSy8OoGCZBqBMpXsGOOT235TFm/F8C0
github.com/xanzy/go-gitlab v0.26.0/go.mod h1:t4Bmvnxj7k37S4Y17lfLx+nLqkf/oQwT2HagfWKv5Og=
github.com/xanzy/go-gitlab v0.27.0 h1:zy7xBB8+PID6izH07ZArtkEisJ192dtQajRaeo4+glg=
github.com/xanzy/go-gitlab v0.27.0/go.mod h1:t4Bmvnxj7k37S4Y17lfLx+nLqkf/oQwT2HagfWKv5Og=
+github.com/xanzy/go-gitlab v0.29.0 h1:9tMvAkG746eIlzcdpnRgpcKPA1woUDmldMIjR/E5OWM=
+github.com/xanzy/go-gitlab v0.29.0/go.mod h1:sPLojNBn68fMUWSxIJtdVVIP8uSBYqesTfDUseX11Ug=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
@@ -275,6 +286,8 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs=
+golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
diff --git a/graphql/resolvers/repo.go b/graphql/resolvers/repo.go
index d090544d..639e8f90 100644
--- a/graphql/resolvers/repo.go
+++ b/graphql/resolvers/repo.go
@@ -4,11 +4,11 @@ import (
"context"
"github.com/MichaelMure/git-bug/bug"
- "github.com/MichaelMure/git-bug/cache"
"github.com/MichaelMure/git-bug/entity"
"github.com/MichaelMure/git-bug/graphql/connections"
"github.com/MichaelMure/git-bug/graphql/graph"
"github.com/MichaelMure/git-bug/graphql/models"
+ "github.com/MichaelMure/git-bug/query"
)
var _ graph.RepositoryResolver = &repoResolver{}
@@ -28,19 +28,19 @@ func (repoResolver) AllBugs(_ context.Context, obj *models.Repository, after *st
Last: last,
}
- var query *cache.Query
+ var q *query.Query
if queryStr != nil {
- query2, err := cache.ParseQuery(*queryStr)
+ query2, err := query.Parse(*queryStr)
if err != nil {
return nil, err
}
- query = query2
+ q = query2
} else {
- query = cache.NewQuery()
+ q = query.NewQuery()
}
// Simply pass a []string with the ids to the pagination algorithm
- source := obj.Repo.QueryBugs(query)
+ source := obj.Repo.QueryBugs(q)
// The edger create a custom edge holding just the id
edger := func(id entity.Id, offset int) connections.Edge {
diff --git a/query/lexer.go b/query/lexer.go
new file mode 100644
index 00000000..ec05f44e
--- /dev/null
+++ b/query/lexer.go
@@ -0,0 +1,71 @@
+package query
+
+import (
+ "fmt"
+ "strings"
+ "unicode"
+)
+
+type token struct {
+ qualifier string
+ value string
+}
+
+// TODO: this lexer implementation behave badly with unmatched quotes.
+// A hand written one would be better instead of relying on strings.FieldsFunc()
+
+// tokenize parse and break a input into tokens ready to be
+// interpreted later by a parser to get the semantic.
+func tokenize(query string) ([]token, error) {
+ fields := splitQuery(query)
+
+ var tokens []token
+ for _, field := range fields {
+ split := strings.Split(field, ":")
+ if len(split) != 2 {
+ return nil, fmt.Errorf("can't tokenize \"%s\"", field)
+ }
+
+ if len(split[0]) == 0 {
+ return nil, fmt.Errorf("can't tokenize \"%s\": empty qualifier", field)
+ }
+ if len(split[1]) == 0 {
+ return nil, fmt.Errorf("empty value for qualifier \"%s\"", split[0])
+ }
+
+ tokens = append(tokens, token{
+ qualifier: split[0],
+ value: removeQuote(split[1]),
+ })
+ }
+ return tokens, nil
+}
+
+func splitQuery(query string) []string {
+ lastQuote := rune(0)
+ f := func(c rune) bool {
+ switch {
+ case c == lastQuote:
+ lastQuote = rune(0)
+ return false
+ case lastQuote != rune(0):
+ return false
+ case unicode.In(c, unicode.Quotation_Mark):
+ lastQuote = c
+ return false
+ default:
+ return unicode.IsSpace(c)
+ }
+ }
+
+ return strings.FieldsFunc(query, f)
+}
+
+func removeQuote(field string) string {
+ if len(field) >= 2 {
+ if field[0] == '"' && field[len(field)-1] == '"' {
+ return field[1 : len(field)-1]
+ }
+ }
+ return field
+}
diff --git a/query/lexer_test.go b/query/lexer_test.go
new file mode 100644
index 00000000..922e3fc9
--- /dev/null
+++ b/query/lexer_test.go
@@ -0,0 +1,45 @@
+package query
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestTokenize(t *testing.T) {
+ var tests = []struct {
+ input string
+ tokens []token
+ }{
+ {"gibberish", nil},
+ {"status:", nil},
+ {":value", nil},
+
+ {"status:open", []token{{"status", "open"}}},
+ {"status:closed", []token{{"status", "closed"}}},
+
+ {"author:rene", []token{{"author", "rene"}}},
+ {`author:"René Descartes"`, []token{{"author", "René Descartes"}}},
+
+ {
+ `status:open status:closed author:rene author:"René Descartes"`,
+ []token{
+ {"status", "open"},
+ {"status", "closed"},
+ {"author", "rene"},
+ {"author", "René Descartes"},
+ },
+ },
+ }
+
+ for _, tc := range tests {
+ tokens, err := tokenize(tc.input)
+ if tc.tokens == nil {
+ assert.Error(t, err)
+ assert.Nil(t, tokens)
+ } else {
+ assert.NoError(t, err)
+ assert.Equal(t, tc.tokens, tokens)
+ }
+ }
+}
diff --git a/query/parser.go b/query/parser.go
new file mode 100644
index 00000000..a379f750
--- /dev/null
+++ b/query/parser.go
@@ -0,0 +1,99 @@
+package query
+
+import (
+ "fmt"
+
+ "github.com/MichaelMure/git-bug/bug"
+)
+
+// Parse parse a query DSL
+//
+// Ex: "status:open author:descartes sort:edit-asc"
+//
+// Supported filter qualifiers and syntax are described in docs/queries.md
+func Parse(query string) (*Query, error) {
+ tokens, err := tokenize(query)
+ if err != nil {
+ return nil, err
+ }
+
+ q := &Query{
+ OrderBy: OrderByCreation,
+ OrderDirection: OrderDescending,
+ }
+ sortingDone := false
+
+ for _, t := range tokens {
+ switch t.qualifier {
+ case "status", "state":
+ status, err := bug.StatusFromString(t.value)
+ if err != nil {
+ return nil, err
+ }
+ q.Status = append(q.Status, status)
+ case "author":
+ q.Author = append(q.Author, t.value)
+ case "actor":
+ q.Actor = append(q.Actor, t.value)
+ case "participant":
+ q.Participant = append(q.Participant, t.value)
+ case "label":
+ q.Label = append(q.Label, t.value)
+ case "title":
+ q.Title = append(q.Title, t.value)
+ case "no":
+ switch t.value {
+ case "label":
+ q.NoLabel = true
+ default:
+ return nil, fmt.Errorf("unknown \"no\" filter \"%s\"", t.value)
+ }
+ case "sort":
+ if sortingDone {
+ return nil, fmt.Errorf("multiple sorting")
+ }
+ err = parseSorting(q, t.value)
+ if err != nil {
+ return nil, err
+ }
+ sortingDone = true
+
+ default:
+ return nil, fmt.Errorf("unknown qualifier \"%s\"", t.qualifier)
+ }
+ }
+ return q, nil
+}
+
+func parseSorting(q *Query, value string) error {
+ switch value {
+ // default ASC
+ case "id-desc":
+ q.OrderBy = OrderById
+ q.OrderDirection = OrderDescending
+ case "id", "id-asc":
+ q.OrderBy = OrderById
+ q.OrderDirection = OrderAscending
+
+ // default DESC
+ case "creation", "creation-desc":
+ q.OrderBy = OrderByCreation
+ q.OrderDirection = OrderDescending
+ case "creation-asc":
+ q.OrderBy = OrderByCreation
+ q.OrderDirection = OrderAscending
+
+ // default DESC
+ case "edit", "edit-desc":
+ q.OrderBy = OrderByEdit
+ q.OrderDirection = OrderDescending
+ case "edit-asc":
+ q.OrderBy = OrderByEdit
+ q.OrderDirection = OrderAscending
+
+ default:
+ return fmt.Errorf("unknown sorting %s", value)
+ }
+
+ return nil
+}
diff --git a/query/parser_test.go b/query/parser_test.go
new file mode 100644
index 00000000..6a509adb
--- /dev/null
+++ b/query/parser_test.go
@@ -0,0 +1,97 @@
+package query
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+
+ "github.com/MichaelMure/git-bug/bug"
+)
+
+func TestParse(t *testing.T) {
+ var tests = []struct {
+ input string
+ output *Query
+ }{
+ {"gibberish", nil},
+ {"status:", nil},
+ {":value", nil},
+
+ {"status:open", &Query{
+ Filters: Filters{Status: []bug.Status{bug.OpenStatus}},
+ }},
+ {"status:closed", &Query{
+ Filters: Filters{Status: []bug.Status{bug.ClosedStatus}},
+ }},
+ {"status:unknown", nil},
+
+ {"author:rene", &Query{
+ Filters: Filters{Author: []string{"rene"}},
+ }},
+ {`author:"René Descartes"`, &Query{
+ Filters: Filters{Author: []string{"René Descartes"}},
+ }},
+
+ {"actor:bernhard", &Query{
+ Filters: Filters{Actor: []string{"bernhard"}},
+ }},
+ {"participant:leonhard", &Query{
+ Filters: Filters{Participant: []string{"leonhard"}},
+ }},
+
+ {"label:hello", &Query{
+ Filters: Filters{Label: []string{"hello"}},
+ }},
+ {`label:"Good first issue"`, &Query{
+ Filters: Filters{Label: []string{"Good first issue"}},
+ }},
+
+ {"title:titleOne", &Query{
+ Filters: Filters{Title: []string{"titleOne"}},
+ }},
+ {`title:"Bug titleTwo"`, &Query{
+ Filters: Filters{Title: []string{"Bug titleTwo"}},
+ }},
+
+ {"no:label", &Query{
+ Filters: Filters{NoLabel: true},
+ }},
+
+ {"sort:edit", &Query{
+ OrderBy: OrderByEdit,
+ }},
+ {"sort:unknown", nil},
+
+ {`status:open author:"René Descartes" participant:leonhard label:hello label:"Good first issue" sort:edit-desc`,
+ &Query{
+ Filters: Filters{
+ Status: []bug.Status{bug.OpenStatus},
+ Author: []string{"René Descartes"},
+ Participant: []string{"leonhard"},
+ Label: []string{"hello", "Good first issue"},
+ },
+ OrderBy: OrderByEdit,
+ OrderDirection: OrderDescending,
+ },
+ },
+ }
+
+ for _, tc := range tests {
+ t.Run(tc.input, func(t *testing.T) {
+ query, err := Parse(tc.input)
+ if tc.output == nil {
+ assert.Error(t, err)
+ assert.Nil(t, query)
+ } else {
+ assert.NoError(t, err)
+ if tc.output.OrderBy != 0 {
+ assert.Equal(t, tc.output.OrderBy, query.OrderBy)
+ }
+ if tc.output.OrderDirection != 0 {
+ assert.Equal(t, tc.output.OrderDirection, query.OrderDirection)
+ }
+ assert.Equal(t, tc.output.Filters, query.Filters)
+ }
+ })
+ }
+}
diff --git a/query/query.go b/query/query.go
new file mode 100644
index 00000000..a499ad38
--- /dev/null
+++ b/query/query.go
@@ -0,0 +1,49 @@
+package query
+
+import "github.com/MichaelMure/git-bug/bug"
+
+// Query is the intermediary representation of a Bug's query. It is either
+// produced by parsing a query string (ex: "status:open author:rene") or created
+// manually. This query doesn't do anything by itself and need to be interpreted
+// for the specific domain of application.
+type Query struct {
+ Filters
+ OrderBy
+ OrderDirection
+}
+
+// NewQuery return an identity query with the default sorting (creation-desc).
+func NewQuery() *Query {
+ return &Query{
+ OrderBy: OrderByCreation,
+ OrderDirection: OrderDescending,
+ }
+}
+
+// Filters is a collection of Filter that implement a complex filter
+type Filters struct {
+ Status []bug.Status
+ Author []string
+ Actor []string
+ Participant []string
+ Label []string
+ Title []string
+ NoLabel bool
+}
+
+type OrderBy int
+
+const (
+ _ OrderBy = iota
+ OrderById
+ OrderByCreation
+ OrderByEdit
+)
+
+type OrderDirection int
+
+const (
+ _ OrderDirection = iota
+ OrderAscending
+ OrderDescending
+)
diff --git a/termui/bug_table.go b/termui/bug_table.go
index 41aa4e83..80d5ebcb 100644
--- a/termui/bug_table.go
+++ b/termui/bug_table.go
@@ -12,6 +12,7 @@ import (
"github.com/MichaelMure/git-bug/cache"
"github.com/MichaelMure/git-bug/entity"
+ "github.com/MichaelMure/git-bug/query"
"github.com/MichaelMure/git-bug/util/colors"
)
@@ -26,7 +27,7 @@ const defaultQuery = "status:open"
type bugTable struct {
repo *cache.RepoCache
queryStr string
- query *cache.Query
+ query *query.Query
allIds []entity.Id
excerpts []*cache.BugExcerpt
pageCursor int
@@ -34,14 +35,14 @@ type bugTable struct {
}
func newBugTable(c *cache.RepoCache) *bugTable {
- query, err := cache.ParseQuery(defaultQuery)
+ q, err := query.Parse(defaultQuery)
if err != nil {
panic(err)
}
return &bugTable{
repo: c,
- query: query,
+ query: q,
queryStr: defaultQuery,
pageCursor: 0,
selectCursor: 0,
diff --git a/termui/termui.go b/termui/termui.go
index 1ef960de..96b7583c 100644
--- a/termui/termui.go
+++ b/termui/termui.go
@@ -12,6 +12,7 @@ import (
"github.com/MichaelMure/git-bug/cache"
"github.com/MichaelMure/git-bug/entity"
"github.com/MichaelMure/git-bug/input"
+ "github.com/MichaelMure/git-bug/query"
)
var errTerminateMainloop = errors.New("terminate gocui mainloop")
@@ -336,12 +337,12 @@ func editQueryWithEditor(bt *bugTable) error {
bt.queryStr = queryStr
- query, err := cache.ParseQuery(queryStr)
+ q, err := query.Parse(queryStr)
if err != nil {
ui.msgPopup.Activate(msgPopupErrorTitle, err.Error())
} else {
- bt.query = query
+ bt.query = q
}
initGui(nil)