-
Notifications
You must be signed in to change notification settings - Fork 1
/
root.go
139 lines (112 loc) · 3.31 KB
/
root.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
package main
import (
"bytes"
"fmt"
"io"
"os"
"text/tabwriter"
"github.com/patrickdappollonio/tabloid/tabloid"
"github.com/spf13/cobra"
)
var version = "development"
const (
helpShort = "tabloid is a simple command line tool to parse and filter column-based CLI outputs from commands like kubectl or docker"
helpLong = `tabloid is a simple command line tool to parse and filter column-based CLI outputs from commands like kubectl or docker.
For documentation, see https://github.com/patrickdappollonio/tabloid`
)
var examples = []string{
`kubectl api-resources | tabloid --expr 'kind == "Namespace"'`,
`kubectl api-resources | tabloid --expr 'apiversion =~ "networking"'`,
`kubectl api-resources | tabloid --expr 'shortnames == "sa"' --column name,shortnames`,
`kubectl get pods --all-namespaces | tabloid --expr 'name =~ "^frontend" || name =~ "redis$"'`,
}
type settings struct {
expr string
columns []string
debug bool
noTitles bool
titlesOnly bool
titlesNormalized bool
}
func rootCommand(r io.Reader) *cobra.Command {
var opts settings
cmd := &cobra.Command{
Use: "tabloid",
Short: helpShort,
Long: helpLong,
SilenceUsage: true,
SilenceErrors: true,
Version: version,
Example: sliceToTabulated(examples),
RunE: func(cmd *cobra.Command, args []string) error {
return run(r, os.Stdout, opts)
},
}
cmd.Flags().StringVarP(&opts.expr, "expr", "e", "", "expression to filter the output")
cmd.Flags().StringSliceVarP(&opts.columns, "column", "c", []string{}, "columns to display")
cmd.Flags().BoolVar(&opts.debug, "debug", false, "enable debug mode")
cmd.Flags().BoolVar(&opts.noTitles, "no-titles", false, "remove column titles from the output")
cmd.Flags().BoolVar(&opts.titlesOnly, "titles-only", false, "only display column titles")
cmd.Flags().BoolVar(&opts.titlesNormalized, "titles-normalized", false, "normalize column titles")
return cmd
}
func run(r io.Reader, w io.Writer, opts settings) error {
var b bytes.Buffer
if _, err := io.Copy(&b, r); err != nil {
return err
}
tab := tabloid.New(&b)
tab.EnableDebug(opts.debug)
cols, err := tab.ParseColumns()
if err != nil {
return err
}
if opts.titlesOnly {
if opts.expr != "" {
return fmt.Errorf("cannot use --expr with --titles-only")
}
if len(opts.columns) > 0 {
return fmt.Errorf("cannot use --column with --titles-only")
}
for _, v := range cols {
if opts.titlesNormalized {
fmt.Fprintln(w, v.ExprTitle)
continue
}
fmt.Fprintln(w, v.Title)
}
return nil
}
filtered, err := tab.Filter(cols, opts.expr)
if err != nil {
return err
}
output, err := tab.Select(filtered, opts.columns)
if err != nil {
return err
}
if len(output) == 0 {
return fmt.Errorf("input had no columns to handle")
}
t := tabwriter.NewWriter(w, 0, 0, 3, ' ', 0)
if !opts.noTitles {
for _, v := range output {
if opts.titlesNormalized {
fmt.Fprintf(t, "%s\t", v.ExprTitle)
continue
}
fmt.Fprintf(t, "%s\t", v.Title)
}
fmt.Fprintln(t, "")
}
for i := 0; i < len(output[0].Values); i++ {
for _, v := range output {
fmt.Fprintf(t, "%s\t", v.Values[i])
}
fmt.Fprintln(t, "")
}
if err := t.Flush(); err != nil {
return fmt.Errorf("unable to flush table contents to screen: %w", err)
}
return nil
}