Add --sort and --reverse flags to bd list command

Implements sorting for bd list by various fields including priority,
created, updated, closed, status, id, title, type, and assignee.

Features:
- --sort flag accepts field name to sort by
- --reverse/-r flag reverses the sort order
- Default sort orders optimized for common usage:
  - priority: ascending (P0 first)
  - dates: descending (newest first)
  - text fields: ascending (alphabetical)

Resolves bd-22g

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Steve Yegge
2025-11-24 01:45:31 -08:00
parent cf560a947c
commit f31ff2c8ad
2 changed files with 118 additions and 64 deletions

File diff suppressed because one or more lines are too long

View File

@@ -6,6 +6,7 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"os" "os"
"sort"
"strings" "strings"
"text/template" "text/template"
"time" "time"
@@ -36,6 +37,59 @@ func parseTimeFlag(s string) (time.Time, error) {
return time.Time{}, fmt.Errorf("unable to parse time %q (try formats: 2006-01-02, 2006-01-02T15:04:05, or RFC3339)", s) return time.Time{}, fmt.Errorf("unable to parse time %q (try formats: 2006-01-02, 2006-01-02T15:04:05, or RFC3339)", s)
} }
// sortIssues sorts a slice of issues by the specified field and direction
func sortIssues(issues []*types.Issue, sortBy string, reverse bool) {
if sortBy == "" {
return
}
sort.Slice(issues, func(i, j int) bool {
var less bool
switch sortBy {
case "priority":
// Lower priority numbers come first (P0 > P1 > P2 > P3 > P4)
less = issues[i].Priority < issues[j].Priority
case "created":
// Default: newest first (descending)
less = issues[i].CreatedAt.After(issues[j].CreatedAt)
case "updated":
// Default: newest first (descending)
less = issues[i].UpdatedAt.After(issues[j].UpdatedAt)
case "closed":
// Default: newest first (descending)
// Handle nil ClosedAt values
if issues[i].ClosedAt == nil && issues[j].ClosedAt == nil {
less = false
} else if issues[i].ClosedAt == nil {
less = false // nil sorts last
} else if issues[j].ClosedAt == nil {
less = true // non-nil sorts before nil
} else {
less = issues[i].ClosedAt.After(*issues[j].ClosedAt)
}
case "status":
less = issues[i].Status < issues[j].Status
case "id":
less = issues[i].ID < issues[j].ID
case "title":
less = strings.ToLower(issues[i].Title) < strings.ToLower(issues[j].Title)
case "type":
less = issues[i].IssueType < issues[j].IssueType
case "assignee":
less = issues[i].Assignee < issues[j].Assignee
default:
// Unknown sort field, no sorting
less = false
}
if reverse {
return !less
}
return less
})
}
var listCmd = &cobra.Command{ var listCmd = &cobra.Command{
Use: "list", Use: "list",
Short: "List issues", Short: "List issues",
@@ -50,6 +104,8 @@ var listCmd = &cobra.Command{
titleSearch, _ := cmd.Flags().GetString("title") titleSearch, _ := cmd.Flags().GetString("title")
idFilter, _ := cmd.Flags().GetString("id") idFilter, _ := cmd.Flags().GetString("id")
longFormat, _ := cmd.Flags().GetBool("long") longFormat, _ := cmd.Flags().GetBool("long")
sortBy, _ := cmd.Flags().GetString("sort")
reverse, _ := cmd.Flags().GetBool("reverse")
// Pattern matching flags // Pattern matching flags
titleContains, _ := cmd.Flags().GetString("title-contains") titleContains, _ := cmd.Flags().GetString("title-contains")
@@ -310,6 +366,9 @@ var listCmd = &cobra.Command{
os.Exit(1) os.Exit(1)
} }
// Apply sorting
sortIssues(issues, sortBy, reverse)
if longFormat { if longFormat {
// Long format: multi-line with details // Long format: multi-line with details
fmt.Printf("\nFound %d issues:\n\n", len(issues)) fmt.Printf("\nFound %d issues:\n\n", len(issues))
@@ -363,6 +422,9 @@ var listCmd = &cobra.Command{
} }
} }
// Apply sorting
sortIssues(issues, sortBy, reverse)
// Handle format flag // Handle format flag
if formatStr != "" { if formatStr != "" {
if err := outputFormattedList(ctx, store, issues, formatStr); err != nil { if err := outputFormattedList(ctx, store, issues, formatStr); err != nil {
@@ -466,6 +528,8 @@ func init() {
listCmd.Flags().String("format", "", "Output format: 'digraph' (for golang.org/x/tools/cmd/digraph), 'dot' (Graphviz), or Go template") listCmd.Flags().String("format", "", "Output format: 'digraph' (for golang.org/x/tools/cmd/digraph), 'dot' (Graphviz), or Go template")
listCmd.Flags().Bool("all", false, "Show all issues (default behavior; flag provided for CLI familiarity)") listCmd.Flags().Bool("all", false, "Show all issues (default behavior; flag provided for CLI familiarity)")
listCmd.Flags().Bool("long", false, "Show detailed multi-line output for each issue") listCmd.Flags().Bool("long", false, "Show detailed multi-line output for each issue")
listCmd.Flags().String("sort", "", "Sort by field: priority, created, updated, closed, status, id, title, type, assignee")
listCmd.Flags().BoolP("reverse", "r", false, "Reverse sort order")
// Pattern matching // Pattern matching
listCmd.Flags().String("title-contains", "", "Filter by title substring (case-insensitive)") listCmd.Flags().String("title-contains", "", "Filter by title substring (case-insensitive)")