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