Added CSV to ASCII table
This commit is contained in:
parent
44e4d09c3a
commit
cb9c05a57b
2 changed files with 152 additions and 0 deletions
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
"encoding/csv"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
@ -12,6 +13,7 @@ import (
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
|
|
@ -184,6 +186,64 @@ var TextFilters = map[string]TextProcessor{
|
||||||
return TextFilterResponse{Output: dst.String()}, nil
|
return TextFilterResponse{Output: dst.String()}, nil
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"csv-to-ascii-table": {
|
||||||
|
Label: "Convert: CSV to ASCII Table",
|
||||||
|
Filter: func(ctx context.Context, input string) (resp TextFilterResponse, err error) {
|
||||||
|
endNL := ""
|
||||||
|
if strings.HasSuffix(input, "\n") {
|
||||||
|
endNL = "\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
reader := csv.NewReader(strings.NewReader(input))
|
||||||
|
reader.FieldsPerRecord = -1
|
||||||
|
records, err := reader.ReadAll()
|
||||||
|
if err != nil {
|
||||||
|
return TextFilterResponse{}, err
|
||||||
|
}
|
||||||
|
if len(records) == 0 {
|
||||||
|
return TextFilterResponse{Output: input}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var colWidths []int
|
||||||
|
for _, record := range records {
|
||||||
|
for i, cell := range record {
|
||||||
|
if i >= len(colWidths) {
|
||||||
|
colWidths = append(colWidths, 0)
|
||||||
|
}
|
||||||
|
colWidths[i] = max(colWidths[i], utf8.RuneCountInString(cell))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
writeRow := func(dst *strings.Builder, cells []string) {
|
||||||
|
line := make([]string, len(colWidths))
|
||||||
|
for i, width := range colWidths {
|
||||||
|
cell := ""
|
||||||
|
if i < len(cells) {
|
||||||
|
cell = cells[i]
|
||||||
|
}
|
||||||
|
line[i] = cell + strings.Repeat(" ", width-utf8.RuneCountInString(cell))
|
||||||
|
}
|
||||||
|
dst.WriteString(strings.TrimRight(strings.Join(line, " "), " "))
|
||||||
|
dst.WriteString("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
dashes := make([]string, len(colWidths))
|
||||||
|
for i, width := range colWidths {
|
||||||
|
dashes[i] = strings.Repeat("-", width)
|
||||||
|
}
|
||||||
|
dashLine := strings.Join(dashes, " ") + "\n"
|
||||||
|
|
||||||
|
var dst strings.Builder
|
||||||
|
writeRow(&dst, records[0])
|
||||||
|
dst.WriteString(dashLine)
|
||||||
|
for _, record := range records[1:] {
|
||||||
|
writeRow(&dst, record)
|
||||||
|
}
|
||||||
|
dst.WriteString(dashLine)
|
||||||
|
|
||||||
|
return TextFilterResponse{Output: strings.TrimSuffix(dst.String(), "\n") + endNL}, nil
|
||||||
|
},
|
||||||
|
},
|
||||||
"convert-yaml-to-json": {
|
"convert-yaml-to-json": {
|
||||||
Label: "Convert: YAML to JSON",
|
Label: "Convert: YAML to JSON",
|
||||||
Filter: func(ctx context.Context, input string) (resp TextFilterResponse, err error) {
|
Filter: func(ctx context.Context, input string) (resp TextFilterResponse, err error) {
|
||||||
|
|
|
||||||
92
textfilters_test.go
Normal file
92
textfilters_test.go
Normal file
|
|
@ -0,0 +1,92 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCSVToASCIITable(t *testing.T) {
|
||||||
|
filter := TextFilters["csv-to-ascii-table"].Filter
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input string
|
||||||
|
want string
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "basic table with quoted value",
|
||||||
|
input: "alpha,bravo\n" +
|
||||||
|
"1,b\n" +
|
||||||
|
"\"long value\",something\n",
|
||||||
|
want: "alpha bravo\n" +
|
||||||
|
"---------- ---------\n" +
|
||||||
|
"1 b\n" +
|
||||||
|
"long value something\n" +
|
||||||
|
"---------- ---------\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no trailing newline",
|
||||||
|
input: "a,b\n1,2",
|
||||||
|
want: "a b\n" +
|
||||||
|
"- -\n" +
|
||||||
|
"1 2\n" +
|
||||||
|
"- -",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "quoted field containing comma",
|
||||||
|
input: "name,desc\n\"x, y\",z\n",
|
||||||
|
want: "name desc\n" +
|
||||||
|
"---- ----\n" +
|
||||||
|
"x, y z\n" +
|
||||||
|
"---- ----\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ragged rows padded with empty cells",
|
||||||
|
input: "a,b,c\n1\n2,3\n",
|
||||||
|
want: "a b c\n" +
|
||||||
|
"- - -\n" +
|
||||||
|
"1\n" +
|
||||||
|
"2 3\n" +
|
||||||
|
"- - -\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "header only",
|
||||||
|
input: "one,two\n",
|
||||||
|
want: "one two\n" +
|
||||||
|
"--- ---\n" +
|
||||||
|
"--- ---\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty input unchanged",
|
||||||
|
input: "",
|
||||||
|
want: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid CSV returns error",
|
||||||
|
input: "a,\"b\nc",
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
resp, err := filter(context.Background(), tt.input)
|
||||||
|
if tt.wantErr {
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("expected error, got output %q", resp.Output)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if resp.Output != tt.want {
|
||||||
|
t.Errorf("output mismatch\ngot:\n%s\nwant:\n%s", resp.Output, tt.want)
|
||||||
|
}
|
||||||
|
if resp.Append {
|
||||||
|
t.Errorf("Append should be false")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue