diff --git a/go.mod b/go.mod index f3a729f..04c9ad2 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index d1aa9ed..cec6207 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/pkg/genericcli/printers/csv.go b/pkg/genericcli/printers/csv.go index df9d005..b7a62ee 100644 --- a/pkg/genericcli/printers/csv.go +++ b/pkg/genericcli/printers/csv.go @@ -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 { @@ -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 } diff --git a/pkg/genericcli/printers/csv_test.go b/pkg/genericcli/printers/csv_test.go index 88f95bf..fffc4ca 100644 --- a/pkg/genericcli/printers/csv_test.go +++ b/pkg/genericcli/printers/csv_test.go @@ -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) } }