Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cell formatter #13

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 60 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
# uitable [![GoDoc](https://godoc.org/github.com/gosuri/uitable?status.svg)](https://godoc.org/github.com/gosuri/uitable) [![Build Status](https://travis-ci.org/gosuri/uitable.svg?branch=master)](https://travis-ci.org/gosuri/uitable)

uitable is a go library for representing data as tables for terminal applications. It provides primitives for sizing and wrapping columns to improve readability.
`uitable` is a go library for representing data as tables for terminal applications. It provides primitives for sizing and wrapping columns to improve readability. It also makes it possible to attach a formatter to each table cell, typically used to colorize terminal output.

## Example Usage

Full source code for the example is available at [example/main.go](example/main.go)

```go
table := uitable.New()
table.CellFormatter = func(x, y int) uitable.Formatter {
if y == 0 { return headerFormat }
if x == 0 { return nameFormat }
return uitable.DefaultFormatter
}
table.MaxColWidth = 50

table.AddRow("NAME", "BIRTHDAY", "BIO")
Expand All @@ -17,18 +22,37 @@ for _, hacker := range hackers {
fmt.Println(table)
```

Will render the data as:

```sh
NAME BIRTHDAY BIO
Ada Lovelace December 10, 1815 Ada was a British mathematician and writer, chi...
Alan Turing June 23, 1912 Alan was a British pioneering computer scientis...
```
Will render the data like:

<table>
<tr>
<td><b>NAME</b></td>
<td><b>BIRTHDAY</b></td>
<td><b>BIO</b></td>
</tr>
<tr>
<td style="color: green">Ada Lovelace</td>
<td>December 10, 1815</td>
<td>Ada was a British mathematician and writer, chi...</td>
</tr>
<tr>
<td style="color: green">Alan Turing</td>
<td>June 23, 1912</td>
<td>Alan was a British pioneering computer scientis...</td>
</tr>
</tr>

</table>

For wrapping in two columns:

```go
table = uitable.New()
table.CellFormatter = func(x, y int) uitable.Formatter {
if x == 0 { return headerFormat }
if x == 1 && y%4 == 0 { return nameFormat }
return uitable.DefaultFormatter
}
table.MaxColWidth = 80
table.Wrap = true // wrap columns

Expand All @@ -41,20 +65,34 @@ for _, hacker := range hackers {
fmt.Println(table)
```

Will render the data as:

```
Name: Ada Lovelace
Birthday: December 10, 1815
Bio: Ada was a British mathematician and writer, chiefly known for her work on
Charles Babbage's early mechanical general-purpose computer, the Analytical
Engine

Name: Alan Turing
Birthday: June 23, 1912
Bio: Alan was a British pioneering computer scientist, mathematician, logician,
cryptanalyst and theoretical biologist
```
Will render the data like:

<table align="top">
<tr>
<td><b>Name:</b><td>
<td style="color: green">Ada Lovelace</td>
</tr>
<tr>
<td><b>Birthday:</b><td>
<td>December 10, 1815</td>
</tr>
<tr>
<td><b>Bio:</b><td>
<td>Ada was a British mathematician and writer, chiefly known for her work on Charles Babbage's early mechanical general-purpose computer, the Analytical Engine</td>
</tr>
<tr>
<td><b>Name:</b><td>
<td style="color: green">Alan Turing</td>
</tr>
<tr>
<td><b>Birthday:</b><td>
<td>June 23, 1912</td>
</tr>
<tr>
<td><b>Bio:</b><td>
<td>Alan was a British pioneering computer scientist, mathematician, logician, cryptanalyst and theoretical biologist</td>
</tr>
</table>

## Installation

Expand Down
26 changes: 25 additions & 1 deletion example/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ package main
import (
"fmt"

"github.com/gosuri/uitable"
"github.com/fatih/color"
"github.com/svenax/uitable"
)

type hacker struct {
Expand All @@ -15,8 +16,22 @@ var hackers = []hacker{
{"Alan Turing", "June 23, 1912", "Alan was a British pioneering computer scientist, mathematician, logician, cryptanalyst and theoretical biologist"},
}

var (
headerFormat = color.New(color.FgBlack, color.Bold).SprintFunc()
nameFormat = color.New(color.FgGreen).SprintFunc()
)

func main() {
table := uitable.New()
table.CellFormatter = func(x, y int) uitable.Formatter {
if y == 0 {
return headerFormat
}
if x == 0 {
return nameFormat
}
return uitable.DefaultFormatter
}
table.MaxColWidth = 50

fmt.Println("==> List")
Expand All @@ -28,6 +43,15 @@ func main() {

fmt.Print("\n==> Details\n")
table = uitable.New()
table.CellFormatter = func(x, y int) uitable.Formatter {
if x == 0 {
return headerFormat
}
if x == 1 && y%4 == 0 {
return nameFormat
}
return uitable.DefaultFormatter
}
table.MaxColWidth = 80
table.Wrap = true
for _, hacker := range hackers {
Expand Down
48 changes: 39 additions & 9 deletions table.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,24 @@ import (
"github.com/mattn/go-runewidth"
)

// Separator is the default column seperator
var Separator = "\t"
// CellFormatter takes x and y cell coordinates and returns a function
// used to format the content.
type CellFormatter func(x, y int) Formatter

// Formatter formats the content of a cell. The signature
// is that of fmt.Sprint which is used as default.
type Formatter func(s ...interface{}) string

var (
// Separator is the default column seperator
Separator = "\t"

// DefaultFormatter is the default cell content formatter
DefaultFormatter = fmt.Sprint

// DefaultCellFormatter is the default cell formatting function
DefaultCellFormatter = func(x, y int) Formatter { return DefaultFormatter }
)

// Table represents a decorator that renders the data in formatted in a table
type Table struct {
Expand All @@ -28,16 +44,20 @@ type Table struct {
// Separator is the seperator for columns in the table. Default is "\t"
Separator string

// CellFormatter is the function that selects formatting functions for each cell
CellFormatter CellFormatter

mtx *sync.RWMutex
rightAlign map[int]bool
}

// New returns a new Table with default values
func New() *Table {
return &Table{
Separator: Separator,
mtx: new(sync.RWMutex),
rightAlign: map[int]bool{},
Separator: Separator,
CellFormatter: DefaultCellFormatter,
mtx: new(sync.RWMutex),
rightAlign: map[int]bool{},
}
}

Expand All @@ -47,6 +67,7 @@ func (t *Table) AddRow(data ...interface{}) *Table {
defer t.mtx.Unlock()
r := NewRow(data...)
t.Rows = append(t.Rows, r)
r.setCellFormatter(len(t.Rows)-1, t.CellFormatter)
return t
}

Expand All @@ -55,6 +76,7 @@ func (t *Table) Bytes() []byte {
return []byte(t.String())
}

// RightAlign sets the column at index col to be right aligned
func (t *Table) RightAlign(col int) {
t.mtx.Lock()
t.rightAlign[col] = true
Expand Down Expand Up @@ -135,7 +157,7 @@ func (r *Row) String() string {
for x := 0; x < lc; x++ {
cells[x] = make([]*Cell, len(r.Cells))
for y := 0; y < len(r.Cells); y++ {
cells[x][y] = &Cell{Width: r.Cells[y].Width}
cells[x][y] = &Cell{Width: r.Cells[y].Width, Formatter: r.Cells[y].Formatter}
}
}

Expand All @@ -152,13 +174,20 @@ func (r *Row) String() string {
for x := range lines {
line := make([]string, len(cells[x]))
for y := range cells[x] {
line[y] = cells[x][y].String()
c := cells[x][y]
line[y] = c.Formatter(c.String())
}
lines[x] = strutil.Join(line, r.Separator)
}
return strutil.Join(lines, "\n")
}

func (r *Row) setCellFormatter(y int, cellFormatter CellFormatter) {
for x, c := range r.Cells {
c.Formatter = cellFormatter(x, y)
}
}

// Cell represents a column in a row
type Cell struct {
// Width is the width of the cell
Expand All @@ -172,6 +201,8 @@ type Cell struct {

// Data is the cell data
Data interface{}

Formatter Formatter
}

// LineWidth returns the max width of all the lines in a cell
Expand All @@ -195,9 +226,8 @@ func (c *Cell) String() string {
if c.Width > 0 {
if c.Wrap && uint(len(s)) > c.Width {
return wordwrap.WrapString(s, c.Width)
} else {
return strutil.Resize(s, c.Width, c.RightAlign)
}
return strutil.Resize(s, c.Width, c.RightAlign)
}
return s
}
32 changes: 32 additions & 0 deletions table_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,18 @@ func TestCell(t *testing.T) {
}
}

func TestFormatCell(t *testing.T) {
c := &Cell{
Data: "foo bar",
Formatter: formatter,
}

got := c.Formatter(c.String())
if got != ">>foo bar<<" {
t.Fatal("need", ">>foo bar<<", "got", got)
}
}

func TestRow(t *testing.T) {
row := &Row{
Separator: "\t",
Expand All @@ -45,6 +57,22 @@ func TestRow(t *testing.T) {
}
}

func TestFormatRow(t *testing.T) {
row := &Row{
Separator: "\t",
Cells: []*Cell{
{Data: "foo", Width: 3, Wrap: true, Formatter: formatter},
{Data: "bar baz", Width: 3, Wrap: true, Formatter: formatter},
},
}
got := row.String()
need := ">>foo<<\t>>bar<<\n>> <<\t>>baz<<"

if got != need {
t.Fatalf("need: %q | got: %q ", need, got)
}
}

func TestAlign(t *testing.T) {
table := New()
table.AddRow("foo", "bar baz")
Expand Down Expand Up @@ -79,3 +107,7 @@ func TestAddRow(t *testing.T) {
t.Fatal("want", 100, "got", len(table.Rows))
}
}

func formatter(s ...interface{}) string {
return ">>" + DefaultFormatter(s[0]) + "<<"
}