Skip to content

Commit 249100d

Browse files
authored
Add -format flag and -format csv (#5)
1 parent 79c0353 commit 249100d

14 files changed

+450
-229
lines changed

README.md

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,22 @@ $ netstat -i | head -5 | mktable -s ' +'
3030

3131
## Flags
3232

33-
| Flag | Default | Description |
34-
| ------------------ | ---------------- | ----------------------------------------------- |
35-
| `-s` | `[ \t]*\t[ \t]*` | Regexp used to set the delimiter |
36-
| `-no-headers` | `false` | Skip printing headers |
37-
| `-r` / `-reformat` | `false` | Reformat existing markdown table |
38-
| `-a` | none | Sets the alignment. See [Alignment](#alignment) |
33+
| Flag | Default | Description |
34+
| ------------------ | ---------------- | -------------------------------------------------------------- |
35+
| `-s` | `[ \t]*\t[ \t]*` | Regexp used to set the delimiter |
36+
| `-no-headers` | `false` | Skip printing headers |
37+
| `-r` / `-reformat` | `false` | Reformat existing markdown table. Alias for `-format mk` |
38+
| `-a` | none | Sets the alignment. See [Alignment](#alignment) |
39+
| `-f` / `-format` | `regexp` | Sets the input format. See [Formats](#format) for more details |
40+
41+
## Formats
42+
43+
| Format | Description |
44+
| ----------------- | --------------------------------------------------- |
45+
| `re` / `regexp` | Regular Expression Delimiter |
46+
| `csv` | Comma Separated List |
47+
| `mk` / `markdown` | Consume an existing markdown table for reformatting |
48+
3949

4050
## Alignment
4151

formatvalue.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package main
2+
3+
import "github.com/keyneston/mktable/table"
4+
5+
type FormatValue struct {
6+
format table.Format
7+
}
8+
9+
func (fv *FormatValue) Get() interface{} {
10+
return fv.GetFormat()
11+
}
12+
13+
func (fv *FormatValue) GetFormat() table.Format {
14+
if fv.format == "" {
15+
return table.FormatRE
16+
}
17+
18+
return fv.format
19+
}
20+
21+
func (fv *FormatValue) String() string {
22+
return string(fv.format)
23+
}
24+
25+
func (fv *FormatValue) Set(in string) error {
26+
f, err := table.ParseFormat(in)
27+
if err != nil {
28+
return err
29+
}
30+
31+
fv.format = f
32+
return nil
33+
}

main.go

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@ package main
22

33
import (
44
"flag"
5+
"fmt"
56
"log"
67
"os"
78
"regexp"
9+
"strings"
810

911
"github.com/keyneston/mktable/table"
1012
)
@@ -14,19 +16,24 @@ type Config struct {
1416
Seperator string
1517
MaxPadding int
1618
Reformat bool
19+
Format FormatValue
1720

1821
Alignments ParseAlignments
1922

2023
fset *flag.FlagSet
2124
}
2225

2326
func (c *Config) Register(f *flag.FlagSet) *Config {
27+
allFormats := strings.Join(table.AllFormats(), ",")
28+
2429
c.fset = f
2530
c.fset.BoolVar(&c.SkipHeaders, "no-header", false, "Skip Setting Headers")
2631
c.fset.StringVar(&c.Seperator, "s", `[ \t]*\t[ \t]*`, "Regexp of Delimiter to Build Table on")
2732
c.fset.IntVar(&c.MaxPadding, "max-padding", -1, "Maximum units of padding. Set to a negative number for unlimited")
2833
c.fset.BoolVar(&c.Reformat, "r", false, "Read in markdown table and reformat")
2934
c.fset.BoolVar(&c.Reformat, "reformat", false, "Alias for -r")
35+
c.fset.Var(&c.Format, "f", fmt.Sprintf("Set the format. Available formats: %v", allFormats))
36+
c.fset.Var(&c.Format, "format", "Alias for -f")
3037
c.fset.Var(&c.Alignments, "a", "Set column alignments; Can be called multiple times and/or comma separated. Arrow indicates direction '<' left, '>' right, '=' center; Columns are zero indexed; e.g. -a '0<,1>,2='")
3138

3239
return c
@@ -47,13 +54,18 @@ func main() {
4754
log.Fatalf("Error: %v", err)
4855
}
4956

50-
tb := table.NewTable(regexp.MustCompile(c.Seperator))
51-
52-
tb.MaxPadding = c.MaxPadding
53-
tb.SkipHeaders = c.SkipHeaders
54-
tb.Reformat = c.Reformat
55-
tb.Alignments = c.Alignments.alignments
57+
tableConfig := table.TableConfig{
58+
MaxPadding: c.MaxPadding,
59+
SkipHeaders: c.SkipHeaders,
60+
Alignments: c.Alignments.alignments,
61+
Seperator: regexp.MustCompile(c.Seperator),
62+
Format: c.Format.GetFormat(),
63+
}
64+
if c.Reformat {
65+
tableConfig.Format = table.FormatMK
66+
}
5667

68+
tb := table.NewTable(tableConfig)
5769
tb.Read(os.Stdin)
5870
tb.Write(os.Stdout)
5971
}

table/config.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package table
2+
3+
import "regexp"
4+
5+
type TableConfig struct {
6+
MaxPadding int
7+
SkipHeaders bool
8+
Format Format
9+
Alignments map[int]Alignment
10+
Seperator *regexp.Regexp
11+
NewLine NewLine
12+
}
13+
14+
func NewConfig() TableConfig {
15+
return TableConfig{}
16+
}
17+
18+
func (t TableConfig) SetSeperator(in string) TableConfig {
19+
t.Seperator = regexp.MustCompile(in)
20+
t.Format = FormatRE
21+
22+
return t
23+
}
24+
25+
func (t TableConfig) SetFormat(format Format) TableConfig {
26+
t.Format = format
27+
return t
28+
}

table/format.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package table
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
)
7+
8+
type Format string
9+
10+
const (
11+
FormatRE Format = "regexp"
12+
FormatCSV Format = "csv"
13+
FormatTSV Format = "tsv"
14+
FormatMK Format = "mk"
15+
FormatUnknown Format = "unknown"
16+
)
17+
18+
func AllFormats() []string {
19+
return []string{
20+
string(FormatRE),
21+
string(FormatMK),
22+
string(FormatCSV),
23+
}
24+
}
25+
26+
func ParseFormat(in string) (Format, error) {
27+
switch strings.ToLower(in) {
28+
case "re", "regexp", "regex":
29+
return FormatRE, nil
30+
case "csv":
31+
return FormatCSV, nil
32+
// case "tsv":
33+
// return FormatTSV, nil
34+
case "mk", "markdown":
35+
return FormatMK, nil
36+
default:
37+
return FormatUnknown, fmt.Errorf("Unknown format %q", in)
38+
}
39+
}

table/format_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
package table

table/read_csv.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package table
2+
3+
import (
4+
"encoding/csv"
5+
"io"
6+
)
7+
8+
func (t *Table) readFormatCSV(r io.Reader) error {
9+
reader := csv.NewReader(r)
10+
11+
var err error
12+
t.data, err = reader.ReadAll()
13+
return err
14+
}

table/read_csv_test.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package table
2+
3+
import (
4+
"bytes"
5+
"strings"
6+
"testing"
7+
8+
"github.com/go-test/deep"
9+
)
10+
11+
func TestReadFormatCSV(t *testing.T) {
12+
type testCase struct {
13+
name string
14+
input string
15+
expected [][]string
16+
}
17+
18+
cases := []testCase{
19+
{
20+
name: "basic", input: "a\nb\nc\n",
21+
expected: [][]string{{"a"}, {"b"}, {"c"}},
22+
},
23+
{
24+
name: "multi-column", input: "a,1\nb,2\nc,3\n",
25+
expected: [][]string{{"a", "1"}, {"b", "2"}, {"c", "3"}},
26+
},
27+
{
28+
name: "quotations", input: "a,\",\"\nb,2\nc,3\n",
29+
expected: [][]string{{"a", ","}, {"b", "2"}, {"c", "3"}},
30+
},
31+
}
32+
33+
for _, c := range cases {
34+
tb := NewTable(NewConfig().SetFormat(FormatCSV))
35+
if err := tb.Read(bytes.NewBufferString(c.input)); err != nil {
36+
t.Errorf("Error doing read: %v", err)
37+
continue
38+
}
39+
40+
if diff := deep.Equal(c.expected, tb.data); diff != nil {
41+
t.Errorf("Table.Read(%q) =\n%v", c.name, strings.Join(diff, "\n"))
42+
}
43+
}
44+
}

table/read_mk.go

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package table
2+
3+
import (
4+
"bufio"
5+
"bytes"
6+
"errors"
7+
"io"
8+
"strings"
9+
)
10+
11+
func (t *Table) readFormatMK(r io.Reader) error {
12+
scanner := bufio.NewScanner(r)
13+
scanner.Split(func(data []byte, atEOF bool) (advance int, token []byte, err error) {
14+
start := 0
15+
for i := range data {
16+
switch data[i] {
17+
case '\n':
18+
token := bytes.TrimSpace(data[start:i])
19+
if len(token) > 0 {
20+
return i, token, nil
21+
}
22+
23+
return i + 1, []byte{'\n'}, nil
24+
case '|':
25+
if i > 0 && data[i-1] == '\\' {
26+
continue
27+
}
28+
if i == 0 {
29+
start = i + 1
30+
continue
31+
}
32+
33+
token := bytes.TrimSpace(data[start:i])
34+
return i + 1, token, nil
35+
}
36+
}
37+
38+
return 0, nil, nil
39+
})
40+
41+
current := []string{}
42+
alignments := map[int]Alignment{}
43+
isHeader := false
44+
column := 0
45+
46+
for scanner.Scan() {
47+
token := scanner.Text()
48+
49+
if token == "\n" {
50+
if isHeader {
51+
t.Alignments = alignments
52+
} else {
53+
t.data = append(t.data, current)
54+
}
55+
56+
alignments = map[int]Alignment{}
57+
column = 0
58+
current = nil
59+
isHeader = false
60+
continue
61+
}
62+
63+
// Check if it is likely part of a header row, by removing all header
64+
// row chars and seeing if we have nothing left
65+
if len(strings.Trim(token, ":-")) == 0 {
66+
isHeader = true
67+
alignments[column] = parseAlignmentHeader(token)
68+
}
69+
70+
current = append(current, token)
71+
72+
column++
73+
}
74+
if err := scanner.Err(); err != nil && !errors.Is(err, io.EOF) {
75+
return err
76+
}
77+
78+
return nil
79+
}

table/read_mk_test.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package table
2+
3+
import (
4+
"bytes"
5+
"strings"
6+
"testing"
7+
8+
"github.com/go-test/deep"
9+
)
10+
11+
func TestReadFormatMK(t *testing.T) {
12+
type testCase struct {
13+
name string
14+
input string
15+
expected [][]string
16+
}
17+
18+
cases := []testCase{
19+
{
20+
name: "basic", input: "| a | b | c |\n",
21+
expected: [][]string{{"a", "b", "c"}},
22+
},
23+
{
24+
name: "multiline", input: "| a | b | c |\n| --- | ---| ---|\n|1 | 2|3|\n",
25+
expected: [][]string{{"a", "b", "c"}, {"1", "2", "3"}},
26+
},
27+
{
28+
name: "trailing space", input: "| a | \n| --- |\n|1 |\n",
29+
expected: [][]string{{"a"}, {"1"}},
30+
},
31+
{
32+
name: "header", input: "| --- | --- | --- |\n",
33+
expected: nil,
34+
},
35+
{
36+
name: "trailing pipe", input: "| a | b | \\| |\n",
37+
expected: [][]string{{"a", "b", `\|`}},
38+
},
39+
}
40+
41+
for _, c := range cases {
42+
tb := NewTable(TableConfig{
43+
Format: FormatMK,
44+
})
45+
if err := tb.Read(bytes.NewBufferString(c.input)); err != nil {
46+
t.Errorf("Error doing read: %v", err)
47+
continue
48+
}
49+
50+
if diff := deep.Equal(c.expected, tb.data); diff != nil {
51+
t.Errorf("Table.Read(%q) =\n%v", c.name, strings.Join(diff, "\n"))
52+
}
53+
}
54+
}

0 commit comments

Comments
 (0)