diff --git a/cmd/root.go b/cmd/root.go
index 4ab2b73..fd0446d 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -33,6 +33,13 @@ var rootCmd = &cobra.Command{
return nil
}
+ lib.PrepareColumns()
+
+ err := lib.PrepareModeFlags()
+ if err != nil {
+ return err
+ }
+
return lib.ProcessFiles(args)
},
}
@@ -46,9 +53,17 @@ func Execute() {
func init() {
rootCmd.PersistentFlags().BoolVarP(&lib.Debug, "debug", "d", false, "Enable debugging")
- rootCmd.PersistentFlags().BoolVarP(&lib.XtendedOut, "extended", "x", false, "Enable extended output")
rootCmd.PersistentFlags().BoolVarP(&lib.NoNumbering, "no-numbering", "n", false, "Disable header numbering")
rootCmd.PersistentFlags().BoolVarP(&lib.ShowVersion, "version", "v", false, "Print program version")
rootCmd.PersistentFlags().StringVarP(&lib.Separator, "separator", "s", "", "Custom field separator")
rootCmd.PersistentFlags().StringVarP(&lib.Columns, "columns", "c", "", "Only show the speficied columns (separated by ,)")
+
+ // output flags, only 1 allowed
+ rootCmd.PersistentFlags().BoolVarP(&lib.OutflagExtended, "extended", "X", false, "Enable extended output")
+ rootCmd.PersistentFlags().BoolVarP(&lib.OutflagMarkdown, "markdown", "M", false, "Enable markdown table output")
+ rootCmd.PersistentFlags().BoolVarP(&lib.OutflagOrgtable, "orgtbl", "O", false, "Enable org-mode table output")
+ rootCmd.MarkFlagsMutuallyExclusive("extended", "markdown", "orgtbl")
+
+ // same thing but more common, takes precedence over above group
+ rootCmd.PersistentFlags().StringVarP(&lib.OutputMode, "output", "o", "", "Output mode - one of: orgtbl, markdown, extended, ascii(default)")
}
diff --git a/go.mod b/go.mod
index 1e8b3fa..38b9a5b 100644
--- a/go.mod
+++ b/go.mod
@@ -4,11 +4,13 @@ go 1.18
require (
github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897
+ github.com/olekukonko/tablewriter v0.0.5
github.com/spf13/cobra v1.5.0
)
require (
github.com/inconshreveable/mousetrap v1.0.0 // indirect
+ github.com/mattn/go-runewidth v0.0.9 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/stretchr/testify v1.8.0 // indirect
)
diff --git a/go.sum b/go.sum
index 2e9690d..6ec3e3c 100644
--- a/go.sum
+++ b/go.sum
@@ -6,6 +6,10 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
+github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
+github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
+github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
+github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
diff --git a/lib/common.go b/lib/common.go
index 980d54f..104d452 100644
--- a/lib/common.go
+++ b/lib/common.go
@@ -17,6 +17,7 @@ along with this program. If not, see .
package lib
+// command line flags
var Debug bool
var XtendedOut bool
var NoNumbering bool
@@ -24,5 +25,10 @@ var ShowVersion bool
var Columns string
var UseColumns []int
var Separator string
+var OutflagExtended bool
+var OutflagMarkdown bool
+var OutflagOrgtable bool
+var OutputMode string
var Version = "v1.0.2"
+var validOutputmodes = "(orgtbl|markdown|extended|ascii)"
diff --git a/lib/helpers.go b/lib/helpers.go
index 08ab0de..5dab33f 100644
--- a/lib/helpers.go
+++ b/lib/helpers.go
@@ -18,8 +18,10 @@ along with this program. If not, see .
package lib
import (
+ "errors"
"fmt"
"os"
+ "regexp"
"strconv"
"strings"
)
@@ -38,7 +40,7 @@ func contains(s []int, e int) bool {
return false
}
-func prepareColumns() {
+func PrepareColumns() {
if len(Columns) > 0 {
for _, use := range strings.Split(Columns, ",") {
usenum, err := strconv.Atoi(use)
@@ -49,3 +51,32 @@ func prepareColumns() {
}
}
}
+
+func PrepareModeFlags() error {
+ if len(OutputMode) == 0 {
+ switch {
+ case OutflagExtended:
+ OutputMode = "extended"
+ case OutflagMarkdown:
+ OutputMode = "markdown"
+ case OutflagOrgtable:
+ OutputMode = "orgtbl"
+ default:
+ OutputMode = "ascii"
+ }
+ } else {
+ r, err := regexp.Compile(validOutputmodes)
+
+ if err != nil {
+ return errors.New("Failed to validate output mode spec!")
+ }
+
+ match := r.MatchString(OutputMode)
+
+ if !match {
+ return errors.New("Invalid output mode!")
+ }
+ }
+
+ return nil
+}
diff --git a/lib/io.go b/lib/io.go
index 1bb5caf..3665dbf 100644
--- a/lib/io.go
+++ b/lib/io.go
@@ -27,7 +27,7 @@ func ProcessFiles(args []string) error {
var pattern string
havefiles := false
- prepareColumns()
+ //prepareColumns()
if len(args) > 0 {
if _, err := os.Stat(args[0]); err != nil {
diff --git a/lib/parser.go b/lib/parser.go
index 24fac74..eadac31 100644
--- a/lib/parser.go
+++ b/lib/parser.go
@@ -144,7 +144,7 @@ func parseFile(input io.Reader, pattern string) Tabdata {
// if Debug {
// fmt.Printf("<%s> ", value)
// }
- values = append(values, value)
+ values = append(values, strings.TrimSpace(value))
idx++
}
diff --git a/lib/printer.go b/lib/printer.go
index 2f1233b..2f6a056 100644
--- a/lib/printer.go
+++ b/lib/printer.go
@@ -19,78 +19,146 @@ package lib
import (
"fmt"
+ "github.com/olekukonko/tablewriter"
+ "os"
+ "regexp"
"strings"
)
func printData(data Tabdata) {
- if XtendedOut {
- printExtendedData(data)
- } else {
- printTabularData(data)
- }
-}
-
-func printTabularData(data Tabdata) {
- // needed for data output
- var formats []string
-
- if len(data.entries) > 0 {
- // headers
+ // prepare headers
+ // FIXME: maybe do this already in parseFile()?
+ if !NoNumbering {
+ numberedHeaders := []string{}
for i, head := range data.headers {
if len(Columns) > 0 {
if !contains(UseColumns, i+1) {
continue
}
}
-
- // calculate column width
- var width int
- var iwidth int
- var format string
-
- // generate format string
- if len(head) > data.maxwidthPerCol[i] {
- width = len(head)
- } else {
- width = data.maxwidthPerCol[i]
- }
-
- if NoNumbering {
- iwidth = 0
- } else {
- iwidth = len(fmt.Sprintf("%d", i)) // in case i > 9
- }
-
- format = fmt.Sprintf("%%-%ds", 3+iwidth+width)
-
- if NoNumbering {
- fmt.Printf(format, fmt.Sprintf("%s ", head))
- } else {
- fmt.Printf(format, fmt.Sprintf("%s(%d) ", head, i+1))
- }
-
- // register
- formats = append(formats, format)
- }
- fmt.Println()
-
- // entries
- var idx int
- for _, entry := range data.entries {
- idx = 0
- //fmt.Println(entry)
- for i, value := range entry {
- if len(Columns) > 0 {
- if !contains(UseColumns, i+1) {
- continue
- }
- }
- fmt.Printf(formats[idx], strings.TrimSpace(value))
- idx++
- }
- fmt.Println()
+ numberedHeaders = append(numberedHeaders, fmt.Sprintf("%s(%d)", head, i+1))
}
+ data.headers = numberedHeaders
}
+
+ // prepare data
+ if len(Columns) > 0 {
+ reducedEntries := [][]string{}
+ reducedEntry := []string{}
+ for _, entry := range data.entries {
+ reducedEntry = nil
+ for i, value := range entry {
+ if !contains(UseColumns, i+1) {
+ continue
+ }
+
+ reducedEntry = append(reducedEntry, value)
+ }
+ reducedEntries = append(reducedEntries, reducedEntry)
+ }
+ data.entries = reducedEntries
+ }
+
+ switch OutputMode {
+ case "extended":
+ printExtendedData(data)
+ case "ascii":
+ printAsciiData(data)
+ case "orgtbl":
+ printOrgmodeData(data)
+ case "markdown":
+ printMarkdownData(data)
+ default:
+ printAsciiData(data)
+ }
+}
+
+func trimRow(row []string) []string {
+ // FIXME: remove this when we only use Tablewriter and strip in ParseFile()!
+ var fixedrow []string
+ for _, cell := range row {
+ fixedrow = append(fixedrow, strings.TrimSpace(cell))
+ }
+
+ return fixedrow
+}
+
+/*
+ Emacs org-mode compatible table (also orgtbl-mode)
+*/
+func printOrgmodeData(data Tabdata) {
+ tableString := &strings.Builder{}
+ table := tablewriter.NewWriter(tableString)
+
+ table.SetHeader(data.headers)
+
+ for _, row := range data.entries {
+ table.Append(trimRow(row))
+ }
+
+ table.Render()
+
+ /* fix output for org-mode (orgtbl)
+ tableWriter output:
+ +------+------+
+ | cell | cell |
+ +------+------+
+
+ Needed for org-mode compatibility:
+ |------+------|
+ | cell | cell |
+ |------+------|
+ */
+ leftR := regexp.MustCompile("(?m)^\\+")
+ rightR := regexp.MustCompile("\\+(?m)$")
+
+ fmt.Print(rightR.ReplaceAllString(leftR.ReplaceAllString(tableString.String(), "|"), "|"))
+}
+
+/*
+ Markdown table
+*/
+func printMarkdownData(data Tabdata) {
+ table := tablewriter.NewWriter(os.Stdout)
+
+ table.SetHeader(data.headers)
+
+ for _, row := range data.entries {
+ table.Append(trimRow(row))
+ }
+
+ table.SetBorders(tablewriter.Border{Left: true, Top: false, Right: true, Bottom: false})
+ table.SetCenterSeparator("|")
+
+ table.Render()
+}
+
+/*
+ Simple ASCII table without any borders etc, just like the input we expect
+*/
+func printAsciiData(data Tabdata) {
+ table := tablewriter.NewWriter(os.Stdout)
+
+ table.SetHeader(data.headers)
+ table.AppendBulk(data.entries)
+
+ // for _, row := range data.entries {
+ // table.Append(trimRow(row))
+ // }
+
+ table.SetAutoWrapText(false)
+ table.SetAutoFormatHeaders(true)
+ table.SetHeaderAlignment(tablewriter.ALIGN_LEFT)
+ table.SetAlignment(tablewriter.ALIGN_LEFT)
+ table.SetCenterSeparator("")
+ table.SetColumnSeparator("")
+ table.SetRowSeparator("")
+ table.SetHeaderLine(false)
+ table.SetBorder(false)
+ table.SetTablePadding("\t") // pad with tabs
+ table.SetNoWhiteSpace(true)
+
+ table.Render()
}
/*
diff --git a/tablizer.pod b/tablizer.pod
index b837422..bb11ee0 100644
--- a/tablizer.pod
+++ b/tablizer.pod
@@ -10,9 +10,12 @@ tablizer - Manipulate tabular output of other programs
Flags:
-c, --columns string Only show the speficied columns (separated by ,)
-d, --debug Enable debugging
- -x, --extended Enable extended output
+ -X, --extended Enable extended output
-h, --help help for tablizer
+ -M, --markdown Enable markdown table output
-n, --no-numbering Disable header numbering
+ -O, --orgtbl Enable org-mode table output
+ -o, --output string Output mode - one of: orgtbl, markdown, extended, ascii(default)
-s, --separator string Custom field separator
-v, --version Print program version
@@ -70,12 +73,12 @@ The numbering can be suppressed by using the B<-n> option.
There might be cases when the tabular output of a program is way too
large for your current terminal but you still need to see every
-column. In such cases the B<-x> option can be usefull which enables
-I. In this mode, each row will be printed vertically,
-header left, value right, aligned by the field widths. Here's an
-example:
+column. In such cases the B<-X> option (or B<-o extended> can be
+usefull which enables I. In this mode, each row will be
+printed vertically, header left, value right, aligned by the field
+widths. Here's an example:
- kubectl get pods | ./tablizer -x
+ kubectl get pods | ./tablizer -X
NAME: repldepl-7bcd8d5b64-7zq4l
READY: 1/1
STATUS: Running
@@ -85,6 +88,10 @@ example:
You can of course still use a regex to reduce the number of rows
displayed.
+Beside normal ascii mode (the default) and extended mode there more
+output modes available: B which prints an Emacs org-mode table
+and B which prints a Markdown table.
+
Finally the B<-d> option enables debugging output which is mostly
usefull for the developer.