1package commands
2
3import (
4 "encoding/json"
5 "errors"
6 "fmt"
7 "strings"
8
9 "github.com/spf13/cobra"
10
11 "github.com/MichaelMure/git-bug/bug"
12 "github.com/MichaelMure/git-bug/cache"
13 _select "github.com/MichaelMure/git-bug/commands/select"
14 "github.com/MichaelMure/git-bug/util/colors"
15 "github.com/MichaelMure/git-bug/util/interrupt"
16)
17
18type showOptions struct {
19 query string
20 format string
21}
22
23func newShowCommand() *cobra.Command {
24 env := newEnv()
25 options := showOptions{}
26
27 cmd := &cobra.Command{
28 Use: "show [<id>]",
29 Short: "Display the details of a bug.",
30 PreRunE: loadRepo(env),
31 RunE: func(cmd *cobra.Command, args []string) error {
32 return runShow(env, options, args)
33 },
34 }
35
36 flags := cmd.Flags()
37 flags.SortFlags = false
38
39 flags.StringVarP(&options.query, "field", "", "",
40 "Select field to display. Valid values are [author,authorEmail,createTime,lastEdit,humanId,id,labels,shortId,status,title,actors,participants]")
41 flags.StringVarP(&options.format, "format", "f", "default",
42 "Select the output formatting style. Valid values are [default,json,org-mode]")
43
44 return cmd
45}
46
47func runShow(env *Env, opts showOptions, args []string) error {
48 backend, err := cache.NewRepoCache(env.repo)
49 if err != nil {
50 return err
51 }
52 defer backend.Close()
53 interrupt.RegisterCleaner(backend.Close)
54
55 b, args, err := _select.ResolveBug(backend, args)
56 if err != nil {
57 return err
58 }
59
60 snap := b.Snapshot()
61
62 if len(snap.Comments) == 0 {
63 return errors.New("invalid bug: no comment")
64 }
65
66 if opts.query != "" {
67 switch opts.query {
68 case "author":
69 env.out.Printf("%s\n", snap.Author.DisplayName())
70 case "authorEmail":
71 env.out.Printf("%s\n", snap.Author.Email())
72 case "createTime":
73 env.out.Printf("%s\n", snap.CreateTime.String())
74 case "lastEdit":
75 env.out.Printf("%s\n", snap.EditTime().String())
76 case "humanId":
77 env.out.Printf("%s\n", snap.Id().Human())
78 case "id":
79 env.out.Printf("%s\n", snap.Id())
80 case "labels":
81 for _, l := range snap.Labels {
82 env.out.Printf("%s\n", l.String())
83 }
84 case "actors":
85 for _, a := range snap.Actors {
86 env.out.Printf("%s\n", a.DisplayName())
87 }
88 case "participants":
89 for _, p := range snap.Participants {
90 env.out.Printf("%s\n", p.DisplayName())
91 }
92 case "shortId":
93 env.out.Printf("%s\n", snap.Id().Human())
94 case "status":
95 env.out.Printf("%s\n", snap.Status)
96 case "title":
97 env.out.Printf("%s\n", snap.Title)
98 default:
99 return fmt.Errorf("\nUnsupported field: %s\n", opts.query)
100 }
101
102 return nil
103 }
104
105 switch opts.format {
106 case "org-mode":
107 return showOrgModeFormatter(env, snap)
108 case "json":
109 return showJsonFormatter(env, snap)
110 case "default":
111 return showDefaultFormatter(env, snap)
112 default:
113 return fmt.Errorf("unknown format %s", opts.format)
114 }
115}
116
117func showDefaultFormatter(env *Env, snapshot *bug.Snapshot) error {
118 // Header
119 env.out.Printf("%s [%s] %s\n\n",
120 colors.Cyan(snapshot.Id().Human()),
121 colors.Yellow(snapshot.Status),
122 snapshot.Title,
123 )
124
125 env.out.Printf("%s opened this issue %s\n",
126 colors.Magenta(snapshot.Author.DisplayName()),
127 snapshot.CreateTime.String(),
128 )
129
130 env.out.Printf("This was last edited at %s\n\n",
131 snapshot.EditTime().String(),
132 )
133
134 // Labels
135 var labels = make([]string, len(snapshot.Labels))
136 for i := range snapshot.Labels {
137 labels[i] = string(snapshot.Labels[i])
138 }
139
140 env.out.Printf("labels: %s\n",
141 strings.Join(labels, ", "),
142 )
143
144 // Actors
145 var actors = make([]string, len(snapshot.Actors))
146 for i := range snapshot.Actors {
147 actors[i] = snapshot.Actors[i].DisplayName()
148 }
149
150 env.out.Printf("actors: %s\n",
151 strings.Join(actors, ", "),
152 )
153
154 // Participants
155 var participants = make([]string, len(snapshot.Participants))
156 for i := range snapshot.Participants {
157 participants[i] = snapshot.Participants[i].DisplayName()
158 }
159
160 env.out.Printf("participants: %s\n\n",
161 strings.Join(participants, ", "),
162 )
163
164 // Comments
165 indent := " "
166
167 for i, comment := range snapshot.Comments {
168 var message string
169 env.out.Printf("%s#%d %s <%s>\n\n",
170 indent,
171 i,
172 comment.Author.DisplayName(),
173 comment.Author.Email(),
174 )
175
176 if comment.Message == "" {
177 message = colors.GreyBold("No description provided.")
178 } else {
179 message = comment.Message
180 }
181
182 env.out.Printf("%s%s\n\n\n",
183 indent,
184 message,
185 )
186 }
187
188 return nil
189}
190
191type JSONBugSnapshot struct {
192 Id string `json:"id"`
193 HumanId string `json:"human_id"`
194 CreateTime JSONTime `json:"create_time"`
195 EditTime JSONTime `json:"edit_time"`
196 Status string `json:"status"`
197 Labels []bug.Label `json:"labels"`
198 Title string `json:"title"`
199 Author JSONIdentity `json:"author"`
200 Actors []JSONIdentity `json:"actors"`
201 Participants []JSONIdentity `json:"participants"`
202 Comments []JSONComment `json:"comments"`
203}
204
205type JSONComment struct {
206 Id string `json:"id"`
207 HumanId string `json:"human_id"`
208 Author JSONIdentity `json:"author"`
209 Message string `json:"message"`
210}
211
212func NewJSONComment(comment bug.Comment) JSONComment {
213 return JSONComment{
214 Id: comment.Id().String(),
215 HumanId: comment.Id().Human(),
216 Author: NewJSONIdentity(comment.Author),
217 Message: comment.Message,
218 }
219}
220
221func showJsonFormatter(env *Env, snapshot *bug.Snapshot) error {
222 jsonBug := JSONBugSnapshot{
223 Id: snapshot.Id().String(),
224 HumanId: snapshot.Id().Human(),
225 CreateTime: NewJSONTime(snapshot.CreateTime, 0),
226 EditTime: NewJSONTime(snapshot.EditTime(), 0),
227 Status: snapshot.Status.String(),
228 Labels: snapshot.Labels,
229 Title: snapshot.Title,
230 Author: NewJSONIdentity(snapshot.Author),
231 }
232
233 jsonBug.Actors = make([]JSONIdentity, len(snapshot.Actors))
234 for i, element := range snapshot.Actors {
235 jsonBug.Actors[i] = NewJSONIdentity(element)
236 }
237
238 jsonBug.Participants = make([]JSONIdentity, len(snapshot.Participants))
239 for i, element := range snapshot.Participants {
240 jsonBug.Participants[i] = NewJSONIdentity(element)
241 }
242
243 jsonBug.Comments = make([]JSONComment, len(snapshot.Comments))
244 for i, comment := range snapshot.Comments {
245 jsonBug.Comments[i] = NewJSONComment(comment)
246 }
247
248 jsonObject, _ := json.MarshalIndent(jsonBug, "", " ")
249 env.out.Printf("%s\n", jsonObject)
250
251 return nil
252}
253
254func showOrgModeFormatter(env *Env, snapshot *bug.Snapshot) error {
255 // Header
256 env.out.Printf("%s [%s] %s\n",
257 snapshot.Id().Human(),
258 snapshot.Status,
259 snapshot.Title,
260 )
261
262 env.out.Printf("* Author: %s\n",
263 snapshot.Author.DisplayName(),
264 )
265
266 env.out.Printf("* Creation Time: %s\n",
267 snapshot.CreateTime.String(),
268 )
269
270 env.out.Printf("* Last Edit: %s\n",
271 snapshot.EditTime().String(),
272 )
273
274 // Labels
275 var labels = make([]string, len(snapshot.Labels))
276 for i, label := range snapshot.Labels {
277 labels[i] = string(label)
278 }
279
280 env.out.Printf("* Labels:\n")
281 if len(labels) > 0 {
282 env.out.Printf("** %s\n",
283 strings.Join(labels, "\n** "),
284 )
285 }
286
287 // Actors
288 var actors = make([]string, len(snapshot.Actors))
289 for i, actor := range snapshot.Actors {
290 actors[i] = fmt.Sprintf("%s %s",
291 actor.Id().Human(),
292 actor.DisplayName(),
293 )
294 }
295
296 env.out.Printf("* Actors:\n** %s\n",
297 strings.Join(actors, "\n** "),
298 )
299
300 // Participants
301 var participants = make([]string, len(snapshot.Participants))
302 for i, participant := range snapshot.Participants {
303 participants[i] = fmt.Sprintf("%s %s",
304 participant.Id().Human(),
305 participant.DisplayName(),
306 )
307 }
308
309 env.out.Printf("* Participants:\n** %s\n",
310 strings.Join(participants, "\n** "),
311 )
312
313 env.out.Printf("* Comments:\n")
314
315 for i, comment := range snapshot.Comments {
316 var message string
317 env.out.Printf("** #%d %s\n",
318 i, comment.Author.DisplayName())
319
320 if comment.Message == "" {
321 message = "No description provided."
322 } else {
323 message = strings.ReplaceAll(comment.Message, "\n", "\n: ")
324 }
325
326 env.out.Printf(": %s\n", message)
327 }
328
329 return nil
330}