From a4237aa00cd1ef607566c1de3734e082a57226cd Mon Sep 17 00:00:00 2001 From: Natercio Moniz Date: Sat, 16 May 2026 15:38:19 +0100 Subject: [PATCH] add a csv printer --- cmd/any2anexoj-cli/csv_writer.go | 57 ++++++++++++++++++++++++++++++++ cmd/any2anexoj-cli/main.go | 22 +++++++----- 2 files changed, 70 insertions(+), 9 deletions(-) create mode 100644 cmd/any2anexoj-cli/csv_writer.go diff --git a/cmd/any2anexoj-cli/csv_writer.go b/cmd/any2anexoj-cli/csv_writer.go new file mode 100644 index 0000000..9f31464 --- /dev/null +++ b/cmd/any2anexoj-cli/csv_writer.go @@ -0,0 +1,57 @@ +package main + +import ( + "encoding/csv" + "fmt" + "io" + + "github.com/nmoniz/any2anexoj/internal" +) + +type CSVWriter struct { + w *csv.Writer +} + +func NewCSVWriter(w io.Writer) *CSVWriter { + return &CSVWriter{w: csv.NewWriter(w)} +} + +func (cw *CSVWriter) Render(aw *internal.AggregatorWriter) error { + err := cw.w.Write([]string{ + "source_country", "code", + "realization_year", "realization_month", "realization_day", "realization_value", + "acquisition_year", "acquisition_month", "acquisition_day", "acquisition_value", + "expenses", "foreign_tax_paid", "counter_country", + }) + if err != nil { + return fmt.Errorf("write csv header: %w", err) + } + + for ri := range aw.Iter() { + err := cw.w.Write(reportItemToRow(ri)) + if err != nil { + return fmt.Errorf("write csv row: %w", err) + } + } + + cw.w.Flush() + return cw.w.Error() +} + +func reportItemToRow(ri internal.ReportItem) []string { + return []string{ + fmt.Sprintf("%d", ri.AssetCountry), + string(ri.Nature), + fmt.Sprintf("%d", ri.SellTimestamp.Year()), + fmt.Sprintf("%d", int(ri.SellTimestamp.Month())), + fmt.Sprintf("%d", ri.SellTimestamp.Day()), + ri.SellValue.StringFixed(2), + fmt.Sprintf("%d", ri.BuyTimestamp.Year()), + fmt.Sprintf("%d", int(ri.BuyTimestamp.Month())), + fmt.Sprintf("%d", ri.BuyTimestamp.Day()), + ri.BuyValue.StringFixed(2), + ri.Fees.StringFixed(2), + ri.Taxes.StringFixed(2), + fmt.Sprintf("%d", ri.BrokerCountry), + } +} diff --git a/cmd/any2anexoj-cli/main.go b/cmd/any2anexoj-cli/main.go index 68da9b9..c2887da 100644 --- a/cmd/any2anexoj-cli/main.go +++ b/cmd/any2anexoj-cli/main.go @@ -22,6 +22,7 @@ var ( platform = pflag.StringP("platform", "p", "trading212", "One of the supported platforms") lang = pflag.StringP("language", "l", language.Portuguese.String(), "The 2 letter language code") debug = pflag.BoolP("debug", "d", false, "Activate to log debug messages") + format = pflag.StringP("format", "f", "table", "Output format: table or csv") ofAPIKey = pflag.String("open-figi-api-key", "", "An OpenFIGI API key for faster report generation (better rate api rate limits)") // TODO: improve documentation on selectors selectors = pflag.StringSlice("selectors", nil, "Only process entries that conform to all the selectors:") @@ -80,16 +81,19 @@ func run(ctx context.Context) error { return err } - loc, err := NewLocalizer(*lang) - if err != nil { - return fmt.Errorf("create localizer: %w", err) + switch *format { + case "csv": + return NewCSVWriter(os.Stdout).Render(writer) + case "table": + loc, err := NewLocalizer(*lang) + if err != nil { + return fmt.Errorf("create localizer: %w", err) + } + NewPrettyPrinter(os.Stdout, loc).Render(writer) + return nil + default: + return fmt.Errorf("unsupported format %q: must be table or csv", *format) } - - printer := NewPrettyPrinter(os.Stdout, loc) - - printer.Render(writer) - - return nil } func getReader(platform string, ofAPIKey string) (internal.RecordReader, error) {