diff --git a/internal/cli/cli.go b/internal/cli/cli.go index 63a81b0a2..338183a9a 100644 --- a/internal/cli/cli.go +++ b/internal/cli/cli.go @@ -6,6 +6,7 @@ import ( "io/ioutil" "os" "path" + "strings" "sync" "github.com/auth0/auth0-cli/internal/display" @@ -33,6 +34,7 @@ type cli struct { verbose bool tenant string + format string initOnce sync.Once path string @@ -96,9 +98,16 @@ func (c *cli) init() error { c.tenant = c.data.DefaultTenant } + format := strings.ToLower(c.format) + if format != "" && format != string(display.OutputFormatJSON) { + err = fmt.Errorf("Invalid format. Use `--format=json` or ommit this option to use the default format.") + return + } + c.renderer = &display.Renderer{ Tenant: c.tenant, Writer: os.Stdout, + Format: display.OutputFormat(format), } }) diff --git a/internal/cli/root.go b/internal/cli/root.go index 62496f639..359639a9e 100644 --- a/internal/cli/root.go +++ b/internal/cli/root.go @@ -39,11 +39,14 @@ func Execute() { rootCmd.SetUsageTemplate(namespaceUsageTemplate()) rootCmd.PersistentFlags().StringVar(&cli.tenant, - "tenant", "", "Specific tenant to use") + "tenant", "", "Specific tenant to use.") rootCmd.PersistentFlags().BoolVar(&cli.verbose, "verbose", false, "Enable verbose mode.") + rootCmd.PersistentFlags().StringVar(&cli.format, + "format", "", "Command output format. Options: json.") + rootCmd.AddCommand(clientsCmd(cli)) rootCmd.AddCommand(logsCmd(cli)) diff --git a/internal/display/clients.go b/internal/display/clients.go index 35f068f81..58ef7fccf 100644 --- a/internal/display/clients.go +++ b/internal/display/clients.go @@ -6,21 +6,37 @@ import ( "gopkg.in/auth0.v5/management" ) +type clientView struct { + Name string + Type string + ClientID string +} + +func (v *clientView) AsTableHeader() []string { + return []string{"Name", "Type", "ClientID"} +} + +func (v *clientView) AsTableRow() []string { + return []string{v.Name, v.Type, ansi.Faint(v.ClientID)} +} + func (r *Renderer) ClientList(clients []*management.Client) { r.Heading(ansi.Bold(r.Tenant), "clients\n") - var rows [][]string + var res []View for _, c := range clients { if auth0.StringValue(c.Name) == deprecatedAppName { continue } - rows = append(rows, []string{ - auth0.StringValue(c.Name), - appTypeFor(c.AppType), - ansi.Faint(auth0.StringValue(c.ClientID)), + res = append(res, &clientView{ + Name: auth0.StringValue(c.Name), + Type: appTypeFor(c.AppType), + ClientID: auth0.StringValue(c.ClientID), }) + } - r.Table([]string{"Name", "Type", "ClientID"}, rows) + + r.Results(res) } // TODO(cyx): determine if there's a better way to filter this out. diff --git a/internal/display/display.go b/internal/display/display.go index f1eb84a13..d694932b3 100644 --- a/internal/display/display.go +++ b/internal/display/display.go @@ -1,6 +1,7 @@ package display import ( + "encoding/json" "fmt" "io" "os" @@ -13,11 +14,19 @@ import ( "github.com/olekukonko/tablewriter" ) +type OutputFormat string + +const ( + OutputFormatJSON OutputFormat = "json" +) + type Renderer struct { Tenant string Writer io.Writer + Format OutputFormat + initOnce sync.Once } @@ -54,7 +63,33 @@ func (r *Renderer) Heading(text ...string) { fmt.Fprintf(r.Writer, "%s %s\n", ansi.Faint("==="), strings.Join(text, " ")) } -func (r *Renderer) Table(header []string, data [][]string) { +type View interface { + AsTableHeader() []string + AsTableRow() []string +} + +func (r *Renderer) Results(data []View) { + if len(data) > 0 { + switch r.Format { + case OutputFormatJSON: + b, err := json.MarshalIndent(data, "", " ") + if err != nil { + r.Errorf("couldn't marshal results as JSON: %v", err) + return + } + fmt.Fprint(r.Writer, string(b)) + + default: + rows := make([][]string, len(data)) + for i, d := range data { + rows[i] = d.AsTableRow() + } + r.table(data[0].AsTableHeader(), rows) + } + } +} + +func (r *Renderer) table(header []string, data [][]string) { tableString := &strings.Builder{} table := tablewriter.NewWriter(tableString) table.SetHeader(header)