refactored output printer, now using tablewriter

This commit is contained in:
2022-10-01 14:14:53 +02:00
parent 1b1b63caa3
commit 4ca3a56280
9 changed files with 203 additions and 70 deletions

View File

@@ -33,6 +33,13 @@ var rootCmd = &cobra.Command{
return nil return nil
} }
lib.PrepareColumns()
err := lib.PrepareModeFlags()
if err != nil {
return err
}
return lib.ProcessFiles(args) return lib.ProcessFiles(args)
}, },
} }
@@ -46,9 +53,17 @@ func Execute() {
func init() { func init() {
rootCmd.PersistentFlags().BoolVarP(&lib.Debug, "debug", "d", false, "Enable debugging") 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.NoNumbering, "no-numbering", "n", false, "Disable header numbering")
rootCmd.PersistentFlags().BoolVarP(&lib.ShowVersion, "version", "v", false, "Print program version") 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.Separator, "separator", "s", "", "Custom field separator")
rootCmd.PersistentFlags().StringVarP(&lib.Columns, "columns", "c", "", "Only show the speficied columns (separated by ,)") 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)")
} }

2
go.mod
View File

@@ -4,11 +4,13 @@ go 1.18
require ( require (
github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897 github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897
github.com/olekukonko/tablewriter v0.0.5
github.com/spf13/cobra v1.5.0 github.com/spf13/cobra v1.5.0
) )
require ( require (
github.com/inconshreveable/mousetrap v1.0.0 // indirect 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/spf13/pflag v1.0.5 // indirect
github.com/stretchr/testify v1.8.0 // indirect github.com/stretchr/testify v1.8.0 // indirect
) )

4
go.sum
View File

@@ -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/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 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 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= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=

View File

@@ -17,6 +17,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
package lib package lib
// command line flags
var Debug bool var Debug bool
var XtendedOut bool var XtendedOut bool
var NoNumbering bool var NoNumbering bool
@@ -24,5 +25,10 @@ var ShowVersion bool
var Columns string var Columns string
var UseColumns []int var UseColumns []int
var Separator string var Separator string
var OutflagExtended bool
var OutflagMarkdown bool
var OutflagOrgtable bool
var OutputMode string
var Version = "v1.0.2" var Version = "v1.0.2"
var validOutputmodes = "(orgtbl|markdown|extended|ascii)"

View File

@@ -18,8 +18,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
package lib package lib
import ( import (
"errors"
"fmt" "fmt"
"os" "os"
"regexp"
"strconv" "strconv"
"strings" "strings"
) )
@@ -38,7 +40,7 @@ func contains(s []int, e int) bool {
return false return false
} }
func prepareColumns() { func PrepareColumns() {
if len(Columns) > 0 { if len(Columns) > 0 {
for _, use := range strings.Split(Columns, ",") { for _, use := range strings.Split(Columns, ",") {
usenum, err := strconv.Atoi(use) 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
}

View File

@@ -27,7 +27,7 @@ func ProcessFiles(args []string) error {
var pattern string var pattern string
havefiles := false havefiles := false
prepareColumns() //prepareColumns()
if len(args) > 0 { if len(args) > 0 {
if _, err := os.Stat(args[0]); err != nil { if _, err := os.Stat(args[0]); err != nil {

View File

@@ -144,7 +144,7 @@ func parseFile(input io.Reader, pattern string) Tabdata {
// if Debug { // if Debug {
// fmt.Printf("<%s> ", value) // fmt.Printf("<%s> ", value)
// } // }
values = append(values, value) values = append(values, strings.TrimSpace(value))
idx++ idx++
} }

View File

@@ -19,78 +19,146 @@ package lib
import ( import (
"fmt" "fmt"
"github.com/olekukonko/tablewriter"
"os"
"regexp"
"strings" "strings"
) )
func printData(data Tabdata) { func printData(data Tabdata) {
if XtendedOut { // prepare headers
printExtendedData(data) // FIXME: maybe do this already in parseFile()?
} else { if !NoNumbering {
printTabularData(data) numberedHeaders := []string{}
}
}
func printTabularData(data Tabdata) {
// needed for data output
var formats []string
if len(data.entries) > 0 {
// headers
for i, head := range data.headers { for i, head := range data.headers {
if len(Columns) > 0 { if len(Columns) > 0 {
if !contains(UseColumns, i+1) { if !contains(UseColumns, i+1) {
continue continue
} }
} }
numberedHeaders = append(numberedHeaders, fmt.Sprintf("%s(%d)", head, i+1))
// calculate column width }
var width int data.headers = numberedHeaders
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 { // prepare data
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 len(Columns) > 0 {
reducedEntries := [][]string{}
reducedEntry := []string{}
for _, entry := range data.entries {
reducedEntry = nil
for i, value := range entry {
if !contains(UseColumns, i+1) { if !contains(UseColumns, i+1) {
continue continue
} }
reducedEntry = append(reducedEntry, value)
} }
fmt.Printf(formats[idx], strings.TrimSpace(value)) reducedEntries = append(reducedEntries, reducedEntry)
idx++
} }
fmt.Println() 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()
} }
/* /*

View File

@@ -10,9 +10,12 @@ tablizer - Manipulate tabular output of other programs
Flags: Flags:
-c, --columns string Only show the speficied columns (separated by ,) -c, --columns string Only show the speficied columns (separated by ,)
-d, --debug Enable debugging -d, --debug Enable debugging
-x, --extended Enable extended output -X, --extended Enable extended output
-h, --help help for tablizer -h, --help help for tablizer
-M, --markdown Enable markdown table output
-n, --no-numbering Disable header numbering -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 -s, --separator string Custom field separator
-v, --version Print program version -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 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 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 column. In such cases the B<-X> option (or B<-o extended> can be
I<extended mode>. In this mode, each row will be printed vertically, usefull which enables I<extended mode>. In this mode, each row will be
header left, value right, aligned by the field widths. Here's an printed vertically, header left, value right, aligned by the field
example: widths. Here's an example:
kubectl get pods | ./tablizer -x kubectl get pods | ./tablizer -X
NAME: repldepl-7bcd8d5b64-7zq4l NAME: repldepl-7bcd8d5b64-7zq4l
READY: 1/1 READY: 1/1
STATUS: Running STATUS: Running
@@ -85,6 +88,10 @@ example:
You can of course still use a regex to reduce the number of rows You can of course still use a regex to reduce the number of rows
displayed. displayed.
Beside normal ascii mode (the default) and extended mode there more
output modes available: B<orgtbl> which prints an Emacs org-mode table
and B<markdown> which prints a Markdown table.
Finally the B<-d> option enables debugging output which is mostly Finally the B<-d> option enables debugging output which is mostly
usefull for the developer. usefull for the developer.