diff --git a/internal/commands/receipts/receipts.go b/internal/commands/receipts/receipts.go index b9a7c32..61f1d2c 100644 --- a/internal/commands/receipts/receipts.go +++ b/internal/commands/receipts/receipts.go @@ -80,25 +80,32 @@ func getReceipt(ctx context.Context, cmd *cli.Command) error { } func renderReceipt(w io.Writer, receipt *sumup.Receipt) { + sections := make([]display.Section, 0, 4) + if transaction := receipt.TransactionData; transaction != nil { - writeLine(w, "Transaction") - display.DataList(w, []attribute.KeyValue{ - attribute.OptionalString("Code", transaction.TransactionCode), - attribute.OptionalString("Status", transaction.Status), - attribute.OptionalString("Payment Type", transaction.PaymentType), - attribute.Attribute("Amount", attribute.Styled(receiptAmount(transaction))), - attribute.Attribute("Timestamp", attribute.Styled(timePointerToString(transaction.Timestamp))), - attribute.OptionalString("Entry Mode", transaction.EntryMode), - attribute.OptionalString("Verification", transaction.VerificationMethod), - attribute.Attribute("Card", attribute.Styled(receiptCard(transaction))), + sections = append(sections, display.Section{ + Title: "Transaction", + Pairs: []attribute.KeyValue{ + attribute.OptionalString("Code", transaction.TransactionCode), + attribute.OptionalString("Status", transaction.Status), + attribute.OptionalString("Payment Type", transaction.PaymentType), + attribute.Attribute("Amount", attribute.Styled(receiptAmount(transaction))), + attribute.Attribute("Timestamp", attribute.Styled(timePointerToString(transaction.Timestamp))), + attribute.OptionalString("Entry Mode", transaction.EntryMode), + attribute.OptionalString("Verification", transaction.VerificationMethod), + attribute.Attribute("Card", attribute.Styled(receiptCard(transaction))), + }, }) } else { - writeLine(w, "Transaction: -") + sections = append(sections, display.Section{ + Title: "Transaction", + Lines: []string{"-"}, + }) } if merchant := receipt.MerchantData; merchant != nil { - writeLine(w, "\nMerchant") pairs := make([]attribute.KeyValue, 0, 5) + lines := []string{} if profile := merchant.MerchantProfile; profile != nil { pairs = append(pairs, attribute.OptionalString("Name", profile.BusinessName)) pairs = append(pairs, attribute.OptionalString("Code", profile.MerchantCode)) @@ -109,40 +116,42 @@ func renderReceipt(w io.Writer, receipt *sumup.Receipt) { } pairs = append(pairs, attribute.OptionalString("Email", profile.Email)) } else { - writeLine(w, "Merchant profile unavailable") + lines = append(lines, "Merchant profile unavailable") } if merchant.Locale != nil && *merchant.Locale != "" { pairs = append(pairs, attribute.Attribute("Locale", attribute.Styled(*merchant.Locale))) } - display.DataList(w, pairs) + sections = append(sections, display.Section{ + Title: "Merchant", + Pairs: pairs, + Lines: lines, + }) } if acquirer := receipt.AcquirerData; acquirer != nil { - writeLine(w, "\nAcquirer") - display.DataList(w, []attribute.KeyValue{ - attribute.OptionalString("Terminal ID", acquirer.Tid), - attribute.OptionalString("Authorization Code", acquirer.AuthorizationCode), - attribute.OptionalString("Return Code", acquirer.ReturnCode), - attribute.OptionalString("Local Time", acquirer.LocalTime), + sections = append(sections, display.Section{ + Title: "Acquirer", + Pairs: []attribute.KeyValue{ + attribute.OptionalString("Terminal ID", acquirer.Tid), + attribute.OptionalString("Authorization Code", acquirer.AuthorizationCode), + attribute.OptionalString("Return Code", acquirer.ReturnCode), + attribute.OptionalString("Local Time", acquirer.LocalTime), + }, }) } - if transaction := receipt.TransactionData; transaction != nil { - if len(transaction.Events) > 0 { - writef(w, "\nEvents (%d)\n", len(transaction.Events)) - for _, event := range transaction.Events { - writef(w, " - %s %s\n", enumValue(event.Type), enumValue(event.Status)) - } + if transaction := receipt.TransactionData; transaction != nil && len(transaction.Events) > 0 { + lines := make([]string, 0, len(transaction.Events)) + for _, event := range transaction.Events { + lines = append(lines, fmt.Sprintf("- %s %s", enumValue(event.Type), enumValue(event.Status))) } + sections = append(sections, display.Section{ + Title: fmt.Sprintf("Events (%d)", len(transaction.Events)), + Lines: lines, + }) } -} - -func writeLine(w io.Writer, value string) { - _, _ = fmt.Fprintln(w, value) -} -func writef(w io.Writer, format string, args ...any) { - _, _ = fmt.Fprintf(w, format, args...) + display.RenderSections(w, sections) } func receiptAmount(transaction *sumup.ReceiptTransaction) string { diff --git a/internal/display/output_test.go b/internal/display/output_test.go index 9458dad..60b2e41 100644 --- a/internal/display/output_test.go +++ b/internal/display/output_test.go @@ -46,3 +46,25 @@ func TestRenderTable(t *testing.T) { t.Fatalf("RenderTable() output = %q, want title and row content", rendered) } } + +func TestRenderSections(t *testing.T) { + var out bytes.Buffer + + display.RenderSections(&out, []display.Section{ + { + Title: "Transaction", + Pairs: []attribute.KeyValue{ + attribute.Attribute("Status", attribute.Styled("ok")), + }, + }, + { + Title: "Events", + Lines: []string{"- created"}, + }, + }) + + rendered := out.String() + if !strings.Contains(rendered, "Transaction") || !strings.Contains(rendered, "Events") || !strings.Contains(rendered, "- created") { + t.Fatalf("RenderSections() output = %q, want section titles and lines", rendered) + } +} diff --git a/internal/display/sections.go b/internal/display/sections.go new file mode 100644 index 0000000..3157afa --- /dev/null +++ b/internal/display/sections.go @@ -0,0 +1,41 @@ +package display + +import ( + "io" + "strings" + + "github.com/sumup/sumup-cli/internal/display/attribute" +) + +// Section groups related output under a title. +type Section struct { + Title string + Pairs []attribute.KeyValue + Lines []string +} + +// RenderSections renders titled output blocks with a blank line between them. +func RenderSections(w io.Writer, sections []Section) { + rendered := 0 + for _, section := range sections { + if strings.TrimSpace(section.Title) == "" { + continue + } + if len(section.Pairs) == 0 && len(section.Lines) == 0 { + continue + } + + if rendered > 0 { + writeln(w) + } + + writeln(w, section.Title) + if len(section.Pairs) > 0 { + DataList(w, section.Pairs) + } + for _, line := range section.Lines { + writeln(w, line) + } + rendered++ + } +}