Skip to content

Commit

Permalink
Add -format flag and -format csv (#5)
Browse files Browse the repository at this point in the history
  • Loading branch information
keyneston authored Apr 29, 2021
1 parent 79c0353 commit 249100d
Show file tree
Hide file tree
Showing 14 changed files with 450 additions and 229 deletions.
22 changes: 16 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,22 @@ $ netstat -i | head -5 | mktable -s ' +'

## Flags

| Flag | Default | Description |
| ------------------ | ---------------- | ----------------------------------------------- |
| `-s` | `[ \t]*\t[ \t]*` | Regexp used to set the delimiter |
| `-no-headers` | `false` | Skip printing headers |
| `-r` / `-reformat` | `false` | Reformat existing markdown table |
| `-a` | none | Sets the alignment. See [Alignment](#alignment) |
| Flag | Default | Description |
| ------------------ | ---------------- | -------------------------------------------------------------- |
| `-s` | `[ \t]*\t[ \t]*` | Regexp used to set the delimiter |
| `-no-headers` | `false` | Skip printing headers |
| `-r` / `-reformat` | `false` | Reformat existing markdown table. Alias for `-format mk` |
| `-a` | none | Sets the alignment. See [Alignment](#alignment) |
| `-f` / `-format` | `regexp` | Sets the input format. See [Formats](#format) for more details |

## Formats

| Format | Description |
| ----------------- | --------------------------------------------------- |
| `re` / `regexp` | Regular Expression Delimiter |
| `csv` | Comma Separated List |
| `mk` / `markdown` | Consume an existing markdown table for reformatting |


## Alignment

Expand Down
33 changes: 33 additions & 0 deletions formatvalue.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package main

import "github.com/keyneston/mktable/table"

type FormatValue struct {
format table.Format
}

func (fv *FormatValue) Get() interface{} {
return fv.GetFormat()
}

func (fv *FormatValue) GetFormat() table.Format {
if fv.format == "" {
return table.FormatRE
}

return fv.format
}

func (fv *FormatValue) String() string {
return string(fv.format)
}

func (fv *FormatValue) Set(in string) error {
f, err := table.ParseFormat(in)
if err != nil {
return err
}

fv.format = f
return nil
}
24 changes: 18 additions & 6 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ package main

import (
"flag"
"fmt"
"log"
"os"
"regexp"
"strings"

"github.com/keyneston/mktable/table"
)
Expand All @@ -14,19 +16,24 @@ type Config struct {
Seperator string
MaxPadding int
Reformat bool
Format FormatValue

Alignments ParseAlignments

fset *flag.FlagSet
}

func (c *Config) Register(f *flag.FlagSet) *Config {
allFormats := strings.Join(table.AllFormats(), ",")

c.fset = f
c.fset.BoolVar(&c.SkipHeaders, "no-header", false, "Skip Setting Headers")
c.fset.StringVar(&c.Seperator, "s", `[ \t]*\t[ \t]*`, "Regexp of Delimiter to Build Table on")
c.fset.IntVar(&c.MaxPadding, "max-padding", -1, "Maximum units of padding. Set to a negative number for unlimited")
c.fset.BoolVar(&c.Reformat, "r", false, "Read in markdown table and reformat")
c.fset.BoolVar(&c.Reformat, "reformat", false, "Alias for -r")
c.fset.Var(&c.Format, "f", fmt.Sprintf("Set the format. Available formats: %v", allFormats))
c.fset.Var(&c.Format, "format", "Alias for -f")
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='")

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

tb := table.NewTable(regexp.MustCompile(c.Seperator))

tb.MaxPadding = c.MaxPadding
tb.SkipHeaders = c.SkipHeaders
tb.Reformat = c.Reformat
tb.Alignments = c.Alignments.alignments
tableConfig := table.TableConfig{
MaxPadding: c.MaxPadding,
SkipHeaders: c.SkipHeaders,
Alignments: c.Alignments.alignments,
Seperator: regexp.MustCompile(c.Seperator),
Format: c.Format.GetFormat(),
}
if c.Reformat {
tableConfig.Format = table.FormatMK
}

tb := table.NewTable(tableConfig)
tb.Read(os.Stdin)
tb.Write(os.Stdout)
}
28 changes: 28 additions & 0 deletions table/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package table

import "regexp"

type TableConfig struct {
MaxPadding int
SkipHeaders bool
Format Format
Alignments map[int]Alignment
Seperator *regexp.Regexp
NewLine NewLine
}

func NewConfig() TableConfig {
return TableConfig{}
}

func (t TableConfig) SetSeperator(in string) TableConfig {
t.Seperator = regexp.MustCompile(in)
t.Format = FormatRE

return t
}

func (t TableConfig) SetFormat(format Format) TableConfig {
t.Format = format
return t
}
39 changes: 39 additions & 0 deletions table/format.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package table

import (
"fmt"
"strings"
)

type Format string

const (
FormatRE Format = "regexp"
FormatCSV Format = "csv"
FormatTSV Format = "tsv"
FormatMK Format = "mk"
FormatUnknown Format = "unknown"
)

func AllFormats() []string {
return []string{
string(FormatRE),
string(FormatMK),
string(FormatCSV),
}
}

func ParseFormat(in string) (Format, error) {
switch strings.ToLower(in) {
case "re", "regexp", "regex":
return FormatRE, nil
case "csv":
return FormatCSV, nil
// case "tsv":
// return FormatTSV, nil
case "mk", "markdown":
return FormatMK, nil
default:
return FormatUnknown, fmt.Errorf("Unknown format %q", in)
}
}
1 change: 1 addition & 0 deletions table/format_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package table
14 changes: 14 additions & 0 deletions table/read_csv.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package table

import (
"encoding/csv"
"io"
)

func (t *Table) readFormatCSV(r io.Reader) error {
reader := csv.NewReader(r)

var err error
t.data, err = reader.ReadAll()
return err
}
44 changes: 44 additions & 0 deletions table/read_csv_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package table

import (
"bytes"
"strings"
"testing"

"github.com/go-test/deep"
)

func TestReadFormatCSV(t *testing.T) {
type testCase struct {
name string
input string
expected [][]string
}

cases := []testCase{
{
name: "basic", input: "a\nb\nc\n",
expected: [][]string{{"a"}, {"b"}, {"c"}},
},
{
name: "multi-column", input: "a,1\nb,2\nc,3\n",
expected: [][]string{{"a", "1"}, {"b", "2"}, {"c", "3"}},
},
{
name: "quotations", input: "a,\",\"\nb,2\nc,3\n",
expected: [][]string{{"a", ","}, {"b", "2"}, {"c", "3"}},
},
}

for _, c := range cases {
tb := NewTable(NewConfig().SetFormat(FormatCSV))
if err := tb.Read(bytes.NewBufferString(c.input)); err != nil {
t.Errorf("Error doing read: %v", err)
continue
}

if diff := deep.Equal(c.expected, tb.data); diff != nil {
t.Errorf("Table.Read(%q) =\n%v", c.name, strings.Join(diff, "\n"))
}
}
}
79 changes: 79 additions & 0 deletions table/read_mk.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package table

import (
"bufio"
"bytes"
"errors"
"io"
"strings"
)

func (t *Table) readFormatMK(r io.Reader) error {
scanner := bufio.NewScanner(r)
scanner.Split(func(data []byte, atEOF bool) (advance int, token []byte, err error) {
start := 0
for i := range data {
switch data[i] {
case '\n':
token := bytes.TrimSpace(data[start:i])
if len(token) > 0 {
return i, token, nil
}

return i + 1, []byte{'\n'}, nil
case '|':
if i > 0 && data[i-1] == '\\' {
continue
}
if i == 0 {
start = i + 1
continue
}

token := bytes.TrimSpace(data[start:i])
return i + 1, token, nil
}
}

return 0, nil, nil
})

current := []string{}
alignments := map[int]Alignment{}
isHeader := false
column := 0

for scanner.Scan() {
token := scanner.Text()

if token == "\n" {
if isHeader {
t.Alignments = alignments
} else {
t.data = append(t.data, current)
}

alignments = map[int]Alignment{}
column = 0
current = nil
isHeader = false
continue
}

// Check if it is likely part of a header row, by removing all header
// row chars and seeing if we have nothing left
if len(strings.Trim(token, ":-")) == 0 {
isHeader = true
alignments[column] = parseAlignmentHeader(token)
}

current = append(current, token)

column++
}
if err := scanner.Err(); err != nil && !errors.Is(err, io.EOF) {
return err
}

return nil
}
54 changes: 54 additions & 0 deletions table/read_mk_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package table

import (
"bytes"
"strings"
"testing"

"github.com/go-test/deep"
)

func TestReadFormatMK(t *testing.T) {
type testCase struct {
name string
input string
expected [][]string
}

cases := []testCase{
{
name: "basic", input: "| a | b | c |\n",
expected: [][]string{{"a", "b", "c"}},
},
{
name: "multiline", input: "| a | b | c |\n| --- | ---| ---|\n|1 | 2|3|\n",
expected: [][]string{{"a", "b", "c"}, {"1", "2", "3"}},
},
{
name: "trailing space", input: "| a | \n| --- |\n|1 |\n",
expected: [][]string{{"a"}, {"1"}},
},
{
name: "header", input: "| --- | --- | --- |\n",
expected: nil,
},
{
name: "trailing pipe", input: "| a | b | \\| |\n",
expected: [][]string{{"a", "b", `\|`}},
},
}

for _, c := range cases {
tb := NewTable(TableConfig{
Format: FormatMK,
})
if err := tb.Read(bytes.NewBufferString(c.input)); err != nil {
t.Errorf("Error doing read: %v", err)
continue
}

if diff := deep.Equal(c.expected, tb.data); diff != nil {
t.Errorf("Table.Read(%q) =\n%v", c.name, strings.Join(diff, "\n"))
}
}
}
Loading

0 comments on commit 249100d

Please sign in to comment.