Add output formatting support to the 'show' and 'user ls' commands

vince created

This adds options to specify an output format for the commands in question. Supported formats are currently:
- 'plain': plaintext, stripped of all colors
- 'json': prints output as a json object

Change summary

commands/ls.go      |  15 --
commands/show.go    | 209 +++++++++++++++++++++++++++++++++++++++++++---
commands/user_ls.go |  67 ++++++++++++++
3 files changed, 264 insertions(+), 27 deletions(-)

Detailed changes

commands/ls.go 🔗

@@ -72,7 +72,7 @@ func runLsBug(_ *cobra.Command, args []string) error {
 	}
 }
 
-type JSONBug struct {
+type JSONBugExcerpt struct {
 	Id           string    `json:"id"`
 	HumanId      string    `json:"human_id"`
 	CreationTime time.Time `json:"creation_time"`
@@ -89,17 +89,10 @@ type JSONBug struct {
 	Metadata map[string]string `json:"metadata"`
 }
 
-type JSONIdentity struct {
-	Id      string `json:"id"`
-	HumanId string `json:"human_id"`
-	Name    string `json:"name"`
-	Login   string `json:"login"`
-}
-
 func lsJsonFormatter(backend *cache.RepoCache, bugExcerpts []*cache.BugExcerpt) error {
-	jsonBugs := make([]JSONBug, len(bugExcerpts))
+	jsonBugs := make([]JSONBugExcerpt, len(bugExcerpts))
 	for i, b := range bugExcerpts {
-		jsonBug := JSONBug{
+		jsonBug := JSONBugExcerpt{
 			b.Id.String(),
 			b.Id.Human(),
 			time.Unix(b.CreateUnixTime, 0),
@@ -294,5 +287,5 @@ func init() {
 	lsCmd.Flags().StringVarP(&lsSortDirection, "direction", "d", "asc",
 		"Select the sorting direction. Valid values are [asc,desc]")
 	lsCmd.Flags().StringVarP(&lsOutputFormat, "format", "f", "default",
-		"Select the output formatting style. Valid values are [default, plain(text), json]")
+		"Select the output formatting style. Valid values are [default,plain,json]")
 }

commands/show.go 🔗

@@ -1,22 +1,25 @@
 package commands
 
 import (
+	"encoding/json"
 	"errors"
 	"fmt"
-	"strings"
-
+	"github.com/MichaelMure/git-bug/bug"
 	"github.com/MichaelMure/git-bug/cache"
 	_select "github.com/MichaelMure/git-bug/commands/select"
 	"github.com/MichaelMure/git-bug/util/colors"
 	"github.com/MichaelMure/git-bug/util/interrupt"
 	"github.com/spf13/cobra"
+	"strings"
+	"time"
 )
 
 var (
-	showFieldsQuery string
+	showFieldsQuery  string
+	showOutputFormat string
 )
 
-func runShowBug(cmd *cobra.Command, args []string) error {
+func runShowBug(_ *cobra.Command, args []string) error {
 	backend, err := cache.NewRepoCache(repo)
 	if err != nil {
 		return err
@@ -35,16 +38,16 @@ func runShowBug(cmd *cobra.Command, args []string) error {
 		return errors.New("invalid bug: no comment")
 	}
 
-	firstComment := snapshot.Comments[0]
-
 	if showFieldsQuery != "" {
 		switch showFieldsQuery {
 		case "author":
-			fmt.Printf("%s\n", firstComment.Author.DisplayName())
+			fmt.Printf("%s\n", snapshot.Author.DisplayName())
 		case "authorEmail":
-			fmt.Printf("%s\n", firstComment.Author.Email())
+			fmt.Printf("%s\n", snapshot.Author.Email())
 		case "createTime":
-			fmt.Printf("%s\n", firstComment.FormatTime())
+			fmt.Printf("%s\n", snapshot.CreatedAt.String())
+		case "lastEdit":
+			fmt.Printf("%s\n", snapshot.LastEditTime().String())
 		case "humanId":
 			fmt.Printf("%s\n", snapshot.Id().Human())
 		case "id":
@@ -74,6 +77,19 @@ func runShowBug(cmd *cobra.Command, args []string) error {
 		return nil
 	}
 
+	switch showOutputFormat {
+	case "json":
+		return showJsonFormatter(snapshot)
+	case "plain":
+		return showPlainFormatter(snapshot)
+	case "default":
+		return showDefaultFormatter(snapshot)
+	default:
+		return fmt.Errorf("unknown format %s", showOutputFormat)
+	}
+}
+
+func showDefaultFormatter(snapshot *bug.Snapshot) error {
 	// Header
 	fmt.Printf("[%s] %s %s\n\n",
 		colors.Yellow(snapshot.Status),
@@ -81,9 +97,13 @@ func runShowBug(cmd *cobra.Command, args []string) error {
 		snapshot.Title,
 	)
 
-	fmt.Printf("%s opened this issue %s\n\n",
-		colors.Magenta(firstComment.Author.DisplayName()),
-		firstComment.FormatTimeRel(),
+	fmt.Printf("%s opened this issue %s\n",
+		colors.Magenta(snapshot.Author.DisplayName()),
+		snapshot.CreatedAt.String(),
+	)
+
+	fmt.Printf("This was last edited at %s\n\n",
+		snapshot.LastEditTime().String(),
 	)
 
 	// Labels
@@ -143,6 +163,165 @@ func runShowBug(cmd *cobra.Command, args []string) error {
 	return nil
 }
 
+func showPlainFormatter(snapshot *bug.Snapshot) error {
+	// Header
+	fmt.Printf("[%s] %s %s\n",
+		snapshot.Status,
+		snapshot.Id().Human(),
+		snapshot.Title,
+	)
+
+	fmt.Printf("author: %s\n",
+		snapshot.Author.DisplayName(),
+	)
+
+	fmt.Printf("creation time: %s\n",
+		snapshot.CreatedAt.String(),
+	)
+
+	fmt.Printf("last edit: %s\n",
+		snapshot.LastEditTime().String(),
+	)
+
+	// Labels
+	var labels = make([]string, len(snapshot.Labels))
+	for i := range snapshot.Labels {
+		labels[i] = string(snapshot.Labels[i])
+	}
+
+	fmt.Printf("labels: %s\n",
+		strings.Join(labels, ", "),
+	)
+
+	// Actors
+	var actors = make([]string, len(snapshot.Actors))
+	for i := range snapshot.Actors {
+		actors[i] = snapshot.Actors[i].DisplayName()
+	}
+
+	fmt.Printf("actors: %s\n",
+		strings.Join(actors, ", "),
+	)
+
+	// Participants
+	var participants = make([]string, len(snapshot.Participants))
+	for i := range snapshot.Participants {
+		participants[i] = snapshot.Participants[i].DisplayName()
+	}
+
+	fmt.Printf("participants: %s\n",
+		strings.Join(participants, ", "),
+	)
+
+	// Comments
+	indent := "  "
+
+	for i, comment := range snapshot.Comments {
+		var message string
+		fmt.Printf("%s#%d %s <%s>\n",
+			indent,
+			i,
+			comment.Author.DisplayName(),
+			comment.Author.Email(),
+		)
+
+		if comment.Message == "" {
+			message = "No description provided."
+		} else {
+			message = comment.Message
+		}
+
+		fmt.Printf("%s%s\n",
+			indent,
+			message,
+		)
+	}
+
+	return nil
+}
+
+type JSONBugSnapshot struct {
+	Id           string    `json:"id"`
+	HumanId      string    `json:"human_id"`
+	CreationTime time.Time `json:"creation_time"`
+	LastEdited   time.Time `json:"last_edited"`
+
+	Status       string         `json:"status"`
+	Labels       []bug.Label    `json:"labels"`
+	Title        string         `json:"title"`
+	Author       JSONIdentity   `json:"author"`
+	Actors       []JSONIdentity `json:"actors"`
+	Participants []JSONIdentity `json:"participants"`
+
+	Comments []JSONComment `json:"comments"`
+}
+
+type JSONComment struct {
+	Id          int    `json:"id"`
+	AuthorName  string `json:"author_name"`
+	AuthorLogin string `json:"author_login"`
+	Message     string `json:"message"`
+}
+
+func showJsonFormatter(snapshot *bug.Snapshot) error {
+	jsonBug := JSONBugSnapshot{
+		snapshot.Id().String(),
+		snapshot.Id().Human(),
+		snapshot.CreatedAt,
+		snapshot.LastEditTime(),
+		snapshot.Status.String(),
+		snapshot.Labels,
+		snapshot.Title,
+		JSONIdentity{},
+		[]JSONIdentity{},
+		[]JSONIdentity{},
+		[]JSONComment{},
+	}
+
+	jsonBug.Author.Name = snapshot.Author.DisplayName()
+	jsonBug.Author.Login = snapshot.Author.Login()
+	jsonBug.Author.Id = snapshot.Author.Id().String()
+	jsonBug.Author.HumanId = snapshot.Author.Id().Human()
+
+	for _, element := range snapshot.Actors {
+		jsonBug.Actors = append(jsonBug.Actors, JSONIdentity{
+			element.Id().String(),
+			element.Id().Human(),
+			element.Name(),
+			element.Login(),
+		})
+	}
+
+	for _, element := range snapshot.Participants {
+		jsonBug.Actors = append(jsonBug.Actors, JSONIdentity{
+			element.Id().String(),
+			element.Id().Human(),
+			element.Name(),
+			element.Login(),
+		})
+	}
+
+	for i, comment := range snapshot.Comments {
+		var message string
+		if comment.Message == "" {
+			message = "No description provided."
+		} else {
+			message = comment.Message
+		}
+		jsonBug.Comments = append(jsonBug.Comments, JSONComment{
+			i,
+			comment.Author.Name(),
+			comment.Author.Login(),
+			message,
+		})
+	}
+
+	jsonObject, _ := json.MarshalIndent(jsonBug, "", "    ")
+	fmt.Printf("%s\n", jsonObject)
+
+	return nil
+}
+
 var showCmd = &cobra.Command{
 	Use:     "show [<id>]",
 	Short:   "Display the details of a bug.",
@@ -152,6 +331,8 @@ var showCmd = &cobra.Command{
 
 func init() {
 	RootCmd.AddCommand(showCmd)
-	showCmd.Flags().StringVarP(&showFieldsQuery, "field", "f", "",
-		"Select field to display. Valid values are [author,authorEmail,createTime,humanId,id,labels,shortId,status,title,actors,participants]")
+	showCmd.Flags().StringVarP(&showFieldsQuery, "field", "", "",
+		"Select field to display. Valid values are [author,authorEmail,createTime,lastEdit,humanId,id,labels,shortId,status,title,actors,participants]")
+	showCmd.Flags().StringVarP(&showOutputFormat, "format", "f", "default",
+		"Select the output formatting style. Valid values are [default,plain,json]")
 }

commands/user_ls.go 🔗

@@ -1,15 +1,19 @@
 package commands
 
 import (
+	"encoding/json"
 	"fmt"
-
 	"github.com/MichaelMure/git-bug/cache"
 	"github.com/MichaelMure/git-bug/util/colors"
 	"github.com/MichaelMure/git-bug/util/interrupt"
 	"github.com/spf13/cobra"
 )
 
-func runUserLs(cmd *cobra.Command, args []string) error {
+var (
+	userLsOutputFormat string
+)
+
+func runUserLs(_ *cobra.Command, _ []string) error {
 	backend, err := cache.NewRepoCache(repo)
 	if err != nil {
 		return err
@@ -17,6 +21,42 @@ func runUserLs(cmd *cobra.Command, args []string) error {
 	defer backend.Close()
 	interrupt.RegisterCleaner(backend.Close)
 
+	switch userLsOutputFormat {
+	case "json":
+		return userLsJsonFormatter(backend)
+	case "plain":
+		return userLsPlainFormatter(backend)
+	case "default":
+		return userLsDefaultFormatter(backend)
+	default:
+		return fmt.Errorf("unknown format %s", userLsOutputFormat)
+	}
+}
+
+type JSONIdentity struct {
+	Id      string `json:"id"`
+	HumanId string `json:"human_id"`
+	Name    string `json:"name"`
+	Login   string `json:"login"`
+}
+
+func userLsPlainFormatter(backend *cache.RepoCache) error {
+	for _, id := range backend.AllIdentityIds() {
+		i, err := backend.ResolveIdentityExcerpt(id)
+		if err != nil {
+			return err
+		}
+
+		fmt.Printf("%s %s\n",
+			i.Id.Human(),
+			i.DisplayName(),
+		)
+	}
+
+	return nil
+}
+
+func userLsDefaultFormatter(backend *cache.RepoCache) error {
 	for _, id := range backend.AllIdentityIds() {
 		i, err := backend.ResolveIdentityExcerpt(id)
 		if err != nil {
@@ -32,6 +72,27 @@ func runUserLs(cmd *cobra.Command, args []string) error {
 	return nil
 }
 
+func userLsJsonFormatter(backend *cache.RepoCache) error {
+	users := []JSONIdentity{}
+	for _, id := range backend.AllIdentityIds() {
+		i, err := backend.ResolveIdentityExcerpt(id)
+		if err != nil {
+			return err
+		}
+
+		users = append(users, JSONIdentity{
+			i.Id.String(),
+			i.Id.Human(),
+			i.Name,
+			i.Login,
+		})
+	}
+
+	jsonObject, _ := json.MarshalIndent(users, "", "    ")
+	fmt.Printf("%s\n", jsonObject)
+	return nil
+}
+
 var userLsCmd = &cobra.Command{
 	Use:     "ls",
 	Short:   "List identities.",
@@ -42,4 +103,6 @@ var userLsCmd = &cobra.Command{
 func init() {
 	userCmd.AddCommand(userLsCmd)
 	userLsCmd.Flags().SortFlags = false
+	userLsCmd.Flags().StringVarP(&userLsOutputFormat, "format", "f", "default",
+		"Select the output formatting style. Valid values are [default,plain,json]")
 }