Skip to content

Commit

Permalink
Replace CSV printer with more generic approach. (#131)
Browse files Browse the repository at this point in the history
  • Loading branch information
Gerrit91 authored Mar 11, 2024
1 parent 0ac6699 commit b308737
Show file tree
Hide file tree
Showing 4 changed files with 91 additions and 132 deletions.
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)
}
}

0 comments on commit b308737

Please sign in to comment.