-
Notifications
You must be signed in to change notification settings - Fork 55
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: --format=json #7
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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,17 @@ func (c *cli) init() error { | |
c.tenant = c.data.DefaultTenant | ||
} | ||
|
||
format := strings.ToLower(c.format) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. json, JSON it should work for the user |
||
if format != "" && format != string(display.OutputFormatJSON) { | ||
err = fmt.Errorf("Invalid format. Use `--format=json` or omit this option to use the default format.") | ||
return | ||
} | ||
|
||
c.renderer = &display.Renderer{ | ||
Tenant: c.tenant, | ||
Writer: os.Stdout, | ||
Tenant: c.tenant, | ||
MessageWriter: os.Stderr, | ||
ResultWriter: os.Stdout, | ||
Comment on lines
+109
to
+110
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. intermediate messages use Stderr to keep the stdout clean for the final result (json or not), this way the output can be piped to another command (like |
||
Format: display.OutputFormat(format), | ||
} | ||
}) | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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), | ||
}) | ||
Comment on lines
+31
to
35
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What if for JSON we just dumped the whole object? let users do whatever filtering/processing/formatting they need with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I like the idea of having There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. imagine if this command includes everything available here 😅: https://docs.github.com/en/rest/reference/pulls#list-pull-request
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think for text mode it makes sense to curate what the user sees, but for JSON mode it's likely just being piped to something else and the full object probably makes more sense IMO. But in any case, absolutely not a blocker and can be discussed (bikeshedded 😆) later. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it is a pretty valid point, eventually we'll need to show more details. we can go back to this as we move on with the commands 🙇 |
||
|
||
} | ||
r.Table([]string{"Name", "Type", "ClientID"}, rows) | ||
|
||
r.Results(res) | ||
} | ||
|
||
// TODO(cyx): determine if there's a better way to filter this out. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,7 @@ | ||
package display | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"io" | ||
"os" | ||
|
@@ -13,48 +14,90 @@ import ( | |
"github.com/olekukonko/tablewriter" | ||
) | ||
|
||
type OutputFormat string | ||
|
||
const ( | ||
OutputFormatJSON OutputFormat = "json" | ||
) | ||
|
||
type Renderer struct { | ||
Tenant string | ||
|
||
Writer io.Writer | ||
// MessageWriter receives the renderer messages (typically os.Stderr) | ||
MessageWriter io.Writer | ||
|
||
// ResultWriter writes the final result of the commands (typically os.Stdout which can be piped to other commands) | ||
ResultWriter io.Writer | ||
|
||
// Format indicates how the results are rendered. Default (empty) will write as table | ||
Format OutputFormat | ||
|
||
initOnce sync.Once | ||
} | ||
|
||
func (r *Renderer) init() { | ||
r.initOnce.Do(func() { | ||
if r.Writer == nil { | ||
r.Writer = os.Stdout | ||
if r.MessageWriter == nil { | ||
r.MessageWriter = os.Stderr | ||
} | ||
if r.ResultWriter == nil { | ||
r.ResultWriter = os.Stdout | ||
} | ||
}) | ||
} | ||
|
||
func (r *Renderer) Infof(format string, a ...interface{}) { | ||
r.init() | ||
|
||
fmt.Fprint(r.Writer, aurora.Green(" ▸ ")) | ||
fmt.Fprintf(r.Writer, format+"\n", a...) | ||
fmt.Fprint(r.MessageWriter, aurora.Green(" ▸ ")) | ||
fmt.Fprintf(r.MessageWriter, format+"\n", a...) | ||
} | ||
|
||
func (r *Renderer) Warnf(format string, a ...interface{}) { | ||
r.init() | ||
|
||
fmt.Fprint(r.Writer, aurora.Yellow(" ▸ ")) | ||
fmt.Fprintf(r.Writer, format+"\n", a...) | ||
fmt.Fprint(r.MessageWriter, aurora.Yellow(" ▸ ")) | ||
fmt.Fprintf(r.MessageWriter, format+"\n", a...) | ||
} | ||
|
||
func (r *Renderer) Errorf(format string, a ...interface{}) { | ||
r.init() | ||
|
||
fmt.Fprint(r.Writer, aurora.BrightRed(" ▸ ")) | ||
fmt.Fprintf(r.Writer, format+"\n", a...) | ||
fmt.Fprint(r.MessageWriter, aurora.BrightRed(" ▸ ")) | ||
fmt.Fprintf(r.MessageWriter, format+"\n", a...) | ||
} | ||
|
||
func (r *Renderer) Heading(text ...string) { | ||
fmt.Fprintf(r.Writer, "%s %s\n", ansi.Faint("==="), strings.Join(text, " ")) | ||
fmt.Fprintf(r.MessageWriter, "%s %s\n", ansi.Faint("==="), strings.Join(text, " ")) | ||
} | ||
|
||
type View interface { | ||
AsTableHeader() []string | ||
AsTableRow() []string | ||
} | ||
Comment on lines
+74
to
+77
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think I partially agree with @paddycarey regarding full results eventually. For the purposes of this PR, let's keep it as it is. The only change I think we need to do in the future is maybe:
Either way great progress! |
||
|
||
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.ResultWriter, string(b)) | ||
|
||
default: | ||
rows := make([][]string, len(data)) | ||
for i, d := range data { | ||
rows[i] = d.AsTableRow() | ||
} | ||
writeTable(r.ResultWriter, data[0].AsTableHeader(), rows) | ||
} | ||
} | ||
} | ||
|
||
func (r *Renderer) Table(header []string, data [][]string) { | ||
func writeTable(w io.Writer, header []string, data [][]string) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. private now in favor of .Result(). The cli setup will know when to render as table, or json, YAML, etc |
||
tableString := &strings.Builder{} | ||
table := tablewriter.NewWriter(tableString) | ||
table.SetHeader(header) | ||
|
@@ -74,7 +117,7 @@ func (r *Renderer) Table(header []string, data [][]string) { | |
} | ||
|
||
table.Render() | ||
fmt.Fprint(r.Writer, tableString.String()) | ||
fmt.Fprint(w, tableString.String()) | ||
} | ||
|
||
func timeAgo(ts time.Time) string { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd like to use
display.OutputFormat
here directly (instead of string) but I didn't know how to make it work case insensitive