Skip to content
This repository has been archived by the owner on Dec 23, 2024. It is now read-only.

Commit

Permalink
Merge pull request #1 from InVisionApp/siplify-api
Browse files Browse the repository at this point in the history
reworked public API and added Parse to support other output destinations
  • Loading branch information
vmogilev authored Apr 26, 2018
2 parents c661599 + bf5ba28 commit 8679916
Show file tree
Hide file tree
Showing 6 changed files with 187 additions and 44 deletions.
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ language: go

script:
- go test -race -coverprofile=coverage.txt -covermode=atomic
- go run example/example.go

after_success:
- bash <(curl -s https://codecov.io/bash)
45 changes: 33 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,31 +6,37 @@

# tabular

Tabular package to print ASCII tables from command line utilities.
Tabular simplifies printing ASCII tables from command line utilities without the need to pass large sets of data to it's API.

Example:
Simply define the table columns and `tabular` will parse the right [format specifier](https://golang.org/pkg/fmt/#Printf) that you can use in your calls to `fmt` or any other function that supports it.

Table columns can be defined once and then reused over and over again making it easy to modify column length and heading in one place. And a subset of columns can be specified during Print() or Parse() calls to modify the table's title without redefining it.

Example (also available in [`example/example.go`](example/example.go):

```go
package main

import (
"fmt"
"log"

"github.com/InVisionApp/tabular"
)

var tab tabular.Format
var tab tabular.Columns

func init() {
tab = tabular.New()
tab.Add("env", "Environment", 14)
tab.Add("cls", "Cluster", 10)
tab.Add("svc", "Service", 15)
tab.Add("hst", "Database Host", 20)
tab.Add("pct", "%CPU", 7)
tab.Col("env", "Environment", 14)
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
}

// Sample data-set
var data = []struct {
e, c, s, d string
v float64
Expand Down Expand Up @@ -67,22 +73,30 @@ var data = []struct {

func main() {
// Print Environments and Clusters
format := tab.Do("env", "cls")
format := tab.Print("env", "cls")
for _, x := range data {
fmt.Printf(format, x.e, x.c)
}

// Print Environments, Clusters and Services
format = tab.Do("env", "cls", "svc")
format = tab.Print("env", "cls", "svc")
for _, x := range data {
fmt.Printf(format, x.e, x.c, x.s)
}

// Print Clusters, Services and Database Hosts
format = tab.Do("cls", "svc", "hst", "pct")
// 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")
log.Println(table.Header)
log.Println(table.SubHeader)
for _, x := range data {
log.Printf(table.Format, x.c, x.s, x.d, x.v)
}
}
```

Expand All @@ -109,4 +123,11 @@ 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
```
82 changes: 82 additions & 0 deletions example/example.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package main

import (
"fmt"
"log"

"github.com/InVisionApp/tabular"
)

var tab tabular.Columns

func init() {
tab = tabular.New()
tab.Col("env", "Environment", 14)
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
}

var data = []struct {
e, c, s, d string
v float64
}{
{
e: "production",
c: "cluster-1",
s: "service-a",
d: "database-host-1",
v: 70.01,
},
{
e: "production",
c: "cluster-1",
s: "service-b",
d: "database-host-2",
v: 99.51,
},
{
e: "production",
c: "cluster-2",
s: "service-a",
d: "database-host-1",
v: 70.01,
},
{
e: "production",
c: "cluster-2",
s: "service-b",
d: "database-host-2",
v: 99.51,
},
}

func main() {
// Print 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")
for _, x := range data {
fmt.Printf(format, x.e, x.c, x.s)
}

// 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")
log.Println(table.Header)
log.Println(table.SubHeader)
for _, x := range data {
log.Printf(table.Format, x.c, x.s, x.d, x.v)
}
}
28 changes: 20 additions & 8 deletions format_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,27 @@ import (

func TestFormat(t *testing.T) {
tab := tabular.New()
tab.Add("env", "Environment", 14)
tab.Add("cls", "Cluster", 10)
tab.Add("svc", "Service", 25)
tab.Add("hst", "Database Host", 25)
tab.Add("pct", "%CPU", 5)
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["pct"].RightJustified = true

want := "%-14v %-10v %-25v %-25v %5v\n"
if got := tab.Do("env", "cls", "svc", "hst", "pct"); got != want {
t.Fatalf("ERROR: tab.Do() failed\n want: %q\n got: %q", want, got)
tWant := tabular.Table{
Header: "Environment Cluster Service Database Host %CPU",
SubHeader: "-------------- ---------- ------------------------- ------------------------- -----",
Format: "%-14v %-10v %-25v %-25v %5v\n",
}

// Test Printing
want := tWant.Format
if got := tab.Print("env", "cls", "svc", "hst", "pct"); got != want {
t.Errorf("ERROR: tab.Print() failed\n want: %q\n got: %q", want, got)
}

// Test Parsing
if tGot := tab.Parse("env", "cls", "svc", "hst", "pct"); tGot != tWant {
t.Errorf("ERROR: tab.Parse() failed\n want: %v\n got: %v", tWant, tGot)
}
}
71 changes: 49 additions & 22 deletions tabular.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,14 @@ import (
"strings"
)

// Format - maps short names of columns to their structure defining:
// Table - parsed table's header, subheader and format specifier
type Table 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:
Expand All @@ -14,7 +21,7 @@ import (
// "srv": Column{Name: "Service", Length: 35},
// "hst": Column{Name: "Host", Length: 45},
// "pct": Column{Name: "%CPU", Length: 7, RightJustified: true},
type Format map[string]*Column
type Columns map[string]*Column

// Column - defines column's name, length and if it's right justified
type Column struct {
Expand All @@ -23,11 +30,14 @@ type Column struct {
RightJustified bool
}

// Do - does the following:
// New - Creates a map of tabular Columns
func New() Columns { return Columns{} }

// Print - does the following:
//
// 1) prints a table style heading for a given list of columns.
//
// For example if Format is defined as:
// For example if Columns are defined as:
//
// "env": Column{Name: "Environment", Length: 14},
// "cls": Column{Name: "Cluster", Length: 40},
Expand All @@ -38,30 +48,47 @@ type Column struct {
// Environment Cluster Service
// -------------- ---------------------------------------- -----------------------------------
//
// 2) Returns an fmt style `format` string to output values
// under the above heading via Printf(format,...):
// 2) Returns an fmt style format specifier string that you can use
// to output values under the above heading via Printf(format,...):
//
// %-14v %-40v %-35v
func (fm Format) Do(cols ...string) string {
var title string
var uline string
func (cl Columns) Print(cols ...string) string {
t := cl.parse(cols...)
fmt.Println(t.Header)
fmt.Println(t.SubHeader)
return t.Format
}

// Parse - builds a Table out of a given list of columns
//
// 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...)
}

// 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}
}

func (cl Columns) parse(cols ...string) Table {
var header string
var subHeader string
var format string
for _, c := range cols {
title = title + " " + fmt.Sprintf(fm[c].f(), fm[c].Name)
uline = uline + " " + fmt.Sprintf(fm[c].f(), r(fm[c].Length))
format = format + " " + fm[c].f()
header = header + " " + fmt.Sprintf(cl[c].f(), cl[c].Name)
subHeader = subHeader + " " + fmt.Sprintf(cl[c].f(), r(cl[c].Length))
format = format + " " + cl[c].f()
}
fmt.Println(strings.TrimSpace(title))
fmt.Println(strings.TrimSpace(uline))
return strings.TrimSpace(format) + "\n"
}

// New - Creates a new tabular Format
func New() Format { return Format{} }

// Add - adds a new column to existing tabular Format
func (fm Format) Add(shortName, fullName string, columnLength int) {
fm[shortName] = &Column{Name: fullName, Length: columnLength}
return Table{
Header: strings.TrimSpace(header),
SubHeader: strings.TrimSpace(subHeader),
Format: strings.TrimSpace(format) + "\n",
}
}

// f() returns fmt formatting, for example:
Expand Down
4 changes: 2 additions & 2 deletions tabular_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (
)

func TestTabular(t *testing.T) {
want := Format{
want := Columns{
"env": &Column{Name: "Environment", Length: 14},
"cls": &Column{Name: "Cluster", Length: 10},
"svc": &Column{Name: "Service", Length: 25},
Expand All @@ -16,7 +16,7 @@ func TestTabular(t *testing.T) {

got := New()
for k, v := range want {
got.Add(k, v.Name, v.Length)
got.Col(k, v.Name, v.Length)
got[k].RightJustified = v.RightJustified
}

Expand Down

0 comments on commit 8679916

Please sign in to comment.