From 140a54a009b27c41fe7d602b5fbada129fcacd4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mislav=20Marohni=C4=87?= Date: Wed, 4 Aug 2021 15:56:10 +0200 Subject: [PATCH] Add machine-readable output formats - Default table output (when stdout is attached to a terminal) stays the same; - When stdout is redirected, output tab-separated values and no header line; - With `--json` flag, output structured JSON data. Example: $ ghcs list --json [ { "Branch": "main", "Created At": "2021-06-10T15:04:46+02:00", "Name": "mislav-playground-jvqj", "Repository": "mislav/playground", "State": "Shutdown" }, { "Branch": "master", "Created At": "2021-07-15T15:51:08+02:00", "Name": "mislav-github-github-pwgg365xv", "Repository": "github/github", "State": "Shutdown" } ] --- cmd/ghcs/delete.go | 6 +++--- cmd/ghcs/list.go | 17 ++++++++++++----- cmd/ghcs/output/format_json.go | 33 +++++++++++++++++++++++++++++++++ cmd/ghcs/output/format_table.go | 28 ++++++++++++++++++++++++++++ cmd/ghcs/output/format_tsv.go | 25 +++++++++++++++++++++++++ 5 files changed, 101 insertions(+), 8 deletions(-) create mode 100644 cmd/ghcs/output/format_json.go create mode 100644 cmd/ghcs/output/format_table.go create mode 100644 cmd/ghcs/output/format_tsv.go diff --git a/cmd/ghcs/delete.go b/cmd/ghcs/delete.go index 2694e9cdf..5cec0f80b 100644 --- a/cmd/ghcs/delete.go +++ b/cmd/ghcs/delete.go @@ -73,7 +73,7 @@ func Delete(codespaceName string) error { fmt.Println("Codespace deleted.") - return List() + return List(&ListOptions{}) } func DeleteAll() error { @@ -103,7 +103,7 @@ func DeleteAll() error { fmt.Printf("Codespace deleted: %s\n", c.Name) } - return List() + return List(&ListOptions{}) } func DeleteByRepo(repo string) error { @@ -143,5 +143,5 @@ func DeleteByRepo(repo string) error { fmt.Printf("No codespace was found for repository: %s\n", repo) } - return List() + return List(&ListOptions{}) } diff --git a/cmd/ghcs/list.go b/cmd/ghcs/list.go index 6db79af97..bbee2aae8 100644 --- a/cmd/ghcs/list.go +++ b/cmd/ghcs/list.go @@ -5,21 +5,28 @@ import ( "fmt" "os" - "github.com/olekukonko/tablewriter" - "github.com/github/ghcs/api" + "github.com/github/ghcs/cmd/ghcs/output" "github.com/spf13/cobra" ) +type ListOptions struct { + AsJSON bool +} + func NewListCmd() *cobra.Command { + opts := &ListOptions{} + listCmd := &cobra.Command{ Use: "list", Short: "List GitHub Codespaces you have on your account.", RunE: func(cmd *cobra.Command, args []string) error { - return List() + return List(opts) }, } + listCmd.Flags().BoolVar(&opts.AsJSON, "json", false, "Output as JSON") + return listCmd } @@ -27,7 +34,7 @@ func init() { rootCmd.AddCommand(NewListCmd()) } -func List() error { +func List(opts *ListOptions) error { apiClient := api.New(os.Getenv("GITHUB_TOKEN")) ctx := context.Background() @@ -46,7 +53,7 @@ func List() error { return nil } - table := tablewriter.NewWriter(os.Stdout) + table := output.NewTable(os.Stdout, opts.AsJSON) table.SetHeader([]string{"Name", "Repository", "Branch", "State", "Created At"}) for _, codespace := range codespaces { table.Append([]string{ diff --git a/cmd/ghcs/output/format_json.go b/cmd/ghcs/output/format_json.go new file mode 100644 index 000000000..37208629c --- /dev/null +++ b/cmd/ghcs/output/format_json.go @@ -0,0 +1,33 @@ +package output + +import ( + "encoding/json" + "io" +) + +type jsonwriter struct { + w io.Writer + pretty bool + cols []string + data []interface{} +} + +func (j *jsonwriter) SetHeader(cols []string) { + j.cols = cols +} + +func (j *jsonwriter) Append(values []string) { + row := make(map[string]string) + for i, v := range values { + row[j.cols[i]] = v + } + j.data = append(j.data, row) +} + +func (j *jsonwriter) Render() { + enc := json.NewEncoder(j.w) + if j.pretty { + enc.SetIndent("", " ") + } + _ = enc.Encode(j.data) +} diff --git a/cmd/ghcs/output/format_table.go b/cmd/ghcs/output/format_table.go new file mode 100644 index 000000000..97e7cab58 --- /dev/null +++ b/cmd/ghcs/output/format_table.go @@ -0,0 +1,28 @@ +package output + +import ( + "io" + "os" + + "github.com/olekukonko/tablewriter" + "golang.org/x/term" +) + +type Table interface { + SetHeader([]string) + Append([]string) + Render() +} + +func NewTable(w io.Writer, asJSON bool) Table { + f, ok := w.(*os.File) + isTTY := ok && term.IsTerminal(int(f.Fd())) + + if asJSON { + return &jsonwriter{w: w, pretty: isTTY} + } + if isTTY { + return tablewriter.NewWriter(w) + } + return &tabwriter{w: w} +} diff --git a/cmd/ghcs/output/format_tsv.go b/cmd/ghcs/output/format_tsv.go new file mode 100644 index 000000000..3f1d226ca --- /dev/null +++ b/cmd/ghcs/output/format_tsv.go @@ -0,0 +1,25 @@ +package output + +import ( + "fmt" + "io" +) + +type tabwriter struct { + w io.Writer +} + +func (j *tabwriter) SetHeader([]string) {} + +func (j *tabwriter) Append(values []string) { + var sep string + for i, v := range values { + if i == 1 { + sep = "\t" + } + fmt.Fprintf(j.w, "%s%s", sep, v) + } + fmt.Fprint(j.w, "\n") +} + +func (j *tabwriter) Render() {}