Skip to content

Commit 846f3aa

Browse files
committed
Implement unaligned format
1 parent 97bbfd9 commit 846f3aa

File tree

4 files changed

+140
-17
lines changed

4 files changed

+140
-17
lines changed

csv/writer.go

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package csv
2+
3+
import (
4+
"bufio"
5+
"io"
6+
)
7+
8+
// Writer identical to "encoding/csv", but does not quote values and allows any newline.
9+
type Writer struct {
10+
Comma rune
11+
Newline string
12+
w *bufio.Writer
13+
}
14+
15+
// NewWriter returns a new Writer that writes to w.
16+
func NewWriter(w io.Writer) *Writer {
17+
return &Writer{
18+
Comma: ',',
19+
Newline: "\n",
20+
w: bufio.NewWriter(w),
21+
}
22+
}
23+
24+
// Write writes a single CSV record to w.
25+
// A record is a slice of strings with each string being one field.
26+
// Writes are buffered, so Flush must eventually be called to ensure
27+
// that the record is written to the underlying io.Writer.
28+
func (w *Writer) Write(record []string) error {
29+
for n, field := range record {
30+
if n > 0 {
31+
if _, err := w.w.WriteRune(w.Comma); err != nil {
32+
return err
33+
}
34+
}
35+
36+
if _, err := w.w.WriteString(field); err != nil {
37+
return err
38+
}
39+
}
40+
_, err := w.w.WriteString(w.Newline)
41+
return err
42+
}
43+
44+
// Flush writes any buffered data to the underlying io.Writer.
45+
// To check if an error occurred during the Flush, call Error.
46+
func (w *Writer) Flush() {
47+
w.w.Flush()
48+
}
49+
50+
// Error reports any error that has occurred during a previous Write or Flush.
51+
func (w *Writer) Error() error {
52+
_, err := w.w.Write(nil)
53+
return err
54+
}

encode.go

+37-10
Original file line numberDiff line numberDiff line change
@@ -391,7 +391,7 @@ func (enc *TableEncoder) header() {
391391

392392
if enc.title != nil && enc.title.Width != 0 {
393393
maxWidth := ((enc.tableWidth() - enc.title.Width) / 2) + enc.title.Width
394-
enc.writeAligned(enc.title.Buf, rs.filler, AlignRight, enc.title.Width, maxWidth)
394+
enc.writeAligned(enc.title.Buf, rs.filler, AlignRight, maxWidth-enc.title.Width)
395395
enc.w.Write(enc.newline)
396396
}
397397
// draw top border
@@ -590,9 +590,16 @@ func (enc *TableEncoder) row(vals []*Value, rs rowStyle) {
590590
width += v.Width
591591
}
592592

593-
enc.writeAligned(v.Buf[start:end], rs.filler, v.Align, width, enc.maxWidths[i])
593+
padding := enc.maxWidths[i] - width
594+
// no padding for last cell if no border
595+
if enc.border <= 1 && i == len(vals)-1 && (!rs.hasWrapping || l >= len(v.Newlines)) {
596+
padding = 0
597+
}
598+
enc.writeAligned(v.Buf[start:end], rs.filler, v.Align, padding)
594599
} else {
595-
enc.w.Write(bytes.Repeat(rs.filler, enc.maxWidths[i]))
600+
if enc.border > 1 || i != len(vals)-1 {
601+
enc.w.Write(bytes.Repeat(rs.filler, enc.maxWidths[i]))
602+
}
596603
}
597604

598605
// write newline wrap value
@@ -624,9 +631,8 @@ func (enc *TableEncoder) row(vals []*Value, rs rowStyle) {
624631
}
625632
}
626633

627-
func (enc *TableEncoder) writeAligned(b, filler []byte, a Align, width, max int) {
634+
func (enc *TableEncoder) writeAligned(b, filler []byte, a Align, padding int) {
628635
// calc padding
629-
padding := max - width
630636
paddingLeft := 0
631637
paddingRight := 0
632638
switch a {
@@ -1129,12 +1135,18 @@ func (enc *JSONEncoder) scanAndFormat(vals []interface{}) ([]*Value, error) {
11291135

11301136
// CSVEncoder is an unbuffered CSV encoder for result sets.
11311137
type CSVEncoder struct {
1132-
// ResultSet is the result set to encode.
1138+
// resultSet is the result set to encode.
11331139
resultSet ResultSet
11341140

1141+
// newCSVWriter that should have all options already set
1142+
newCSVWriter func(io.Writer) CSVWriter
1143+
11351144
// fieldsep is the field separator to use.
11361145
fieldsep rune
11371146

1147+
// fieldsep is true if fieldsep should be a zero byte.
1148+
fieldsepIsZero bool
1149+
11381150
// newline is the newline to use.
11391151
newline []byte
11401152

@@ -1148,6 +1160,12 @@ type CSVEncoder struct {
11481160
empty *Value
11491161
}
11501162

1163+
type CSVWriter interface {
1164+
Write([]string) error
1165+
Flush()
1166+
Error() error
1167+
}
1168+
11511169
// NewCSVEncoder creates a new CSV encoder using the provided options.
11521170
func NewCSVEncoder(resultSet ResultSet, opts ...Option) (Encoder, error) {
11531171
var err error
@@ -1164,6 +1182,18 @@ func NewCSVEncoder(resultSet ResultSet, opts ...Option) (Encoder, error) {
11641182
return nil, err
11651183
}
11661184
}
1185+
if enc.newCSVWriter == nil {
1186+
enc.newCSVWriter = func(w io.Writer) CSVWriter {
1187+
writer := csv.NewWriter(w)
1188+
if enc.fieldsep != 0 {
1189+
writer.Comma = enc.fieldsep
1190+
}
1191+
if enc.fieldsepIsZero {
1192+
writer.Comma = 0
1193+
}
1194+
return writer
1195+
}
1196+
}
11671197
return enc, nil
11681198
}
11691199

@@ -1177,10 +1207,7 @@ func (enc *CSVEncoder) Encode(w io.Writer) error {
11771207
var i int
11781208
var err error
11791209

1180-
c := csv.NewWriter(w)
1181-
if enc.fieldsep != 0 {
1182-
c.Comma = enc.fieldsep
1183-
}
1210+
c := enc.newCSVWriter(w)
11841211

11851212
// get and check columns
11861213
cols, err := enc.resultSet.Columns()

opts.go

+46-7
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import (
77
txt "text/template"
88
"unicode/utf8"
99

10+
"github.com/xo/tblfmt/csv"
11+
1012
"github.com/nathan-fiscaletti/consolesize-go"
1113
)
1214

@@ -24,11 +26,39 @@ func FromMap(opts map[string]string) (Builder, []Option) {
2426
case "json":
2527
return NewJSONEncoder, nil
2628

27-
case "csv":
29+
case "csv", "unaligned":
2830
var csvOpts []Option
29-
if s, ok := opts["fieldsep"]; ok {
30-
sep, _ := utf8.DecodeRuneInString(s)
31-
csvOpts = append(csvOpts, WithFieldSeparator(sep))
31+
if opts["format"] == "unaligned" {
32+
newline := "\n"
33+
if s, ok := opts["recordsep"]; ok {
34+
newline = s
35+
}
36+
fieldsep := '|'
37+
if s, ok := opts["fieldsep"]; ok {
38+
r, _ := utf8.DecodeRuneInString(s)
39+
fieldsep = r
40+
}
41+
if s, ok := opts["fieldsep_zero"]; ok && s == "on" {
42+
fieldsep = 0
43+
}
44+
csvOpts = append(csvOpts, WithNewCSVWriter(func(w io.Writer) CSVWriter {
45+
writer := csv.NewWriter(w)
46+
writer.Newline = newline
47+
writer.Comma = fieldsep
48+
return writer
49+
}))
50+
} else {
51+
csvOpts = append(csvOpts, WithNewline(""))
52+
// recognize both for backward-compatibility, but csv_fieldsep takes precedence
53+
for _, name := range []string{"fieldsep", "csv_fieldsep"} {
54+
if s, ok := opts[name]; ok {
55+
sep, _ := utf8.DecodeRuneInString(s)
56+
csvOpts = append(csvOpts, WithFieldSeparator(sep))
57+
}
58+
}
59+
}
60+
if s, ok := opts["fieldsep_zero"]; ok && s == "on" {
61+
csvOpts = append(csvOpts, WithFieldSeparator(0))
3262
}
3363
if s, ok := opts["tuples_only"]; ok && s == "on" {
3464
csvOpts = append(csvOpts, WithSkipHeader(true))
@@ -42,9 +72,6 @@ func FromMap(opts map[string]string) (Builder, []Option) {
4272
WithTitle(opts["title"]),
4373
}
4474

45-
case "unaligned":
46-
fallthrough
47-
4875
case "aligned":
4976
var tableOpts []Option
5077
if s, ok := opts["border"]; ok {
@@ -345,12 +372,24 @@ func WithNewline(newline string) Option {
345372
}
346373
}
347374

375+
// WithNewCSVWriter is a encoder option to set the newCSVWriter func.
376+
func WithNewCSVWriter(f func(io.Writer) CSVWriter) Option {
377+
return func(v interface{}) error {
378+
switch enc := v.(type) {
379+
case *CSVEncoder:
380+
enc.newCSVWriter = f
381+
}
382+
return nil
383+
}
384+
}
385+
348386
// WithFieldSeparator is a encoder option to set the field separator.
349387
func WithFieldSeparator(fieldsep rune) Option {
350388
return func(v interface{}) error {
351389
switch enc := v.(type) {
352390
case *CSVEncoder:
353391
enc.fieldsep = fieldsep
392+
enc.fieldsepIsZero = fieldsep == 0
354393
}
355394
return nil
356395
}

tblfmt_test.go

+3
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ func TestEncodeFormats(t *testing.T) {
2020
name: "unaligned",
2121
params: map[string]string{
2222
"format": "unaligned",
23+
"title": "won't print",
24+
// TODO psql does print the footer
25+
"footer": "off",
2326
},
2427
},
2528
{

0 commit comments

Comments
 (0)