Skip to content
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

Replace CSV printer with more generic approach. #131

Merged
merged 1 commit into from
Mar 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ require (
github.com/google/go-cmp v0.6.0
github.com/google/uuid v1.6.0
github.com/icza/dyno v0.0.0-20230330125955-09f820a8d9c0
github.com/jszwec/csvutil v1.10.0
github.com/mattn/go-isatty v0.0.20
github.com/meilisearch/meilisearch-go v0.26.1
github.com/metal-stack/security v0.7.2
Expand Down
2 changes: 0 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -225,8 +225,6 @@ github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86/go.mod h1:aFAMt
github.com/jsimonetti/rtnetlink v1.4.1 h1:JfD4jthWBqZMEffc5RjgmlzpYttAVw1sdnmiNaPO3hE=
github.com/jsimonetti/rtnetlink v1.4.1/go.mod h1:xJjT7t59UIZ62GLZbv6PLLo8VFrostJMPBAheR6OM8w=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jszwec/csvutil v1.10.0 h1:upMDUxhQKqZ5ZDCs/wy+8Kib8rZR8I8lOR34yJkdqhI=
github.com/jszwec/csvutil v1.10.0/go.mod h1:/E4ONrmGkwmWsk9ae9jpXnv9QT8pLHEPcCirMFhxG9I=
github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
Expand Down
49 changes: 25 additions & 24 deletions pkg/genericcli/printers/csv.go
Original file line number Diff line number Diff line change
@@ -1,38 +1,36 @@
package printers

import (
"encoding/csv"
"fmt"
"io"
"os"

"github.com/jszwec/csvutil"
"strings"
)

const defaultDelimiter = ';'

type CSVPrinter struct {
c *CSVPrinterConfig
out io.Writer
c *CSVPrinterConfig
}

type CSVPrinterConfig struct {
// AutoHeader will generate headers during print, default is go standard ("false")
AutoHeader bool
// Delimiter the char to separate the columns, default is ";"
Delimiter rune
// ToHeaderAndRows is called during print to obtain the headers and rows for the given data.
ToHeaderAndRows func(data any) ([]string, [][]string, error)
// NoHeaders will omit headers during pring when set to true
NoHeaders bool
// Out defines the output writer for the printer, will default to os.stdout
Out io.Writer
// Tag sets the struct field tag used for printing (default: json)
Tag string
// Delimiter the char to separate the columns, default is ";"
Delimiter rune
}

func NewCSVPrinter(config *CSVPrinterConfig) *CSVPrinter {
if config.Out == nil {
config.Out = os.Stdout
if config == nil {
config = &CSVPrinterConfig{}
}

if config.Tag == "" {
config.Tag = "json"
if config.Out == nil {
config.Out = os.Stdout
}

if config.Delimiter == 0 {
Expand All @@ -45,25 +43,28 @@ func NewCSVPrinter(config *CSVPrinterConfig) *CSVPrinter {
}

func (cp *CSVPrinter) WithOut(out io.Writer) *CSVPrinter {
cp.out = out
cp.c.Out = out

return cp
}

func (cp *CSVPrinter) Print(data any) error {
w := csv.NewWriter(cp.out)
w.Comma = cp.c.Delimiter

enc := csvutil.NewEncoder(w)
enc.AutoHeader = cp.c.AutoHeader
enc.Tag = cp.c.Tag
if cp.c.ToHeaderAndRows == nil {
return fmt.Errorf("missing to header and rows function in printer configuration")
}

err := enc.Encode(data)
headers, rows, err := cp.c.ToHeaderAndRows(data)
if err != nil {
return err
}

w.Flush()
if !cp.c.NoHeaders {
fmt.Fprintln(cp.c.Out, strings.Join(headers, string(cp.c.Delimiter)))
}

for _, row := range rows {
fmt.Fprintln(cp.c.Out, strings.Join(row, string(cp.c.Delimiter)))
}

return nil
}
171 changes: 66 additions & 105 deletions pkg/genericcli/printers/csv_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,135 +2,96 @@ package printers_test

import (
"bytes"
"reflect"
"testing"

"github.com/google/go-cmp/cmp"
"github.com/metal-stack/metal-lib/pkg/genericcli/printers"
)

type CSVReport struct {
FirstName string `json:"first_name,omitempty"`
LastName string `json:"last_name,omitempty"`
Username string `json:"username,omitempty"`
}

var (
content = []CSVReport{
{
FirstName: "John",
LastName: "Deere",
Username: "jd",
},
{
FirstName: "New",
LastName: "Holland",
Username: "nh",
func TestBasicCSVPrinter(t *testing.T) {
buffer := new(bytes.Buffer)
printer := printers.NewCSVPrinter(&printers.CSVPrinterConfig{
Out: buffer,
ToHeaderAndRows: func(data any) ([]string, [][]string, error) {
if data != "test" {
t.Errorf("want data test, got %s", data)
}
return []string{"a", "b"}, [][]string{
{"1", "2"},
{"3", "4"},
}, nil
},
}
expectationWithHeader = `first_name;last_name;username
John;Deere;jd
New;Holland;nh
`
expectationWithOutHeader = `John;Deere;jd
New;Holland;nh
`
delimiter = ';' // rune(59) ≙ ';'

)

func TestCSVWithHeader(t *testing.T) {
t.Parallel()

out := new(bytes.Buffer)
config := &printers.CSVPrinterConfig{
Delimiter: delimiter,
AutoHeader: true,
}
printer := printers.NewCSVPrinter(config).WithOut(out)
})

err := printer.Print(content)
err := printer.Print("test")
if err != nil {
t.Error(err)
}

got := out.String()
want := expectationWithHeader

if !reflect.DeepEqual(got, want) {
diff := cmp.Diff(want, got)
t.Errorf("got %v, want %v, diff %s", got, want, diff)
}
}

func TestCSVWithHeaderNoDelimiter(t *testing.T) {
t.Parallel()

out := new(bytes.Buffer)
config := &printers.CSVPrinterConfig{
AutoHeader: true,
}
printer := printers.NewCSVPrinter(config).WithOut(out)

err := printer.Print(content)
if err != nil {
t.Error(err)
}

got := out.String()
want := expectationWithHeader

if !reflect.DeepEqual(got, want) {
diff := cmp.Diff(want, got)
t.Errorf("got %v, want %v, diff %s", got, want, diff)
got := buffer.String()
want := `a;b
1;2
3;4
`
if diff := cmp.Diff(want, got); diff != "" {
t.Errorf("diff (+got -want):\n %s", diff)
}
}

func TestCSVNoHeader(t *testing.T) {
t.Parallel()

out := new(bytes.Buffer)

config := &printers.CSVPrinterConfig{
AutoHeader: false,
Delimiter: delimiter,
}
printer := printers.NewCSVPrinter(config).WithOut(out)
func TestBasicCSVPrinterWithoutHeader(t *testing.T) {
buffer := new(bytes.Buffer)
printer := printers.NewCSVPrinter(&printers.CSVPrinterConfig{
Out: buffer,
ToHeaderAndRows: func(data any) ([]string, [][]string, error) {
if data != "test" {
t.Errorf("want data test, got %s", data)
}
return []string{"a", "b"}, [][]string{
{"1", "2"},
{"3", "4"},
}, nil
},
NoHeaders: true,
})

err := printer.Print(content)
err := printer.Print("test")
if err != nil {
t.Error(err)
}

got := out.String()
want := expectationWithOutHeader

if !reflect.DeepEqual(got, want) {
diff := cmp.Diff(want, got)
t.Errorf("got %v, want %v, diff %s", got, want, diff)
got := buffer.String()
want := `1;2
3;4
`
if diff := cmp.Diff(want, got); diff != "" {
t.Errorf("diff (+got -want):\n %s", diff)
}
}

func TestCSVEmptyArgHeader(t *testing.T) {
t.Parallel()

out := new(bytes.Buffer)

config := &printers.CSVPrinterConfig{
Delimiter: delimiter,
}
printer := printers.NewCSVPrinter(config).WithOut(out)
func TestBasicCSVPrinterWithCustomDelimiter(t *testing.T) {
buffer := new(bytes.Buffer)
printer := printers.NewCSVPrinter(&printers.CSVPrinterConfig{
Out: buffer,
ToHeaderAndRows: func(data any) ([]string, [][]string, error) {
if data != "test" {
t.Errorf("want data test, got %s", data)
}
return []string{"a", "b"}, [][]string{
{"1", "2"},
{"3", "4"},
}, nil
},
Delimiter: ',',
})

err := printer.Print(content)
err := printer.Print("test")
if err != nil {
t.Error(err)
}

got := out.String()
want := expectationWithOutHeader

if !reflect.DeepEqual(got, want) {
diff := cmp.Diff(want, got)
t.Errorf("got %v, want %v, diff %s", got, want, diff)
got := buffer.String()
want := `a,b
1,2
3,4
`
if diff := cmp.Diff(want, got); diff != "" {
t.Errorf("diff (+got -want):\n %s", diff)
}
}
Loading