Skip to content

Commit

Permalink
Merge pull request #8 from sullivtr/main
Browse files Browse the repository at this point in the history
output wrapper lib
  • Loading branch information
scotwells authored Jan 2, 2025
2 parents 8f93b96 + 52ce928 commit 2a367b9
Show file tree
Hide file tree
Showing 3 changed files with 164 additions and 30 deletions.
39 changes: 9 additions & 30 deletions internal/cmd/organizations/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,13 @@ package organizations

import (
"fmt"
"os"

resourcemanagerv1alpha "buf.build/gen/go/datum-cloud/datum-os/protocolbuffers/go/datum/os/resourcemanager/v1alpha"
"buf.build/go/protoyaml"
"github.com/rodaine/table"
"github.com/spf13/cobra"
"google.golang.org/protobuf/encoding/protojson"

"go.datum.net/datumctl/internal/keyring"
"go.datum.net/datumctl/internal/output"
"go.datum.net/datumctl/internal/resourcemanager"
)

Expand All @@ -35,37 +34,17 @@ func listOrgsCommand() *cobra.Command {
return fmt.Errorf("failed to list organizations: %w", err)
}

// TODO: We should look at abstracting the formatting here into a library
// that can be used by multiple commands needing to offer multiple
// output formats from a command.
switch outputFormat {
case "yaml":
marshaller := &protoyaml.MarshalOptions{
Indent: 2,
}
output, err := marshaller.Marshal(listOrgs)
if err != nil {
return fmt.Errorf("failed to list organizations: %w", err)
}
fmt.Print(string(output))
case "json":
output, err := protojson.Marshal(listOrgs)
if err != nil {
return fmt.Errorf("failed to list organizations: %w", err)
}
fmt.Print(string(output))
case "table":
orgTable := table.New("DISPLAY NAME", "RESOURCE ID")
if len(listOrgs.Organizations) == 0 {
fmt.Printf("No organizations found")
} else {
if err := output.CLIPrint(os.Stdout, outputFormat, listOrgs, func() (output.ColumnFormatter, output.RowFormatterFunc) {
return output.ColumnFormatter{"DISPLAY NAME", "RESOURCE ID"}, func() output.RowFormatter {
var rowData output.RowFormatter
for _, org := range listOrgs.Organizations {
orgTable.AddRow(org.DisplayName, org.OrganizationId)
rowData = append(rowData, []any{org.DisplayName, org.OrganizationId})
}
return rowData
}
orgTable.Print()
}); err != nil {
return fmt.Errorf("a problem occured while printing organizations list: %w", err)
}

return nil
},
}
Expand Down
65 changes: 65 additions & 0 deletions internal/output/output.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package output

import (
"fmt"
"io"

"buf.build/go/protoyaml"
"github.com/rodaine/table"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/proto"
)

type TableFormatterFunc func() (ColumnFormatter, RowFormatterFunc)
type RowFormatterFunc func() RowFormatter
type RowFormatter [][]any
type ColumnFormatter []any

func CLIPrint(w io.Writer, format string, data proto.Message, tableFormatterFunc TableFormatterFunc) error {
switch format {
case "yaml":
return printYAML(w, data)
case "json":
return printJSON(w, data)
case "table":
headers, rowDataFunc := tableFormatterFunc()
if headers == nil {
return fmt.Errorf("headers must be provided for table output")
}
return printTable(w, headers, rowDataFunc())
default:
return fmt.Errorf("unsupported format: %s", format)
}
}

func printYAML(w io.Writer, data proto.Message) error {
marshaller := protoyaml.MarshalOptions{
Indent: 2,
}

output, err := marshaller.Marshal(data)
if err != nil {
return fmt.Errorf("failed to marshal data to YAML: %w", err)
}
fmt.Fprint(w, string(output))
return nil
}

func printJSON(w io.Writer, data proto.Message) error {
output, err := protojson.Marshal(data)
if err != nil {
return fmt.Errorf("failed to marshal data to JSON: %w", err)
}
fmt.Fprint(w, string(output))
return nil
}

func printTable(w io.Writer, headers ColumnFormatter, rowData [][]any) error {
t := table.New(headers...)
t.WithWriter(w)
for _, row := range rowData {
t.AddRow(row)
}
t.Print()
return nil
}
90 changes: 90 additions & 0 deletions internal/output/output_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package output

import (
"bytes"
"strings"
"testing"

resourcemanagerv1alpha "buf.build/gen/go/datum-cloud/datum-os/protocolbuffers/go/datum/os/resourcemanager/v1alpha"
"google.golang.org/protobuf/proto"
)

func TestCLIPrint(t *testing.T) {

testOrgProto := &resourcemanagerv1alpha.Organization{
DisplayName: "Test Organization",
OrganizationId: "1234",
}

tests := []struct {
name string
format string
data proto.Message
headers []any
rowData [][]any
wantErr bool
wantOutput string
}{
{
name: "Print YAML",
format: "yaml",
data: testOrgProto,
wantErr: false,
wantOutput: "organizationId: \"1234\"\ndisplayName: Test Organization\n",
},
{
name: "Print JSON",
format: "json",
data: testOrgProto,
wantErr: false,
wantOutput: "{\"organizationId\":\"1234\",\"displayName\":\"Test Organization\"}",
},
{
name: "Print Table",
format: "table",
headers: []any{"Header1", "Header2"},
rowData: [][]any{{"Row1Col1", "Row1Col2"}, {"Row2Col1", "Row2Col2"}},
wantErr: false,
},
{
name: "Unsupported Format",
format: "unsupported",
data: testOrgProto,
wantErr: true,
},
{
name: "Table without headers and rowData",
format: "table",
wantErr: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var buf bytes.Buffer

err := CLIPrint(&buf, tt.format, tt.data, func() (ColumnFormatter, RowFormatterFunc) {
return tt.headers, func() RowFormatter {
return tt.rowData
}
})
if (err != nil) != tt.wantErr {
t.Errorf("CLIPrint() error = %v, wantErr %v", err, tt.wantErr)
return
}

if !tt.wantErr {
if tt.format == "table" {
out := buf.String()
if !strings.Contains(out, tt.headers[0].(string)) || !strings.Contains(out, tt.headers[1].(string)) {
t.Errorf("CLIPrint() output = %v, does not have correct headers", out)
}
} else {
if gotOutput := buf.String(); gotOutput != tt.wantOutput {
t.Errorf("CLIPrint() output = \n%v, want \n%v", gotOutput, tt.wantOutput)
}
}
}
})
}
}

0 comments on commit 2a367b9

Please sign in to comment.