ls: support expressing a query with flags as well

Michael Muré created

Change summary

cache/filter.go              |  8 --
cache/query.go               |  7 ++
commands/commands.go         |  2 
commands/comment.go          |  2 
commands/label.go            |  2 
commands/ls.go               | 98 ++++++++++++++++++++++++++++++++++++-
commands/new.go              |  2 
commands/webui.go            |  3 +
doc/man/git-bug-commands.1   |  8 +-
doc/man/git-bug-comment.1    |  8 +-
doc/man/git-bug-label.1      |  8 +-
doc/man/git-bug-ls.1         | 49 +++++++++++++++++-
doc/man/git-bug-new.1        | 16 +++---
doc/man/git-bug-webui.1      |  8 +-
doc/md/git-bug.md            |  2 
doc/md/git-bug_commands.md   |  2 
doc/md/git-bug_comment.md    |  2 
doc/md/git-bug_label.md      |  2 
doc/md/git-bug_ls.md         | 27 ++++++++-
doc/md/git-bug_new.md        |  4 
doc/md/git-bug_webui.md      |  2 
misc/bash_completion/git-bug | 30 +++++++++--
22 files changed, 237 insertions(+), 55 deletions(-)

Detailed changes

cache/filter.go 🔗

@@ -1,8 +1,6 @@
 package cache
 
 import (
-	"strings"
-
 	"github.com/MichaelMure/git-bug/bug"
 )
 
@@ -23,12 +21,8 @@ func StatusFilter(query string) (Filter, error) {
 
 // AuthorFilter return a Filter that match a bug author
 func AuthorFilter(query string) Filter {
-	cleaned := strings.TrimFunc(query, func(r rune) bool {
-		return r == '"' || r == '\''
-	})
-
 	return func(excerpt *BugExcerpt) bool {
-		return excerpt.Author.Match(cleaned)
+		return excerpt.Author.Match(query)
 	}
 }
 

cache/query.go 🔗

@@ -12,6 +12,11 @@ type Query struct {
 	OrderDirection
 }
 
+// Return an identity query
+func NewQuery() *Query {
+	return &Query{}
+}
+
 // ParseQuery parse a query DSL
 //
 // Ex: "status:open author:descartes sort:edit-asc"
@@ -112,7 +117,7 @@ func (q *Query) parseNoFilter(query string) error {
 	case "label":
 		q.NoFilters = append(q.NoFilters, NoLabelFilter())
 	default:
-		return fmt.Errorf("unknown \"no\" filter")
+		return fmt.Errorf("unknown \"no\" filter %s", query)
 	}
 
 	return nil

commands/commands.go 🔗

@@ -50,6 +50,8 @@ var commandsCmd = &cobra.Command{
 func init() {
 	RootCmd.AddCommand(commandsCmd)
 
+	commandsCmd.Flags().SortFlags = false
+
 	commandsCmd.Flags().BoolVarP(&commandsDesc, "pretty", "p", false,
 		"Output the command description as well as Markdown compatible comment",
 	)

commands/comment.go 🔗

@@ -73,6 +73,8 @@ var commentCmd = &cobra.Command{
 func init() {
 	RootCmd.AddCommand(commentCmd)
 
+	commentCmd.Flags().SortFlags = false
+
 	commentCmd.Flags().StringVarP(&commentMessageFile, "file", "F", "",
 		"Take the message from the given file. Use - to read the message from the standard input",
 	)

commands/label.go 🔗

@@ -57,6 +57,8 @@ var labelCmd = &cobra.Command{
 func init() {
 	RootCmd.AddCommand(labelCmd)
 
+	labelCmd.Flags().SortFlags = false
+
 	labelCmd.Flags().BoolVarP(&labelRemove, "remove", "r", false,
 		"Remove a label",
 	)

commands/ls.go 🔗

@@ -9,6 +9,15 @@ import (
 	"github.com/spf13/cobra"
 )
 
+var (
+	lsStatusQuery   []string
+	lsAuthorQuery   []string
+	lsLabelQuery    []string
+	lsNoQuery       []string
+	lsSortBy        string
+	lsSortDirection string
+)
+
 func runLsBug(cmd *cobra.Command, args []string) error {
 	backend, err := cache.NewRepoCache(repo)
 	if err != nil {
@@ -20,6 +29,11 @@ func runLsBug(cmd *cobra.Command, args []string) error {
 	if len(args) >= 1 {
 		query, err = cache.ParseQuery(args[0])
 
+		if err != nil {
+			return err
+		}
+	} else {
+		query, err = lsQueryFromFlags()
 		if err != nil {
 			return err
 		}
@@ -58,12 +72,90 @@ 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)
+		if err != nil {
+			return nil, err
+		}
+		query.Status = append(query.Status, f)
+	}
+
+	for _, author := range lsAuthorQuery {
+		f := cache.AuthorFilter(author)
+		query.Author = append(query.Author, f)
+	}
+
+	for _, label := range lsLabelQuery {
+		f := cache.LabelFilter(label)
+		query.Label = append(query.Label, f)
+	}
+
+	for _, no := range lsNoQuery {
+		switch no {
+		case "label":
+			query.NoFilters = append(query.NoFilters, cache.NoLabelFilter())
+		default:
+			return nil, fmt.Errorf("unknown \"no\" filter %s", no)
+		}
+	}
+
+	switch lsSortBy {
+	case "id":
+		query.OrderBy = cache.OrderById
+	case "creation":
+		query.OrderBy = cache.OrderByCreation
+	case "edit":
+		query.OrderBy = cache.OrderByEdit
+	default:
+		return nil, fmt.Errorf("unknown sort flag %s", lsSortBy)
+	}
+
+	switch lsSortDirection {
+	case "asc":
+		query.OrderDirection = cache.OrderAscending
+	case "desc":
+		query.OrderDirection = cache.OrderDescending
+	default:
+		return nil, fmt.Errorf("unknown sort direction %s", lsSortDirection)
+	}
+
+	return query, nil
+}
+
 var lsCmd = &cobra.Command{
-	Use:   "ls <query>",
-	Short: "Display a summary of all bugs",
-	RunE:  runLsBug,
+	Use:   "ls [<query>]",
+	Short: "List bugs",
+	Long: `Display a summary of each bugs.
+
+You can pass an additional query to filter and order the list. This query can be expressed either with a simple query language or with flags.`,
+	Example: `List open bugs sorted by last edition with a query:
+git bug ls "status:open sort:edit-desc"
+
+List closed bugs sorted by creation with flags:
+git bug ls --status closed --by creation
+`,
+	RunE: runLsBug,
 }
 
 func init() {
 	RootCmd.AddCommand(lsCmd)
+
+	lsCmd.Flags().SortFlags = false
+
+	lsCmd.Flags().StringSliceVarP(&lsStatusQuery, "status", "s", nil,
+		"Filter by status. Valid values are [open,closed]")
+	lsCmd.Flags().StringSliceVarP(&lsAuthorQuery, "author", "a", nil,
+		"Filter by author")
+	lsCmd.Flags().StringSliceVarP(&lsLabelQuery, "label", "l", nil,
+		"Filter by label")
+	lsCmd.Flags().StringSliceVarP(&lsNoQuery, "no", "n", nil,
+		"Filter by absence of something. Valid values are [label]")
+	lsCmd.Flags().StringVarP(&lsSortBy, "by", "b", "creation",
+		"Sort the results by a characteristic. Valid values are [id,creation,edit]")
+	lsCmd.Flags().StringVarP(&lsSortDirection, "direction", "d", "asc",
+		"Select the sorting direction. Valid values are [asc,desc]")
 }

commands/new.go 🔗

@@ -61,6 +61,8 @@ var newCmd = &cobra.Command{
 func init() {
 	RootCmd.AddCommand(newCmd)
 
+	newCmd.Flags().SortFlags = false
+
 	newCmd.Flags().StringVarP(&newTitle, "title", "t", "",
 		"Provide a title to describe the issue",
 	)

commands/webui.go 🔗

@@ -199,5 +199,8 @@ var webUICmd = &cobra.Command{
 
 func init() {
 	RootCmd.AddCommand(webUICmd)
+
+	webUICmd.Flags().SortFlags = false
+
 	webUICmd.Flags().IntVarP(&port, "port", "p", 0, "Port to listen to")
 }

doc/man/git-bug-commands.1 🔗

@@ -19,14 +19,14 @@ Display available commands
 
 
 .SH OPTIONS
-.PP
-\fB\-h\fP, \fB\-\-help\fP[=false]
-    help for commands
-
 .PP
 \fB\-p\fP, \fB\-\-pretty\fP[=false]
     Output the command description as well as Markdown compatible comment
 
+.PP
+\fB\-h\fP, \fB\-\-help\fP[=false]
+    help for commands
+
 
 .SH SEE ALSO
 .PP

doc/man/git-bug-comment.1 🔗

@@ -23,14 +23,14 @@ Add a new comment to a bug
 \fB\-F\fP, \fB\-\-file\fP=""
     Take the message from the given file. Use \- to read the message from the standard input
 
-.PP
-\fB\-h\fP, \fB\-\-help\fP[=false]
-    help for comment
-
 .PP
 \fB\-m\fP, \fB\-\-message\fP=""
     Provide the new message from the command line
 
+.PP
+\fB\-h\fP, \fB\-\-help\fP[=false]
+    help for comment
+
 
 .SH SEE ALSO
 .PP

doc/man/git-bug-label.1 🔗

@@ -19,14 +19,14 @@ Manipulate bug's label
 
 
 .SH OPTIONS
-.PP
-\fB\-h\fP, \fB\-\-help\fP[=false]
-    help for label
-
 .PP
 \fB\-r\fP, \fB\-\-remove\fP[=false]
     Remove a label
 
+.PP
+\fB\-h\fP, \fB\-\-help\fP[=false]
+    help for label
+
 
 .SH SEE ALSO
 .PP

doc/man/git-bug-ls.1 🔗

@@ -5,25 +5,68 @@
 
 .SH NAME
 .PP
-git\-bug\-ls \- Display a summary of all bugs
+git\-bug\-ls \- List bugs
 
 
 .SH SYNOPSIS
 .PP
-\fBgit\-bug ls <query> [flags]\fP
+\fBgit\-bug ls [<query>] [flags]\fP
 
 
 .SH DESCRIPTION
 .PP
-Display a summary of all bugs
+Display a summary of each bugs.
+
+.PP
+You can pass an additional query to filter and order the list. This query can be expressed either with a simple query language or with flags.
 
 
 .SH OPTIONS
+.PP
+\fB\-s\fP, \fB\-\-status\fP=[]
+    Filter by status. Valid values are [open,closed]
+
+.PP
+\fB\-a\fP, \fB\-\-author\fP=[]
+    Filter by author
+
+.PP
+\fB\-l\fP, \fB\-\-label\fP=[]
+    Filter by label
+
+.PP
+\fB\-n\fP, \fB\-\-no\fP=[]
+    Filter by absence of something. Valid values are [label]
+
+.PP
+\fB\-b\fP, \fB\-\-by\fP="creation"
+    Sort the results by a characteristic. Valid values are [id,creation,edit]
+
+.PP
+\fB\-d\fP, \fB\-\-direction\fP="asc"
+    Select the sorting direction. Valid values are [asc,desc]
+
 .PP
 \fB\-h\fP, \fB\-\-help\fP[=false]
     help for ls
 
 
+.SH EXAMPLE
+.PP
+.RS
+
+.nf
+List open bugs sorted by last edition with a query:
+git bug ls "status:open sort:edit\-desc"
+
+List closed bugs sorted by creation with flags:
+git bug ls \-\-status closed \-\-by creation
+
+
+.fi
+.RE
+
+
 .SH SEE ALSO
 .PP
 \fBgit\-bug(1)\fP

doc/man/git-bug-new.1 🔗

@@ -20,20 +20,20 @@ Create a new bug
 
 .SH OPTIONS
 .PP
-\fB\-F\fP, \fB\-\-file\fP=""
-    Take the message from the given file. Use \- to read the message from the standard input
-
-.PP
-\fB\-h\fP, \fB\-\-help\fP[=false]
-    help for new
+\fB\-t\fP, \fB\-\-title\fP=""
+    Provide a title to describe the issue
 
 .PP
 \fB\-m\fP, \fB\-\-message\fP=""
     Provide a message to describe the issue
 
 .PP
-\fB\-t\fP, \fB\-\-title\fP=""
-    Provide a title to describe the issue
+\fB\-F\fP, \fB\-\-file\fP=""
+    Take the message from the given file. Use \- to read the message from the standard input
+
+.PP
+\fB\-h\fP, \fB\-\-help\fP[=false]
+    help for new
 
 
 .SH SEE ALSO

doc/man/git-bug-webui.1 🔗

@@ -19,14 +19,14 @@ Launch the web UI
 
 
 .SH OPTIONS
-.PP
-\fB\-h\fP, \fB\-\-help\fP[=false]
-    help for webui
-
 .PP
 \fB\-p\fP, \fB\-\-port\fP=0
     Port to listen to
 
+.PP
+\fB\-h\fP, \fB\-\-help\fP[=false]
+    help for webui
+
 
 .SH SEE ALSO
 .PP

doc/md/git-bug.md 🔗

@@ -24,7 +24,7 @@ git-bug [flags]
 * [git-bug commands](git-bug_commands.md)	 - Display available commands
 * [git-bug comment](git-bug_comment.md)	 - Add a new comment to a bug
 * [git-bug label](git-bug_label.md)	 - Manipulate bug's label
-* [git-bug ls](git-bug_ls.md)	 - Display a summary of all bugs
+* [git-bug ls](git-bug_ls.md)	 - List bugs
 * [git-bug new](git-bug_new.md)	 - Create a new bug
 * [git-bug open](git-bug_open.md)	 - Mark the bug as open
 * [git-bug pull](git-bug_pull.md)	 - Pull bugs update from a git remote

doc/md/git-bug_commands.md 🔗

@@ -13,8 +13,8 @@ git-bug commands [<option>...] [flags]
 ### Options
 
 ```
-  -h, --help     help for commands
   -p, --pretty   Output the command description as well as Markdown compatible comment
+  -h, --help     help for commands
 ```
 
 ### SEE ALSO

doc/md/git-bug_comment.md 🔗

@@ -14,8 +14,8 @@ git-bug comment <id> [flags]
 
 ```
   -F, --file string      Take the message from the given file. Use - to read the message from the standard input
-  -h, --help             help for comment
   -m, --message string   Provide the new message from the command line
+  -h, --help             help for comment
 ```
 
 ### SEE ALSO

doc/md/git-bug_label.md 🔗

@@ -13,8 +13,8 @@ git-bug label <id> [<label>...] [flags]
 ### Options
 
 ```
-  -h, --help     help for label
   -r, --remove   Remove a label
+  -h, --help     help for label
 ```
 
 ### SEE ALSO

doc/md/git-bug_ls.md 🔗

@@ -1,19 +1,38 @@
 ## git-bug ls
 
-Display a summary of all bugs
+List bugs
 
 ### Synopsis
 
-Display a summary of all bugs
+Display a summary of each bugs.
 
+You can pass an additional query to filter and order the list. This query can be expressed either with a simple query language or with flags.
+
+```
+git-bug ls [<query>] [flags]
 ```
-git-bug ls <query> [flags]
+
+### Examples
+
+```
+List open bugs sorted by last edition with a query:
+git bug ls "status:open sort:edit-desc"
+
+List closed bugs sorted by creation with flags:
+git bug ls --status closed --by creation
+
 ```
 
 ### Options
 
 ```
-  -h, --help   help for ls
+  -s, --status strings     Filter by status. Valid values are [open,closed]
+  -a, --author strings     Filter by author
+  -l, --label strings      Filter by label
+  -n, --no strings         Filter by absence of something. Valid values are [label]
+  -b, --by string          Sort the results by a characteristic. Valid values are [id,creation,edit] (default "creation")
+  -d, --direction string   Select the sorting direction. Valid values are [asc,desc] (default "asc")
+  -h, --help               help for ls
 ```
 
 ### SEE ALSO

doc/md/git-bug_new.md 🔗

@@ -13,10 +13,10 @@ git-bug new [flags]
 ### Options
 
 ```
+  -t, --title string     Provide a title to describe the issue
+  -m, --message string   Provide a message to describe the issue
   -F, --file string      Take the message from the given file. Use - to read the message from the standard input
   -h, --help             help for new
-  -m, --message string   Provide a message to describe the issue
-  -t, --title string     Provide a title to describe the issue
 ```
 
 ### SEE ALSO

doc/md/git-bug_webui.md 🔗

@@ -13,8 +13,8 @@ git-bug webui [flags]
 ### Options
 
 ```
-  -h, --help       help for webui
   -p, --port int   Port to listen to
+  -h, --help       help for webui
 ```
 
 ### SEE ALSO

misc/bash_completion/git-bug 🔗

@@ -354,6 +354,24 @@ _git-bug_ls()
     flags_with_completion=()
     flags_completion=()
 
+    flags+=("--status=")
+    two_word_flags+=("-s")
+    local_nonpersistent_flags+=("--status=")
+    flags+=("--author=")
+    two_word_flags+=("-a")
+    local_nonpersistent_flags+=("--author=")
+    flags+=("--label=")
+    two_word_flags+=("-l")
+    local_nonpersistent_flags+=("--label=")
+    flags+=("--no=")
+    two_word_flags+=("-n")
+    local_nonpersistent_flags+=("--no=")
+    flags+=("--by=")
+    two_word_flags+=("-b")
+    local_nonpersistent_flags+=("--by=")
+    flags+=("--direction=")
+    two_word_flags+=("-d")
+    local_nonpersistent_flags+=("--direction=")
 
     must_have_one_flag=()
     must_have_one_noun=()
@@ -374,15 +392,15 @@ _git-bug_new()
     flags_with_completion=()
     flags_completion=()
 
-    flags+=("--file=")
-    two_word_flags+=("-F")
-    local_nonpersistent_flags+=("--file=")
-    flags+=("--message=")
-    two_word_flags+=("-m")
-    local_nonpersistent_flags+=("--message=")
     flags+=("--title=")
     two_word_flags+=("-t")
     local_nonpersistent_flags+=("--title=")
+    flags+=("--message=")
+    two_word_flags+=("-m")
+    local_nonpersistent_flags+=("--message=")
+    flags+=("--file=")
+    two_word_flags+=("-F")
+    local_nonpersistent_flags+=("--file=")
 
     must_have_one_flag=()
     must_have_one_noun=()