mirror of
https://codeberg.org/scip/tablizer.git
synced 2025-12-17 04:30:56 +01:00
added CSV output mode, enhanced parser tests
This commit is contained in:
@@ -20,7 +20,6 @@ package lib
|
||||
// contains a whole parsed table
|
||||
type Tabdata struct {
|
||||
maxwidthHeader int // longest header
|
||||
maxwidthPerCol []int // max width per column
|
||||
columns int // count
|
||||
headers []string // [ "ID", "NAME", ...]
|
||||
entries [][]string
|
||||
|
||||
@@ -48,12 +48,7 @@ func TestContains(t *testing.T) {
|
||||
func TestPrepareColumns(t *testing.T) {
|
||||
data := Tabdata{
|
||||
maxwidthHeader: 5,
|
||||
maxwidthPerCol: []int{
|
||||
5,
|
||||
5,
|
||||
8,
|
||||
},
|
||||
columns: 3,
|
||||
columns: 3,
|
||||
headers: []string{
|
||||
"ONE", "TWO", "THREE",
|
||||
},
|
||||
|
||||
@@ -44,30 +44,36 @@ func parseCSV(c cfg.Config, input io.Reader, pattern string) (Tabdata, error) {
|
||||
if len(pattern) > 0 {
|
||||
scanner := bufio.NewScanner(input)
|
||||
lines := []string{}
|
||||
hadFirst := false
|
||||
for scanner.Scan() {
|
||||
line := strings.TrimSpace(scanner.Text())
|
||||
if patternR.MatchString(line) == c.InvertMatch {
|
||||
// by default -v is false, so if a line does NOT
|
||||
// match the pattern, we will ignore it. However,
|
||||
// if the user specified -v, the matching is inverted,
|
||||
// so we ignore all lines, which DO match.
|
||||
continue
|
||||
if hadFirst {
|
||||
// don't match 1st line, it's the header
|
||||
if patternR.MatchString(line) == c.InvertMatch {
|
||||
// by default -v is false, so if a line does NOT
|
||||
// match the pattern, we will ignore it. However,
|
||||
// if the user specified -v, the matching is inverted,
|
||||
// so we ignore all lines, which DO match.
|
||||
continue
|
||||
}
|
||||
}
|
||||
lines = append(lines, line)
|
||||
hadFirst = true
|
||||
}
|
||||
content = strings.NewReader(strings.Join(lines, "\n"))
|
||||
}
|
||||
|
||||
fmt.Println(content)
|
||||
csvreader := csv.NewReader(content)
|
||||
csvreader.Comma = rune(c.Separator[0])
|
||||
|
||||
records, err := csvreader.ReadAll()
|
||||
if err != nil {
|
||||
return data, errors.Unwrap(fmt.Errorf("Could not parse CSV input: %w", pattern, err))
|
||||
return data, errors.Unwrap(fmt.Errorf("Could not parse CSV input: %w", err))
|
||||
}
|
||||
|
||||
if len(records) >= 1 {
|
||||
data.headers = records[0]
|
||||
data.columns = len(records)
|
||||
|
||||
for _, head := range data.headers {
|
||||
// register widest header field
|
||||
@@ -149,16 +155,6 @@ func parseFile(c cfg.Config, input io.Reader, pattern string) (Tabdata, error) {
|
||||
idx := 0 // we cannot use the header index, because we could exclude columns
|
||||
values := []string{}
|
||||
for _, part := range parts {
|
||||
width := len(strings.TrimSpace(part))
|
||||
|
||||
if len(data.maxwidthPerCol)-1 < idx {
|
||||
data.maxwidthPerCol = append(data.maxwidthPerCol, width)
|
||||
} else {
|
||||
if width > data.maxwidthPerCol[idx] {
|
||||
data.maxwidthPerCol[idx] = width
|
||||
}
|
||||
}
|
||||
|
||||
// if Debug {
|
||||
// fmt.Printf("<%s> ", value)
|
||||
// }
|
||||
|
||||
@@ -25,40 +25,58 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
var input = []struct {
|
||||
name string
|
||||
text string
|
||||
separator string
|
||||
}{
|
||||
{
|
||||
name: "tabular-data",
|
||||
separator: cfg.DefaultSeparator,
|
||||
text: `
|
||||
ONE TWO THREE
|
||||
asd igig cxxxncnc
|
||||
19191 EDD 1 X`,
|
||||
},
|
||||
{
|
||||
name: "csv-data",
|
||||
separator: ",",
|
||||
text: `
|
||||
ONE,TWO,THREE
|
||||
asd,igig,cxxxncnc
|
||||
19191,"EDD 1",X`,
|
||||
},
|
||||
}
|
||||
|
||||
func TestParser(t *testing.T) {
|
||||
data := Tabdata{
|
||||
maxwidthHeader: 5,
|
||||
maxwidthPerCol: []int{
|
||||
5, 5, 8,
|
||||
},
|
||||
columns: 3,
|
||||
columns: 3,
|
||||
headers: []string{
|
||||
"ONE", "TWO", "THREE",
|
||||
},
|
||||
entries: [][]string{
|
||||
{
|
||||
"asd", "igig", "cxxxncnc",
|
||||
},
|
||||
{
|
||||
"19191", "EDD 1", "X",
|
||||
},
|
||||
{"asd", "igig", "cxxxncnc"},
|
||||
{"19191", "EDD 1", "X"},
|
||||
},
|
||||
}
|
||||
|
||||
table := `ONE TWO THREE
|
||||
asd igig cxxxncnc
|
||||
19191 EDD 1 X`
|
||||
for _, in := range input {
|
||||
testname := fmt.Sprintf("parse-%s", in.name)
|
||||
t.Run(testname, func(t *testing.T) {
|
||||
readFd := strings.NewReader(strings.TrimSpace(in.text))
|
||||
c := cfg.Config{Separator: in.separator}
|
||||
gotdata, err := parseFile(c, readFd, "")
|
||||
|
||||
readFd := strings.NewReader(table)
|
||||
c := cfg.Config{Separator: cfg.DefaultSeparator}
|
||||
gotdata, err := parseFile(c, readFd, "")
|
||||
if err != nil {
|
||||
t.Errorf("Parser returned error: %s\nData processed so far: %+v", err, gotdata)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Parser returned error: %s\nData processed so far: %+v", err, gotdata)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(data, gotdata) {
|
||||
t.Errorf("Parser returned invalid data, Regex: %s\nExp: %+v\nGot: %+v\n", c.Separator, data, gotdata)
|
||||
if !reflect.DeepEqual(data, gotdata) {
|
||||
t.Errorf("Parser returned invalid data\nExp: %+v\nGot: %+v\n",
|
||||
data, gotdata)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,84 +89,73 @@ func TestParserPatternmatching(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
entries: [][]string{
|
||||
{
|
||||
"asd", "igig", "cxxxncnc",
|
||||
},
|
||||
{"asd", "igig", "cxxxncnc"},
|
||||
},
|
||||
pattern: "ig",
|
||||
invert: false,
|
||||
},
|
||||
{
|
||||
entries: [][]string{
|
||||
{
|
||||
"19191", "EDD 1", "X",
|
||||
},
|
||||
{"19191", "EDD 1", "X"},
|
||||
},
|
||||
pattern: "ig",
|
||||
invert: true,
|
||||
},
|
||||
{
|
||||
entries: [][]string{
|
||||
{
|
||||
"asd", "igig", "cxxxncnc",
|
||||
},
|
||||
{"asd", "igig", "cxxxncnc"},
|
||||
},
|
||||
pattern: "[a-z",
|
||||
want: true,
|
||||
},
|
||||
}
|
||||
|
||||
table := `ONE TWO THREE
|
||||
asd igig cxxxncnc
|
||||
19191 EDD 1 X`
|
||||
for _, in := range input {
|
||||
for _, tt := range tests {
|
||||
testname := fmt.Sprintf("parse-%s-with-pattern-%s-inverted-%t",
|
||||
in.name, tt.pattern, tt.invert)
|
||||
t.Run(testname, func(t *testing.T) {
|
||||
c := cfg.Config{InvertMatch: tt.invert, Pattern: tt.pattern,
|
||||
Separator: in.separator}
|
||||
|
||||
for _, tt := range tests {
|
||||
testname := fmt.Sprintf("parse-with-pattern-%s-inverted-%t", tt.pattern, tt.invert)
|
||||
t.Run(testname, func(t *testing.T) {
|
||||
c := cfg.Config{InvertMatch: tt.invert, Pattern: tt.pattern, Separator: cfg.DefaultSeparator}
|
||||
readFd := strings.NewReader(strings.TrimSpace(in.text))
|
||||
gotdata, err := parseFile(c, readFd, tt.pattern)
|
||||
|
||||
readFd := strings.NewReader(table)
|
||||
gotdata, err := parseFile(c, readFd, tt.pattern)
|
||||
|
||||
if err != nil {
|
||||
if !tt.want {
|
||||
t.Errorf("Parser returned error: %s\nData processed so far: %+v", err, gotdata)
|
||||
if err != nil {
|
||||
if !tt.want {
|
||||
t.Errorf("Parser returned error: %s\nData processed so far: %+v",
|
||||
err, gotdata)
|
||||
}
|
||||
} else {
|
||||
if !reflect.DeepEqual(tt.entries, gotdata.entries) {
|
||||
t.Errorf("Parser returned invalid data (pattern: %s, invert: %t)\nExp: %+v\nGot: %+v\n",
|
||||
tt.pattern, tt.invert, tt.entries, gotdata.entries)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if !reflect.DeepEqual(tt.entries, gotdata.entries) {
|
||||
t.Errorf("Parser returned invalid data (pattern: %s, invert: %t)\nExp: %+v\nGot: %+v\n",
|
||||
tt.pattern, tt.invert, tt.entries, gotdata.entries)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParserIncompleteRows(t *testing.T) {
|
||||
data := Tabdata{
|
||||
maxwidthHeader: 5,
|
||||
maxwidthPerCol: []int{
|
||||
5, 5, 1,
|
||||
},
|
||||
columns: 3,
|
||||
columns: 3,
|
||||
headers: []string{
|
||||
"ONE", "TWO", "THREE",
|
||||
},
|
||||
entries: [][]string{
|
||||
{
|
||||
"asd", "igig", "",
|
||||
},
|
||||
{
|
||||
"19191", "EDD 1", "X",
|
||||
},
|
||||
{"asd", "igig", ""},
|
||||
{"19191", "EDD 1", "X"},
|
||||
},
|
||||
}
|
||||
|
||||
table := `ONE TWO THREE
|
||||
table := `
|
||||
ONE TWO THREE
|
||||
asd igig
|
||||
19191 EDD 1 X`
|
||||
|
||||
readFd := strings.NewReader(table)
|
||||
readFd := strings.NewReader(strings.TrimSpace(table))
|
||||
c := cfg.Config{Separator: cfg.DefaultSeparator}
|
||||
gotdata, err := parseFile(c, readFd, "")
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
package lib
|
||||
|
||||
import (
|
||||
"encoding/csv"
|
||||
"fmt"
|
||||
"github.com/gookit/color"
|
||||
"github.com/olekukonko/tablewriter"
|
||||
@@ -55,6 +56,8 @@ func printData(w io.Writer, c cfg.Config, data *Tabdata) {
|
||||
printShellData(w, c, data)
|
||||
case cfg.Yaml:
|
||||
printYamlData(w, c, data)
|
||||
case cfg.CSV:
|
||||
printCSVData(w, c, data)
|
||||
default:
|
||||
printAsciiData(w, c, data)
|
||||
}
|
||||
@@ -186,7 +189,7 @@ func printShellData(w io.Writer, c cfg.Config, data *Tabdata) {
|
||||
}
|
||||
}
|
||||
|
||||
// no colrization here
|
||||
// no colorization here
|
||||
output(w, out)
|
||||
}
|
||||
|
||||
@@ -225,3 +228,23 @@ func printYamlData(w io.Writer, c cfg.Config, data *Tabdata) {
|
||||
|
||||
output(w, string(yamlstr))
|
||||
}
|
||||
|
||||
func printCSVData(w io.Writer, c cfg.Config, data *Tabdata) {
|
||||
csvout := csv.NewWriter(w)
|
||||
|
||||
if err := csvout.Write(data.headers); err != nil {
|
||||
log.Fatalln("error writing record to csv:", err)
|
||||
}
|
||||
|
||||
for _, entry := range data.entries {
|
||||
if err := csvout.Write(entry); err != nil {
|
||||
log.Fatalln("error writing record to csv:", err)
|
||||
}
|
||||
}
|
||||
|
||||
csvout.Flush()
|
||||
|
||||
if err := csvout.Error(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,13 +29,7 @@ import (
|
||||
func newData() Tabdata {
|
||||
return Tabdata{
|
||||
maxwidthHeader: 8,
|
||||
maxwidthPerCol: []int{
|
||||
5,
|
||||
9,
|
||||
3,
|
||||
26,
|
||||
},
|
||||
columns: 4,
|
||||
columns: 4,
|
||||
headers: []string{
|
||||
"NAME",
|
||||
"DURATION",
|
||||
@@ -86,6 +80,15 @@ NAME(1) DURATION(2) COUNT(3) WHEN(4)
|
||||
beta 1d10h5m1s 33 3/1/2014
|
||||
alpha 4h35m 170 2013-Feb-03
|
||||
ceta 33d12h 9 06/Jan/2008 15:04:05 -0700`,
|
||||
},
|
||||
{
|
||||
mode: cfg.CSV,
|
||||
name: "csv",
|
||||
expect: `
|
||||
NAME,DURATION,COUNT,WHEN
|
||||
beta,1d10h5m1s,33,3/1/2014
|
||||
alpha,4h35m,170,2013-Feb-03
|
||||
ceta,33d12h,9,06/Jan/2008 15:04:05 -0700`,
|
||||
},
|
||||
{
|
||||
name: "default",
|
||||
@@ -265,6 +268,8 @@ func TestPrinter(t *testing.T) {
|
||||
NoColor: true,
|
||||
}
|
||||
|
||||
c.ApplyDefaults()
|
||||
|
||||
// the test checks the len!
|
||||
if len(tt.usecol) > 0 {
|
||||
c.Columns = "yes"
|
||||
|
||||
Reference in New Issue
Block a user