@@ -416,6 +416,244 @@ func main() {
416
416
</table >
417
417
```
418
418
419
+
420
+ #### Custom Invoice Renderer
421
+
422
+ ``` go
423
+
424
+ package main
425
+
426
+ import (
427
+ " fmt"
428
+ " io"
429
+ " os"
430
+ " strings"
431
+
432
+ " github.com/olekukonko/ll"
433
+ " github.com/olekukonko/tablewriter"
434
+ " github.com/olekukonko/tablewriter/tw"
435
+ )
436
+
437
+ // InvoiceRenderer implements tw.Renderer for a basic invoice style.
438
+ type InvoiceRenderer struct {
439
+ writer io.Writer
440
+ logger *ll.Logger
441
+ rendition tw.Rendition
442
+ }
443
+
444
+ func NewInvoiceRenderer () *InvoiceRenderer {
445
+ rendition := tw.Rendition {
446
+ Borders: tw.BorderNone ,
447
+ Symbols: tw.NewSymbols (tw.StyleNone ),
448
+ Settings: tw.Settings {Separators: tw.SeparatorsNone , Lines: tw.LinesNone },
449
+ Streaming: false ,
450
+ }
451
+ defaultLogger := ll.New (" simple-invoice-renderer" )
452
+ return &InvoiceRenderer{logger: defaultLogger, rendition: rendition}
453
+ }
454
+
455
+ func (r *InvoiceRenderer ) Logger (logger *ll .Logger ) {
456
+ if logger != nil {
457
+ r.logger = logger
458
+ }
459
+ }
460
+
461
+ func (r *InvoiceRenderer ) Config () tw .Rendition {
462
+ return r.rendition
463
+ }
464
+
465
+ func (r *InvoiceRenderer ) Start (w io .Writer ) error {
466
+ r.writer = w
467
+ r.logger .Debug (" InvoiceRenderer: Start" )
468
+ return nil
469
+ }
470
+
471
+ func (r *InvoiceRenderer ) formatLine (cells []string , widths tw .Mapper [int , int ], cellContexts map [int ]tw .CellContext ) string {
472
+ var sb strings.Builder
473
+ numCols := 0
474
+ if widths != nil { // Ensure widths is not nil before calling Len
475
+ numCols = widths.Len ()
476
+ }
477
+
478
+ for i := 0 ; i < numCols; i++ {
479
+ data := " "
480
+ if i < len (cells) {
481
+ data = cells[i]
482
+ }
483
+
484
+ width := 0
485
+ if widths != nil { // Check again before Get
486
+ width = widths.Get (i)
487
+ }
488
+
489
+ align := tw.AlignDefault
490
+ if cellContexts != nil { // Check cellContexts
491
+ if ctx , ok := cellContexts[i]; ok {
492
+ align = ctx.Align
493
+ }
494
+ }
495
+
496
+ paddedCell := tw.Pad (data, " " , width, align)
497
+ sb.WriteString (paddedCell)
498
+
499
+ if i < numCols-1 {
500
+ sb.WriteString (" " ) // Column separator
501
+ }
502
+ }
503
+ return sb.String ()
504
+ }
505
+
506
+ func (r *InvoiceRenderer ) Header (headers [][]string , ctx tw .Formatting ) {
507
+ if r.writer == nil {
508
+ return
509
+ }
510
+ r.logger .Debugf (" InvoiceRenderer: Header (lines: %d )" , len (headers))
511
+
512
+ for _ , headerLineCells := range headers {
513
+ lineStr := r.formatLine (headerLineCells, ctx.Row .Widths , ctx.Row .Current )
514
+ fmt.Fprintln (r.writer , lineStr)
515
+ }
516
+
517
+ if len (headers) > 0 {
518
+ totalWidth := 0
519
+ if ctx.Row .Widths != nil {
520
+ ctx.Row .Widths .Each (func (_ int , w int ) { totalWidth += w })
521
+ if ctx.Row .Widths .Len () > 1 {
522
+ totalWidth += (ctx.Row .Widths .Len () - 1 ) * 3 // Separator spaces
523
+ }
524
+ }
525
+ if totalWidth > 0 {
526
+ fmt.Fprintln (r.writer , strings.Repeat (" -" , totalWidth))
527
+ }
528
+ }
529
+ }
530
+
531
+ func (r *InvoiceRenderer ) Row (row []string , ctx tw .Formatting ) {
532
+ if r.writer == nil {
533
+ return
534
+ }
535
+ r.logger .Debug (" InvoiceRenderer: Row" )
536
+ lineStr := r.formatLine (row, ctx.Row .Widths , ctx.Row .Current )
537
+ fmt.Fprintln (r.writer , lineStr)
538
+ }
539
+
540
+ func (r *InvoiceRenderer ) Footer (footers [][]string , ctx tw .Formatting ) {
541
+ if r.writer == nil {
542
+ return
543
+ }
544
+ r.logger .Debugf (" InvoiceRenderer: Footer (lines: %d )" , len (footers))
545
+
546
+ if len (footers) > 0 {
547
+ totalWidth := 0
548
+ if ctx.Row .Widths != nil {
549
+ ctx.Row .Widths .Each (func (_ int , w int ) { totalWidth += w })
550
+ if ctx.Row .Widths .Len () > 1 {
551
+ totalWidth += (ctx.Row .Widths .Len () - 1 ) * 3
552
+ }
553
+ }
554
+ if totalWidth > 0 {
555
+ fmt.Fprintln (r.writer , strings.Repeat (" -" , totalWidth))
556
+ }
557
+ }
558
+
559
+ for _ , footerLineCells := range footers {
560
+ lineStr := r.formatLine (footerLineCells, ctx.Row .Widths , ctx.Row .Current )
561
+ fmt.Fprintln (r.writer , lineStr)
562
+ }
563
+ }
564
+
565
+ func (r *InvoiceRenderer ) Line (ctx tw .Formatting ) {
566
+ r.logger .Debug (" InvoiceRenderer: Line (no-op)" )
567
+ // This simple renderer draws its own lines in Header/Footer.
568
+ }
569
+
570
+ func (r *InvoiceRenderer ) Close () error {
571
+ r.logger .Debug (" InvoiceRenderer: Close" )
572
+ r.writer = nil
573
+ return nil
574
+ }
575
+
576
+ func main () {
577
+ data := [][]string {
578
+ {" Product A" , " 2" , " 10.00" , " 20.00" },
579
+ {" Super Long Product Name B" , " 1" , " 125.50" , " 125.50" },
580
+ {" Item C" , " 10" , " 1.99" , " 19.90" },
581
+ }
582
+
583
+ header := []string {" Description" , " Qty" , " Unit Price" , " Total Price" }
584
+ footer := []string {" " , " " , " Subtotal:\n Tax (10% ):\n GRAND TOTAL:" , " 165.40\n 16.54\n 181.94" }
585
+ invoiceRenderer := NewInvoiceRenderer ()
586
+
587
+ // Create table and set custom renderer using Options
588
+ table := tablewriter.NewTable (os.Stdout ,
589
+ tablewriter.WithRenderer (invoiceRenderer),
590
+ tablewriter.WithAlignment ([]tw.Align {
591
+ tw.AlignLeft , tw.AlignCenter , tw.AlignRight , tw.AlignRight ,
592
+ }),
593
+ )
594
+
595
+ table.Header (header)
596
+ for _ , v := range data {
597
+ table.Append (v)
598
+ }
599
+
600
+ // Use the Footer method with strings containing newlines for multi-line cells
601
+ table.Footer (footer)
602
+
603
+ fmt.Println (" Rendering with InvoiceRenderer:" )
604
+ table.Render ()
605
+
606
+ // For comparison, render with default Blueprint renderer
607
+ // Re-create the table or reset it to use a different renderer
608
+ table2 := tablewriter.NewTable (os.Stdout ,
609
+ tablewriter.WithAlignment ([]tw.Align {
610
+ tw.AlignLeft , tw.AlignCenter , tw.AlignRight , tw.AlignRight ,
611
+ }),
612
+ )
613
+
614
+ table2.Header (header)
615
+ for _ , v := range data {
616
+ table2.Append (v)
617
+ }
618
+ table2.Footer (footer)
619
+ fmt.Println (" \n Rendering with Default Blueprint Renderer (for comparison):" )
620
+ table2.Render ()
621
+ }
622
+
623
+ ```
624
+
625
+
626
+ ```
627
+ Rendering with InvoiceRenderer:
628
+ DESCRIPTION QTY UNIT PRICE TOTAL PRICE
629
+ --------------------------------------------------------------------
630
+ Product A 2 10.00 20.00
631
+ Super Long Product Name B 1 125.50 125.50
632
+ Item C 10 1.99 19.90
633
+ --------------------------------------------------------------------
634
+ Subtotal: 165.40
635
+ --------------------------------------------------------------------
636
+ Tax (10%): 16.54
637
+ --------------------------------------------------------------------
638
+ GRAND TOTAL: 181.94
639
+ ```
640
+
641
+
642
+ ```
643
+ Rendering with Default Blueprint Renderer (for comparison):
644
+ ┌───────────────────────────┬─────┬──────────────┬─────────────┐
645
+ │ DESCRIPTION │ QTY │ UNIT PRICE │ TOTAL PRICE │
646
+ ├───────────────────────────┼─────┼──────────────┼─────────────┤
647
+ │ Product A │ 2 │ 10.00 │ 20.00 │
648
+ │ Super Long Product Name B │ 1 │ 125.50 │ 125.50 │
649
+ │ Item C │ 10 │ 1.99 │ 19.90 │
650
+ ├───────────────────────────┼─────┼──────────────┼─────────────┤
651
+ │ │ │ Subtotal: │ 165.40 │
652
+ │ │ │ Tax (10%): │ 16.54 │
653
+ │ │ │ GRAND TOTAL: │ 181.94 │
654
+ └───────────────────────────┴─────┴──────────────┴─────────────┘
655
+
656
+ ```
419
657
** Notes** :
420
658
- The ` renderer.NewBlueprint() ` is sufficient for most text-based use cases.
421
659
- Custom renderers require implementing all interface methods to handle table structure correctly. ` tw.Formatting ` (which includes ` tw.RowContext ` ) provides cell content and metadata.
0 commit comments