diff --git a/README.md b/README.md index e20af03..0d79abe 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ import ( "github.com/InVisionApp/tabular" ) -var tab tabular.Columns +var tab tabular.Table func init() { tab = tabular.New() @@ -32,11 +32,9 @@ func init() { tab.Col("cls", "Cluster", 10) tab.Col("svc", "Service", 15) tab.Col("hst", "Database Host", 20) - tab.Col("pct", "%CPU", 7) - tab["pct"].RightJustified = true + tab.ColRJ("pct", "%CPU", 7) } -// Sample data-set var data = []struct { e, c, s, d string v float64 @@ -72,30 +70,24 @@ var data = []struct { } func main() { - // Print Environments and Clusters + // Print a subset of columns (Environments and Clusters) format := tab.Print("env", "cls") for _, x := range data { fmt.Printf(format, x.e, x.c) } - // Print Environments, Clusters and Services - format = tab.Print("env", "cls", "svc") + // Print All Columns + format = tab.Print("*") for _, x := range data { - fmt.Printf(format, x.e, x.c, x.s) + fmt.Printf(format, x.e, x.c, x.s, x.d, x.v) } - // Print Everything - format = tab.Print("cls", "svc", "hst", "pct") - for _, x := range data { - fmt.Printf(format, x.c, x.s, x.d, x.v) - } - - // Print Everything to a custom destination such as a log - table := tab.Parse("cls", "svc", "hst", "pct") + // Print All Columns to a custom destination such as a log + table := tab.Parse("*") log.Println(table.Header) log.Println(table.SubHeader) for _, x := range data { - log.Printf(table.Format, x.c, x.s, x.d, x.v) + log.Printf(table.Format, x.e, x.c, x.s, x.d, x.v) } } ``` @@ -110,24 +102,17 @@ production cluster-1 production cluster-2 production cluster-2 -Environment Cluster Service --------------- ---------- --------------- -production cluster-1 service-a -production cluster-1 service-b -production cluster-2 service-a -production cluster-2 service-b - -Cluster Service Database Host %CPU ----------- --------------- -------------------- ------- -cluster-1 service-a database-host-1 70.01 -cluster-1 service-b database-host-2 99.51 -cluster-2 service-a database-host-1 70.01 -cluster-2 service-b database-host-2 99.51 - -2018/04/26 10:17:27 Cluster Service Database Host %CPU -2018/04/26 10:17:27 ---------- --------------- -------------------- ------- -2018/04/26 10:17:27 cluster-1 service-a database-host-1 70.01 -2018/04/26 10:17:27 cluster-1 service-b database-host-2 99.51 -2018/04/26 10:17:27 cluster-2 service-a database-host-1 70.01 -2018/04/26 10:17:27 cluster-2 service-b database-host-2 99.51 +Environment Cluster Service Database Host %CPU +-------------- ---------- --------------- -------------------- ------- +production cluster-1 service-a database-host-1 70.01 +production cluster-1 service-b database-host-2 99.51 +production cluster-2 service-a database-host-1 70.01 +production cluster-2 service-b database-host-2 99.51 + +2018/05/14 11:19:41 Environment Cluster Service Database Host %CPU +2018/05/14 11:19:41 -------------- ---------- --------------- -------------------- ------- +2018/05/14 11:19:41 production cluster-1 service-a database-host-1 70.01 +2018/05/14 11:19:41 production cluster-1 service-b database-host-2 99.51 +2018/05/14 11:19:41 production cluster-2 service-a database-host-1 70.01 +2018/05/14 11:19:41 production cluster-2 service-b database-host-2 99.51 ``` \ No newline at end of file diff --git a/example/example.go b/example/example.go index 02d4001..b4b057c 100644 --- a/example/example.go +++ b/example/example.go @@ -7,7 +7,7 @@ import ( "github.com/InVisionApp/tabular" ) -var tab tabular.Columns +var tab tabular.Table func init() { tab = tabular.New() @@ -15,8 +15,7 @@ func init() { tab.Col("cls", "Cluster", 10) tab.Col("svc", "Service", 15) tab.Col("hst", "Database Host", 20) - tab.Col("pct", "%CPU", 7) - tab["pct"].RightJustified = true + tab.ColRJ("pct", "%CPU", 7) } var data = []struct { @@ -54,29 +53,23 @@ var data = []struct { } func main() { - // Print Environments and Clusters + // Print a subset of columns (Environments and Clusters) format := tab.Print("env", "cls") for _, x := range data { fmt.Printf(format, x.e, x.c) } - // Print Environments, Clusters and Services - format = tab.Print("env", "cls", "svc") + // Print All Columns + format = tab.Print("*") for _, x := range data { - fmt.Printf(format, x.e, x.c, x.s) + fmt.Printf(format, x.e, x.c, x.s, x.d, x.v) } - // Print Clusters, Services and Database Hosts - format = tab.Print("cls", "svc", "hst", "pct") - for _, x := range data { - fmt.Printf(format, x.c, x.s, x.d, x.v) - } - - // Print to a custom destination such as a log - table := tab.Parse("cls", "svc", "hst", "pct") + // Print All Columns to a custom destination such as a log + table := tab.Parse("*") log.Println(table.Header) log.Println(table.SubHeader) for _, x := range data { - log.Printf(table.Format, x.c, x.s, x.d, x.v) + log.Printf(table.Format, x.e, x.c, x.s, x.d, x.v) } } diff --git a/format_test.go b/format_test.go index fecb16d..56ee7f6 100644 --- a/format_test.go +++ b/format_test.go @@ -8,37 +8,41 @@ import ( func TestFormat(t *testing.T) { tab := tabular.New() - tab.Col("id", "ID", 6) + tab.ColRJ("id", "ID", 6) tab.Col("env", "Environment", 14) tab.Col("cls", "Cluster", 10) tab.Col("svc", "Service", 25) tab.Col("hst", "Database Host", 25) - tab.Col("pct", "%CPU", 5) - tab["id"].RightJustified = true - tab["pct"].RightJustified = true + tab.ColRJ("pct", "%CPU", 5) - tWant := tabular.Table{ + // Test Partial Printing + want := "%6v %-14v %-10v\n" + if got := tab.Print("id", "env", "cls"); got != want { + t.Errorf("ERROR: tab.Print() failed\n want: %q\n got: %q", want, got) + } + + tWant := tabular.Output{ Header: " ID Environment Cluster Service Database Host %CPU", SubHeader: "------ -------------- ---------- ------------------------- ------------------------- -----", Format: "%6v %-14v %-10v %-25v %-25v %5v\n", } - // Test Printing - want := tWant.Format - if got := tab.Print("id", "env", "cls", "svc", "hst", "pct"); got != want { - t.Errorf("ERROR: tab.Print() failed\n want: %q\n got: %q", want, got) + // Test Printing All + want = tWant.Format + if got := tab.Print(tabular.All); got != want { + t.Errorf("ERROR: tab.Print(All) failed\n want: %q\n got: %q", want, got) } // Test Parsing if tGot := tab.Parse("id", "env", "cls", "svc", "hst", "pct"); tGot != tWant { if tGot.Header != tWant.Header { - t.Errorf("ERROR: tab.Parse() failed\n want: %v\n got: %v", tWant.Header, tGot.Header) + t.Errorf("ERROR: tab.Parse() failed\n want: %q\n got: %q", tWant.Header, tGot.Header) } if tGot.SubHeader != tWant.SubHeader { - t.Errorf("ERROR: tab.Parse() failed\n want: %v\n got: %v", tWant.SubHeader, tGot.SubHeader) + t.Errorf("ERROR: tab.Parse() failed\n want: %q\n got: %q", tWant.SubHeader, tGot.SubHeader) } if tGot.Format != tWant.Format { - t.Errorf("ERROR: tab.Parse() failed\n want: %v\n got: %v", tWant.Format, tGot.Format) + t.Errorf("ERROR: tab.Parse() failed\n want: %q\n got: %q", tWant.Format, tGot.Format) } } } diff --git a/tabular.go b/tabular.go index b4f6e47..f6abd39 100644 --- a/tabular.go +++ b/tabular.go @@ -5,23 +5,19 @@ import ( "strings" ) -// Table - parsed table's header, subheader and format specifier -type Table struct { +// Output - parsed table's header, subheader and format specifier +type Output struct { Header string SubHeader string Format string } -// Columns - maps short names of columns to their structure defining: -// full name, length and whether it's right justified -// -// For Example: -// "env": Column{Name: "Environment", Length: 14}, -// "cls": Column{Name: "Cluster", Length: 40}, -// "srv": Column{Name: "Service", Length: 35}, -// "hst": Column{Name: "Host", Length: 45}, -// "pct": Column{Name: "%CPU", Length: 7, RightJustified: true}, -type Columns map[string]*Column +// Table - maps and orders short names of columns to their structure defining: +// full name, length and whether it's right justified +type Table struct { + Columns map[string]*Column + order *[]string +} // Column - defines column's name, length and if it's right justified type Column struct { @@ -30,64 +26,88 @@ type Column struct { RightJustified bool } -// New - Creates a map of tabular Columns -func New() Columns { return Columns{} } +// All - pass this to Print() or Parse() to print or parse all columns of a table +const All = "*" + +// New - Creates a new table +func New() Table { + return Table{ + Columns: map[string]*Column{}, + order: &[]string{}, + } +} // Print - does the following: // -// 1) prints a table style heading for a given list of columns. -// -// For example if Columns are defined as: +// 1) prints a table style heading for a given list of columns, +// for example, if Columns are defined as: // // "env": Column{Name: "Environment", Length: 14}, // "cls": Column{Name: "Cluster", Length: 40}, // "srv": Column{Name: "Service", Length: 35}, // -// It'll produce: +// It'll produce: // // Environment Cluster Service // -------------- ---------------------------------------- ----------------------------------- // // 2) Returns an fmt style format specifier string that you can use -// to output values under the above heading via Printf(format,...): +// to output values under the above heading via Printf(format,...): // // %-14v %-40v %-35v -func (cl Columns) Print(cols ...string) string { - t := cl.parse(cols...) +func (tbl Table) Print(cols ...string) string { + t := tbl.parse(cols...) fmt.Println(t.Header) fmt.Println(t.SubHeader) return t.Format } -// Parse - builds a Table out of a given list of columns +// Parse - constructs Table's Output structure containing it's header, +// sub-header and format modifier out of a given list of columns. // -// To simply print the table's title call Print() instead +// To simply print the table's title call Print() instead. // // Parse() is usefull when you need to control where -// to output the title, for example to a log or a trace file -func (cl Columns) Parse(cols ...string) Table { - return cl.parse(cols...) +// to output the title, for example to a log or a trace file. +func (tbl Table) Parse(cols ...string) Output { + return tbl.parse(cols...) } -// Col - adds a new column to existing tabular Format -func (cl Columns) Col(shortName, fullName string, columnLength int) { - cl[shortName] = &Column{Name: fullName, Length: columnLength} +// Col - adds a new column to an existing table +func (tbl Table) Col(shortName, fullName string, columnLength int) { + tbl.Columns[shortName] = &Column{Name: fullName, Length: columnLength} + tbl.appendColumn(shortName) } -func (cl Columns) parse(cols ...string) Table { +// ColRJ - adds a new Right Justified column to an existing table +func (tbl Table) ColRJ(shortName, fullName string, columnLength int) { + tbl.Columns[shortName] = &Column{Name: fullName, Length: columnLength, RightJustified: true} + tbl.appendColumn(shortName) +} + +func (tbl Table) appendColumn(shortName string) { + *tbl.order = append(*tbl.order, shortName) +} + +func (tbl Table) parse(cols ...string) Output { var header string var subHeader string var format string var space string + + if len(cols) == 1 && cols[0] == All { + cols = *tbl.order + } + for _, c := range cols { - cf := cl[c].f() - header = header + space + fmt.Sprintf(cf, cl[c].Name) - subHeader = subHeader + space + fmt.Sprintf(cf, r(cl[c].Length)) + cf := tbl.Columns[c].f() + header = header + space + fmt.Sprintf(cf, tbl.Columns[c].Name) + subHeader = subHeader + space + fmt.Sprintf(cf, r(tbl.Columns[c].Length)) format = format + space + cf space = " " } - return Table{ + return Output{ Header: header, SubHeader: subHeader, Format: format + "\n", diff --git a/tabular_test.go b/tabular_test.go index a0e0898..7a3de12 100644 --- a/tabular_test.go +++ b/tabular_test.go @@ -6,27 +6,48 @@ import ( ) func TestTabular(t *testing.T) { - want := Columns{ - "env": &Column{Name: "Environment", Length: 14}, - "cls": &Column{Name: "Cluster", Length: 10}, - "svc": &Column{Name: "Service", Length: 25}, - "hst": &Column{Name: "Database Host", Length: 25}, - "pct": &Column{Name: "PCT", Length: 5, RightJustified: true}, + want := Table{ + Columns: map[string]*Column{ + "env": &Column{Name: "Environment", Length: 14}, + "cls": &Column{Name: "Cluster", Length: 10}, + "svc": &Column{Name: "Service", Length: 25}, + "hst": &Column{Name: "Database Host", Length: 25}, + "pct": &Column{Name: "PCT", Length: 5, RightJustified: true}, + }, + order: &[]string{ + "env", + "cls", + "svc", + "hst", + "pct", + }, } got := New() - for k, v := range want { - got.Col(k, v.Name, v.Length) - got[k].RightJustified = v.RightJustified + for _, v := range *want.order { + c := want.Columns[v] + f := got.Col + if c.RightJustified { + f = got.ColRJ + } + f(v, c.Name, c.Length) } if !reflect.DeepEqual(want, got) { - if len(got) != len(want) { - t.Fatalf("ERROR: tabular.Add() failed to produce map of the right size") + if len(got.Columns) != len(want.Columns) { + t.Fatalf("ERROR: tabular failed to produce map of the right size\nWant: %d, Got: %d\n", len(want.Columns), len(got.Columns)) + } + if len(*got.order) != len(*want.order) { + t.Fatalf("ERROR: tabular failed to produce array of the right size\nWant: %d, Got: %d\n", len(*want.order), len(*got.order)) + } + for k, v := range want.Columns { + if !reflect.DeepEqual(v, got.Columns[k]) { + t.Errorf("ERROR: tabular.Column mismatch\n want: %#v\n got: %#v", *v, *got.Columns[k]) + } } - for k, v := range want { - if !reflect.DeepEqual(v, got[k]) { - t.Errorf("ERROR: tabular.Column mismatch\n want: %#v\n got: %#v", *v, *got[k]) + for k, v := range *want.order { + if v != (*got.order)[k] { + t.Errorf("ERROR: tabular.order mismatch\n want: %#v\n got: %#v", v, (*got.order)[k]) } }