@@ -11,8 +11,9 @@ import (
11
11
12
12
// Markdown renders tables in Markdown format with customizable settings.
13
13
type Markdown struct {
14
- config tw.Rendition // Rendering configuration
15
- logger * ll.Logger // Debug trace messages
14
+ config tw.Rendition // Rendering configuration
15
+ logger * ll.Logger // Debug trace messages
16
+ alignment tw.Alignment // alias of []tw.Align
16
17
}
17
18
18
19
// NewMarkdown initializes a Markdown renderer with defaults tailored for Markdown (e.g., pipes, header separator).
@@ -65,6 +66,93 @@ func (m *Markdown) Config() tw.Rendition {
65
66
return m .config
66
67
}
67
68
69
+ // Header renders the Markdown table header and its separator line.
70
+ func (m * Markdown ) Header (w io.Writer , headers [][]string , ctx tw.Formatting ) {
71
+ m .resolveAlignment (ctx )
72
+ if len (headers ) == 0 || len (headers [0 ]) == 0 {
73
+ m .logger .Debug ("Header: No headers to render" )
74
+ return
75
+ }
76
+ m .logger .Debug ("Rendering header with %d lines, widths=%v, current=%v, next=%v" ,
77
+ len (headers ), ctx .Row .Widths , ctx .Row .Current , ctx .Row .Next )
78
+
79
+ // Render header content
80
+ m .renderMarkdownLine (w , headers [0 ], ctx , false )
81
+
82
+ // Render separator if enabled
83
+ if m .config .Settings .Lines .ShowHeaderLine .Enabled () {
84
+ sepCtx := ctx
85
+ sepCtx .Row .Widths = ctx .Row .Widths
86
+ sepCtx .Row .Current = ctx .Row .Current
87
+ sepCtx .Row .Previous = ctx .Row .Current
88
+ sepCtx .IsSubRow = true
89
+ m .renderMarkdownLine (w , nil , sepCtx , true )
90
+ }
91
+ }
92
+
93
+ // Row renders a Markdown table data row.
94
+ func (m * Markdown ) Row (w io.Writer , row []string , ctx tw.Formatting ) {
95
+ m .resolveAlignment (ctx )
96
+ m .logger .Debug ("Rendering row with data=%v, widths=%v, previous=%v, current=%v, next=%v" ,
97
+ row , ctx .Row .Widths , ctx .Row .Previous , ctx .Row .Current , ctx .Row .Next )
98
+ m .renderMarkdownLine (w , row , ctx , false )
99
+
100
+ }
101
+
102
+ // Footer renders the Markdown table footer.
103
+ func (m * Markdown ) Footer (w io.Writer , footers [][]string , ctx tw.Formatting ) {
104
+ m .resolveAlignment (ctx )
105
+ if len (footers ) == 0 || len (footers [0 ]) == 0 {
106
+ m .logger .Debug ("Footer: No footers to render" )
107
+ return
108
+ }
109
+ m .logger .Debug ("Rendering footer with %d lines, widths=%v, previous=%v, current=%v, next=%v" ,
110
+ len (footers ), ctx .Row .Widths , ctx .Row .Previous , ctx .Row .Current , ctx .Row .Next )
111
+ m .renderMarkdownLine (w , footers [0 ], ctx , false )
112
+ }
113
+
114
+ // Line is a no-op for Markdown, as only the header separator is rendered (handled by Header).
115
+ func (m * Markdown ) Line (w io.Writer , ctx tw.Formatting ) {
116
+ m .logger .Debug ("Line: Generic Line call received (pos: %s, loc: %s). Markdown ignores these." , ctx .Row .Position , ctx .Row .Location )
117
+ }
118
+
119
+ // Reset clears the renderer's internal state, including debug traces.
120
+ func (m * Markdown ) Reset () {
121
+ m .logger .Info ("Reset: Cleared debug trace" )
122
+ }
123
+
124
+ func (m * Markdown ) Start (w io.Writer ) error {
125
+ m .logger .Warn ("Markdown.Start() called (no-op)." )
126
+ return nil
127
+ }
128
+
129
+ func (m * Markdown ) Close (w io.Writer ) error {
130
+ m .logger .Warn ("Markdown.Close() called (no-op)." )
131
+ return nil
132
+ }
133
+
134
+ func (m * Markdown ) resolveAlignment (ctx tw.Formatting ) tw.Alignment {
135
+ if len (m .alignment ) != 0 {
136
+ return m .alignment
137
+ }
138
+
139
+ // get total columns
140
+ total := len (ctx .Row .Current )
141
+
142
+ // build default alignment
143
+ for i := 0 ; i < total ; i ++ {
144
+ m .alignment = append (m .alignment , tw .AlignLeft )
145
+ }
146
+
147
+ // add per colum alignment if it exits
148
+ for i := 0 ; i < total ; i ++ {
149
+ m .alignment [i ] = ctx .Row .Current [i ].Align
150
+ }
151
+
152
+ m .logger .Debug (" → Alignment Resolved %s" , m .alignment )
153
+ return m .alignment
154
+ }
155
+
68
156
// formatCell formats a Markdown cell's content with padding and alignment, ensuring at least 3 characters wide.
69
157
func (m * Markdown ) formatCell (content string , width int , align tw.Align , padding tw.Padding ) string {
70
158
//if m.config.Settings.TrimWhitespace.Enabled() {
@@ -154,7 +242,7 @@ func (m *Markdown) formatCell(content string, width int, align tw.Align, padding
154
242
155
243
// formatSeparator generates a Markdown separator (e.g., `---`, `:--`, `:-:`) with alignment indicators.
156
244
func (m * Markdown ) formatSeparator (width int , align tw.Align ) string {
157
- targetWidth := tw .Max (3 , width ) // Minimum width of 3
245
+ targetWidth := tw .Max (3 , width )
158
246
var sb strings.Builder
159
247
160
248
switch align {
@@ -168,7 +256,7 @@ func (m *Markdown) formatSeparator(width int, align tw.Align) string {
168
256
sb .WriteRune (':' )
169
257
sb .WriteString (strings .Repeat ("-" , targetWidth - 2 ))
170
258
sb .WriteRune (':' )
171
- default : // Fallback to left alignment for unspecified
259
+ default :
172
260
sb .WriteRune (':' )
173
261
sb .WriteString (strings .Repeat ("-" , targetWidth - 1 ))
174
262
}
@@ -230,17 +318,13 @@ func (m *Markdown) renderMarkdownLine(w io.Writer, line []string, ctx tw.Formatt
230
318
231
319
for colIndex < numCols {
232
320
cellCtx , ok := ctx .Row .Current [colIndex ]
321
+ align := m .alignment [colIndex ]
322
+
233
323
defaultPadding := tw.Padding {Left : " " , Right : " " }
234
324
if ! ok {
235
- defaultAlign := tw .AlignLeft // Default for rows
236
- if ctx .Row .Position == tw .Header && ! isHeaderSep {
237
- defaultAlign = tw .AlignCenter // Default for headers
238
- }
239
- if ctx .Row .Position == tw .Footer {
240
- defaultAlign = tw .AlignRight
241
- }
325
+
242
326
cellCtx = tw.CellContext {
243
- Data : "" , Align : defaultAlign , Padding : defaultPadding ,
327
+ Data : "" , Align : align , Padding : defaultPadding ,
244
328
Width : ctx .Row .Widths .Get (colIndex ), Merge : tw.MergeState {},
245
329
}
246
330
} else if cellCtx .Padding == (tw.Padding {}) {
@@ -256,7 +340,7 @@ func (m *Markdown) renderMarkdownLine(w io.Writer, line []string, ctx tw.Formatt
256
340
257
341
// Calculate width and span
258
342
span := 1
259
- align := cellCtx . Align
343
+
260
344
if align == tw .AlignNone || align == "" {
261
345
if ctx .Row .Position == tw .Header && ! isHeaderSep {
262
346
align = tw .AlignCenter
@@ -299,26 +383,28 @@ func (m *Markdown) renderMarkdownLine(w io.Writer, line []string, ctx tw.Formatt
299
383
} else {
300
384
var formattedSegment string
301
385
if isHeaderSep {
302
- // Use the header's alignment from ctx.Row.Previous (header row)
386
+ // Use header's alignment from ctx.Row.Previous
303
387
headerAlign := tw .AlignCenter // Default for headers
304
388
if headerCellCtx , headerOK := ctx .Row .Previous [colIndex ]; headerOK {
305
389
headerAlign = headerCellCtx .Align
306
390
if headerAlign == tw .AlignNone || headerAlign == "" {
307
391
headerAlign = tw .AlignCenter
308
392
}
309
- } else if cellCtx , ok := ctx .Row .Current [colIndex ]; ok {
310
- headerAlign = cellCtx .Align
311
- if headerAlign == tw .AlignNone || headerAlign == "" {
312
- headerAlign = tw .AlignCenter
313
- }
314
393
}
315
394
formattedSegment = m .formatSeparator (visualWidth , headerAlign )
316
395
} else {
317
396
content := ""
318
397
if colIndex < len (line ) {
319
398
content = line [colIndex ]
320
399
}
321
- formattedSegment = m .formatCell (content , visualWidth , align , cellCtx .Padding )
400
+ // For rows, use the header's alignment if specified
401
+ rowAlign := align
402
+ if headerCellCtx , headerOK := ctx .Row .Previous [colIndex ]; headerOK && isHeaderSep == false {
403
+ if headerCellCtx .Align != tw .AlignNone && headerCellCtx .Align != "" {
404
+ rowAlign = headerCellCtx .Align
405
+ }
406
+ }
407
+ formattedSegment = m .formatCell (content , visualWidth , rowAlign , cellCtx .Padding )
322
408
}
323
409
output .WriteString (formattedSegment )
324
410
m .logger .Debug ("renderMarkdownLine: Wrote col %d (span %d, width %d): '%s'" , colIndex , span , visualWidth , formattedSegment )
@@ -332,64 +418,3 @@ func (m *Markdown) renderMarkdownLine(w io.Writer, line []string, ctx tw.Formatt
332
418
fmt .Fprint (w , output .String ())
333
419
m .logger .Debug ("renderMarkdownLine: Final line: %s" , strings .TrimSuffix (output .String (), tw .NewLine ))
334
420
}
335
-
336
- // Header renders the Markdown table header and its separator line.
337
- func (m * Markdown ) Header (w io.Writer , headers [][]string , ctx tw.Formatting ) {
338
- if len (headers ) == 0 || len (headers [0 ]) == 0 {
339
- m .logger .Debug ("Header: No headers to render" )
340
- return
341
- }
342
- m .logger .Debug ("Rendering header with %d lines, widths=%v, current=%v, next=%v" ,
343
- len (headers ), ctx .Row .Widths , ctx .Row .Current , ctx .Row .Next )
344
-
345
- // Render header content
346
- m .renderMarkdownLine (w , headers [0 ], ctx , false )
347
-
348
- // Render separator if enabled
349
- if m .config .Settings .Lines .ShowHeaderLine .Enabled () {
350
- sepCtx := ctx
351
- sepCtx .Row .Widths = ctx .Row .Widths
352
- sepCtx .Row .Current = ctx .Row .Current
353
- sepCtx .Row .Previous = ctx .Row .Current
354
- sepCtx .IsSubRow = true
355
- m .renderMarkdownLine (w , nil , sepCtx , true )
356
- }
357
- }
358
-
359
- // Row renders a Markdown table data row.
360
- func (m * Markdown ) Row (w io.Writer , row []string , ctx tw.Formatting ) {
361
- m .logger .Debug ("Rendering row with data=%v, widths=%v, previous=%v, current=%v, next=%v" ,
362
- row , ctx .Row .Widths , ctx .Row .Previous , ctx .Row .Current , ctx .Row .Next )
363
- m .renderMarkdownLine (w , row , ctx , false )
364
- }
365
-
366
- // Footer renders the Markdown table footer.
367
- func (m * Markdown ) Footer (w io.Writer , footers [][]string , ctx tw.Formatting ) {
368
- if len (footers ) == 0 || len (footers [0 ]) == 0 {
369
- m .logger .Debug ("Footer: No footers to render" )
370
- return
371
- }
372
- m .logger .Debug ("Rendering footer with %d lines, widths=%v, previous=%v, current=%v, next=%v" ,
373
- len (footers ), ctx .Row .Widths , ctx .Row .Previous , ctx .Row .Current , ctx .Row .Next )
374
- m .renderMarkdownLine (w , footers [0 ], ctx , false )
375
- }
376
-
377
- // Line is a no-op for Markdown, as only the header separator is rendered (handled by Header).
378
- func (m * Markdown ) Line (w io.Writer , ctx tw.Formatting ) {
379
- m .logger .Debug ("Line: Generic Line call received (pos: %s, loc: %s). Markdown ignores these." , ctx .Row .Position , ctx .Row .Location )
380
- }
381
-
382
- // Reset clears the renderer's internal state, including debug traces.
383
- func (m * Markdown ) Reset () {
384
- m .logger .Info ("Reset: Cleared debug trace" )
385
- }
386
-
387
- func (m * Markdown ) Start (w io.Writer ) error {
388
- m .logger .Warn ("Markdown.Start() called (no-op)." )
389
- return nil
390
- }
391
-
392
- func (m * Markdown ) Close (w io.Writer ) error {
393
- m .logger .Warn ("Markdown.Close() called (no-op)." )
394
- return nil
395
- }
0 commit comments