1package commands
2
3import (
4 "encoding/json"
5 "fmt"
6 "strings"
7 "time"
8
9 text "github.com/MichaelMure/go-term-text"
10 "github.com/spf13/cobra"
11
12 "github.com/MichaelMure/git-bug/bug"
13 "github.com/MichaelMure/git-bug/cache"
14 "github.com/MichaelMure/git-bug/query"
15 "github.com/MichaelMure/git-bug/util/colors"
16 "github.com/MichaelMure/git-bug/util/interrupt"
17)
18
19var (
20 lsQuery query.Query
21
22 lsStatusQuery []string
23 lsNoQuery []string
24 lsSortBy string
25 lsSortDirection string
26 lsOutputFormat string
27)
28
29func runLsBug(_ *cobra.Command, args []string) error {
30 backend, err := cache.NewRepoCache(repo)
31 if err != nil {
32 return err
33 }
34 defer backend.Close()
35 interrupt.RegisterCleaner(backend.Close)
36
37 var q *query.Query
38 if len(args) >= 1 {
39 q, err = query.Parse(strings.Join(args, " "))
40
41 if err != nil {
42 return err
43 }
44 } else {
45 err = completeQuery()
46 if err != nil {
47 return err
48 }
49 q = &lsQuery
50 }
51
52 allIds := backend.QueryBugs(q)
53
54 bugExcerpt := make([]*cache.BugExcerpt, len(allIds))
55 for i, id := range allIds {
56 b, err := backend.ResolveBugExcerpt(id)
57 if err != nil {
58 return err
59 }
60 bugExcerpt[i] = b
61 }
62
63 switch lsOutputFormat {
64 case "plain":
65 return lsPlainFormatter(backend, bugExcerpt)
66 case "json":
67 return lsJsonFormatter(backend, bugExcerpt)
68 case "default":
69 return lsDefaultFormatter(backend, bugExcerpt)
70 default:
71 return fmt.Errorf("unknown format %s", lsOutputFormat)
72 }
73}
74
75type JSONBugExcerpt struct {
76 Id string `json:"id"`
77 HumanId string `json:"human_id"`
78 CreationTime time.Time `json:"creation_time"`
79 LastEdited time.Time `json:"last_edited"`
80
81 Status string `json:"status"`
82 Labels []bug.Label `json:"labels"`
83 Title string `json:"title"`
84 Actors []JSONIdentity `json:"actors"`
85 Participants []JSONIdentity `json:"participants"`
86 Author JSONIdentity `json:"author"`
87
88 Comments int `json:"comments"`
89 Metadata map[string]string `json:"metadata"`
90}
91
92func lsJsonFormatter(backend *cache.RepoCache, bugExcerpts []*cache.BugExcerpt) error {
93 jsonBugs := make([]JSONBugExcerpt, len(bugExcerpts))
94 for i, b := range bugExcerpts {
95 jsonBug := JSONBugExcerpt{
96 b.Id.String(),
97 b.Id.Human(),
98 time.Unix(b.CreateUnixTime, 0),
99 time.Unix(b.EditUnixTime, 0),
100 b.Status.String(),
101 b.Labels,
102 b.Title,
103 []JSONIdentity{},
104 []JSONIdentity{},
105 JSONIdentity{},
106 b.LenComments,
107 b.CreateMetadata,
108 }
109
110 if b.AuthorId != "" {
111 author, err := backend.ResolveIdentityExcerpt(b.AuthorId)
112 if err != nil {
113 return err
114 }
115
116 jsonBug.Author.Name = author.DisplayName()
117 jsonBug.Author.Login = author.Login
118 jsonBug.Author.Id = author.Id.String()
119 jsonBug.Author.HumanId = author.Id.Human()
120 } else {
121 jsonBug.Author.Name = b.LegacyAuthor.DisplayName()
122 jsonBug.Author.Login = b.LegacyAuthor.Login
123 }
124
125 for _, element := range b.Actors {
126 actor, err := backend.ResolveIdentityExcerpt(element)
127 if err != nil {
128 return err
129 }
130
131 jsonBug.Actors = append(jsonBug.Actors, JSONIdentity{
132 actor.Id.String(),
133 actor.Id.Human(),
134 actor.Name,
135 actor.Login,
136 })
137 }
138
139 for _, element := range b.Participants {
140 participant, err := backend.ResolveIdentityExcerpt(element)
141 if err != nil {
142 return err
143 }
144 jsonBug.Participants = append(jsonBug.Participants, JSONIdentity{
145 participant.Id.String(),
146 participant.Id.Human(),
147 participant.DisplayName(),
148 participant.Login,
149 })
150 }
151
152 jsonBugs[i] = jsonBug
153 }
154 jsonObject, _ := json.MarshalIndent(jsonBugs, "", " ")
155 fmt.Printf("%s\n", jsonObject)
156 return nil
157}
158
159func lsDefaultFormatter(backend *cache.RepoCache, bugExcerpts []*cache.BugExcerpt) error {
160 for _, b := range bugExcerpts {
161 var name string
162 if b.AuthorId != "" {
163 author, err := backend.ResolveIdentityExcerpt(b.AuthorId)
164 if err != nil {
165 return err
166 }
167 name = author.DisplayName()
168 } else {
169 name = b.LegacyAuthor.DisplayName()
170 }
171
172 var labelsTxt strings.Builder
173 for _, l := range b.Labels {
174 lc256 := l.Color().Term256()
175 labelsTxt.WriteString(lc256.Escape())
176 labelsTxt.WriteString(" ◼")
177 labelsTxt.WriteString(lc256.Unescape())
178 }
179
180 // truncate + pad if needed
181 labelsFmt := text.TruncateMax(labelsTxt.String(), 10)
182 titleFmt := text.LeftPadMaxLine(b.Title, 50-text.Len(labelsFmt), 0)
183 authorFmt := text.LeftPadMaxLine(name, 15, 0)
184
185 comments := fmt.Sprintf("%4d 💬", b.LenComments)
186 if b.LenComments > 9999 {
187 comments = " ∞ 💬"
188 }
189
190 fmt.Printf("%s %s\t%s\t%s\t%s\n",
191 colors.Cyan(b.Id.Human()),
192 colors.Yellow(b.Status),
193 titleFmt+labelsFmt,
194 colors.Magenta(authorFmt),
195 comments,
196 )
197 }
198 return nil
199}
200
201func lsPlainFormatter(_ *cache.RepoCache, bugExcerpts []*cache.BugExcerpt) error {
202 for _, b := range bugExcerpts {
203 fmt.Printf("[%s] %s\n", b.Status, b.Title)
204 }
205 return nil
206}
207
208// Finish the command flags transformation into the query.Query
209func completeQuery() error {
210 for _, str := range lsStatusQuery {
211 status, err := bug.StatusFromString(str)
212 if err != nil {
213 return err
214 }
215 lsQuery.Status = append(lsQuery.Status, status)
216 }
217
218 for _, no := range lsNoQuery {
219 switch no {
220 case "label":
221 lsQuery.NoLabel = true
222 default:
223 return fmt.Errorf("unknown \"no\" filter %s", no)
224 }
225 }
226
227 switch lsSortBy {
228 case "id":
229 lsQuery.OrderBy = query.OrderById
230 case "creation":
231 lsQuery.OrderBy = query.OrderByCreation
232 case "edit":
233 lsQuery.OrderBy = query.OrderByEdit
234 default:
235 return fmt.Errorf("unknown sort flag %s", lsSortBy)
236 }
237
238 switch lsSortDirection {
239 case "asc":
240 lsQuery.OrderDirection = query.OrderAscending
241 case "desc":
242 lsQuery.OrderDirection = query.OrderDescending
243 default:
244 return fmt.Errorf("unknown sort direction %s", lsSortDirection)
245 }
246
247 return nil
248}
249
250var lsCmd = &cobra.Command{
251 Use: "ls [<query>]",
252 Short: "List bugs.",
253 Long: `Display a summary of each bugs.
254
255You 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.`,
256 Example: `List open bugs sorted by last edition with a query:
257git bug ls status:open sort:edit-desc
258
259List closed bugs sorted by creation with flags:
260git bug ls --status closed --by creation
261`,
262 PreRunE: loadRepo,
263 RunE: runLsBug,
264}
265
266func init() {
267 RootCmd.AddCommand(lsCmd)
268
269 lsCmd.Flags().SortFlags = false
270
271 lsCmd.Flags().StringSliceVarP(&lsStatusQuery, "status", "s", nil,
272 "Filter by status. Valid values are [open,closed]")
273 lsCmd.Flags().StringSliceVarP(&lsQuery.Author, "author", "a", nil,
274 "Filter by author")
275 lsCmd.Flags().StringSliceVarP(&lsQuery.Participant, "participant", "p", nil,
276 "Filter by participant")
277 lsCmd.Flags().StringSliceVarP(&lsQuery.Actor, "actor", "A", nil,
278 "Filter by actor")
279 lsCmd.Flags().StringSliceVarP(&lsQuery.Label, "label", "l", nil,
280 "Filter by label")
281 lsCmd.Flags().StringSliceVarP(&lsQuery.Title, "title", "t", nil,
282 "Filter by title")
283 lsCmd.Flags().StringSliceVarP(&lsNoQuery, "no", "n", nil,
284 "Filter by absence of something. Valid values are [label]")
285 lsCmd.Flags().StringVarP(&lsSortBy, "by", "b", "creation",
286 "Sort the results by a characteristic. Valid values are [id,creation,edit]")
287 lsCmd.Flags().StringVarP(&lsSortDirection, "direction", "d", "asc",
288 "Select the sorting direction. Valid values are [asc,desc]")
289 lsCmd.Flags().StringVarP(&lsOutputFormat, "format", "f", "default",
290 "Select the output formatting style. Valid values are [default,plain,json]")
291}