You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cloudstack.apache.org by ro...@apache.org on 2018/04/21 16:54:47 UTC

[cloudstack-cloudmonkey] branch master updated: cmd: introduce poc text and table outputs

This is an automated email from the ASF dual-hosted git repository.

rohit pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cloudstack-cloudmonkey.git


The following commit(s) were added to refs/heads/master by this push:
     new cb70f1f  cmd: introduce poc text and table outputs
cb70f1f is described below

commit cb70f1fe32c7fb785874f1b8c0170d63bb87d9ec
Author: Rohit Yadav <ro...@apache.org>
AuthorDate: Sat Apr 21 22:24:17 2018 +0530

    cmd: introduce poc text and table outputs
    
    Signed-off-by: Rohit Yadav <ro...@apache.org>
---
 cmd/api.go                                         |   75 +-
 config/config.go                                   |    1 +
 .../github.com/olekukonko/tablewriter/LICENCE.md   |   19 +
 vendor/github.com/olekukonko/tablewriter/README.md |  277 +++++
 vendor/github.com/olekukonko/tablewriter/csv.go    |   52 +
 .../olekukonko/tablewriter/csv2table/README.md     |   43 +
 .../olekukonko/tablewriter/csv2table/csv2table.go  |   85 ++
 vendor/github.com/olekukonko/tablewriter/table.go  |  839 ++++++++++++++++
 .../olekukonko/tablewriter/table_test.go           | 1055 ++++++++++++++++++++
 .../olekukonko/tablewriter/table_with_color.go     |  134 +++
 vendor/github.com/olekukonko/tablewriter/util.go   |   78 ++
 vendor/github.com/olekukonko/tablewriter/wrap.go   |   99 ++
 .../github.com/olekukonko/tablewriter/wrap_test.go |   58 ++
 13 files changed, 2811 insertions(+), 4 deletions(-)

diff --git a/cmd/api.go b/cmd/api.go
index 215e58c..7bb6f43 100644
--- a/cmd/api.go
+++ b/cmd/api.go
@@ -22,6 +22,12 @@ import (
 	"errors"
 	"fmt"
 	"strings"
+
+	"cloudmonkey/config"
+	"github.com/olekukonko/tablewriter"
+	"os"
+	"reflect"
+	"sort"
 )
 
 var apiCommand *Command
@@ -31,6 +37,28 @@ func GetAPIHandler() *Command {
 	return apiCommand
 }
 
+func printText(itemMap map[string]interface{}) {
+	for k, v := range itemMap {
+		valueType := reflect.TypeOf(v)
+		if valueType.Kind() == reflect.Slice {
+			fmt.Printf("%s:\n", k)
+			for _, item := range v.([]interface{}) {
+				row, isMap := item.(map[string]interface{})
+				if isMap {
+					for field, value := range row {
+						fmt.Printf("%s = %v\n", field, value)
+					}
+				} else {
+					fmt.Printf("%v\n", item)
+				}
+				fmt.Println("================================================================================")
+			}
+		} else {
+			fmt.Printf("%s = %v\n", k, v)
+		}
+	}
+}
+
 func init() {
 	apiCommand = &Command{
 		Name: "api",
@@ -77,15 +105,54 @@ func init() {
 				return nil
 			}
 
-			b, err := NewAPIRequest(r, api.Name, apiArgs)
+			response, err := NewAPIRequest(r, api.Name, apiArgs)
 			if err != nil {
 				return err
 			}
 
-			response, _ := json.MarshalIndent(b, "", "  ")
+			switch r.Config.Core.Output {
+			case config.TABLE:
+				table := tablewriter.NewWriter(os.Stdout)
+				for k, v := range response {
+					valueType := reflect.TypeOf(v)
+					if valueType.Kind() == reflect.Slice {
+						items, ok := v.([]interface{})
+						if !ok {
+							continue
+						}
+						fmt.Printf("%s:\n", k)
+						var header []string
+						for _, item := range items {
+							row, ok := item.(map[string]interface{})
+							if !ok || len(row) < 1 {
+								continue
+							}
+
+							if len(header) == 0 {
+								for field, _ := range row {
+									header = append(header, field)
+								}
+								sort.Strings(header)
+								table.SetHeader(header)
+							}
+							var rowArray []string
+							for _, field := range header {
+								rowArray = append(rowArray, fmt.Sprintf("%v", row[field]))
+							}
+							table.Append(rowArray)
+						}
+					} else {
+						fmt.Printf("%s = %v\n", k, v)
+					}
+				}
+				table.Render()
+			case config.TEXT:
+				printText(response)
+			default:
+				jsonOutput, _ := json.MarshalIndent(response, "", "  ")
+				fmt.Println(string(jsonOutput))
+			}
 
-			// Implement various output formats
-			fmt.Println(string(response))
 			return nil
 		},
 	}
diff --git a/config/config.go b/config/config.go
index fe18837..429fd4c 100644
--- a/config/config.go
+++ b/config/config.go
@@ -28,6 +28,7 @@ import (
 
 // Output formats
 const (
+	CSV   = "csv"
 	JSON  = "json"
 	XML   = "xml"
 	TABLE = "table"
diff --git a/vendor/github.com/olekukonko/tablewriter/LICENCE.md b/vendor/github.com/olekukonko/tablewriter/LICENCE.md
new file mode 100644
index 0000000..1fd8484
--- /dev/null
+++ b/vendor/github.com/olekukonko/tablewriter/LICENCE.md
@@ -0,0 +1,19 @@
+Copyright (C) 2014 by Oleku Konko
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
\ No newline at end of file
diff --git a/vendor/github.com/olekukonko/tablewriter/README.md b/vendor/github.com/olekukonko/tablewriter/README.md
new file mode 100644
index 0000000..59cb86c
--- /dev/null
+++ b/vendor/github.com/olekukonko/tablewriter/README.md
@@ -0,0 +1,277 @@
+ASCII Table Writer
+=========
+
+[![Build Status](https://travis-ci.org/olekukonko/tablewriter.png?branch=master)](https://travis-ci.org/olekukonko/tablewriter)
+[![Total views](https://img.shields.io/sourcegraph/rrc/github.com/olekukonko/tablewriter.svg)](https://sourcegraph.com/github.com/olekukonko/tablewriter)
+[![Godoc](https://godoc.org/github.com/olekukonko/tablewriter?status.svg)](https://godoc.org/github.com/olekukonko/tablewriter)
+
+Generate ASCII table on the fly ...  Installation is simple as
+
+    go get  github.com/olekukonko/tablewriter
+
+
+#### Features
+- Automatic Padding
+- Support Multiple Lines
+- Supports Alignment
+- Support Custom Separators
+- Automatic Alignment of numbers & percentage
+- Write directly to http , file etc via `io.Writer`
+- Read directly from CSV file
+- Optional row line via `SetRowLine`
+- Normalise table header
+- Make CSV Headers optional
+- Enable or disable table border
+- Set custom footer support
+- Optional identical cells merging
+- Set custom caption
+- Optional reflowing of paragrpahs in multi-line cells.
+
+#### Example   1 - Basic
+```go
+data := [][]string{
+    []string{"A", "The Good", "500"},
+    []string{"B", "The Very very Bad Man", "288"},
+    []string{"C", "The Ugly", "120"},
+    []string{"D", "The Gopher", "800"},
+}
+
+table := tablewriter.NewWriter(os.Stdout)
+table.SetHeader([]string{"Name", "Sign", "Rating"})
+
+for _, v := range data {
+    table.Append(v)
+}
+table.Render() // Send output
+```
+
+##### Output  1
+```
++------+-----------------------+--------+
+| NAME |         SIGN          | RATING |
++------+-----------------------+--------+
+|  A   |       The Good        |    500 |
+|  B   | The Very very Bad Man |    288 |
+|  C   |       The Ugly        |    120 |
+|  D   |      The Gopher       |    800 |
++------+-----------------------+--------+
+```
+
+#### Example 2 - Without Border / Footer / Bulk Append
+```go
+data := [][]string{
+    []string{"1/1/2014", "Domain name", "2233", "$10.98"},
+    []string{"1/1/2014", "January Hosting", "2233", "$54.95"},
+    []string{"1/4/2014", "February Hosting", "2233", "$51.00"},
+    []string{"1/4/2014", "February Extra Bandwidth", "2233", "$30.00"},
+}
+
+table := tablewriter.NewWriter(os.Stdout)
+table.SetHeader([]string{"Date", "Description", "CV2", "Amount"})
+table.SetFooter([]string{"", "", "Total", "$146.93"}) // Add Footer
+table.SetBorder(false)                                // Set Border to false
+table.AppendBulk(data)                                // Add Bulk Data
+table.Render()
+```
+
+##### Output 2
+```
+
+    DATE   |       DESCRIPTION        |  CV2  | AMOUNT
++----------+--------------------------+-------+---------+
+  1/1/2014 | Domain name              |  2233 | $10.98
+  1/1/2014 | January Hosting          |  2233 | $54.95
+  1/4/2014 | February Hosting         |  2233 | $51.00
+  1/4/2014 | February Extra Bandwidth |  2233 | $30.00
++----------+--------------------------+-------+---------+
+                                        TOTAL | $146 93
+                                      +-------+---------+
+
+```
+
+
+#### Example 3 - CSV
+```go
+table, _ := tablewriter.NewCSV(os.Stdout, "testdata/test_info.csv", true)
+table.SetAlignment(tablewriter.ALIGN_LEFT)   // Set Alignment
+table.Render()
+```
+
+##### Output 3
+```
++----------+--------------+------+-----+---------+----------------+
+|  FIELD   |     TYPE     | NULL | KEY | DEFAULT |     EXTRA      |
++----------+--------------+------+-----+---------+----------------+
+| user_id  | smallint(5)  | NO   | PRI | NULL    | auto_increment |
+| username | varchar(10)  | NO   |     | NULL    |                |
+| password | varchar(100) | NO   |     | NULL    |                |
++----------+--------------+------+-----+---------+----------------+
+```
+
+#### Example 4  - Custom Separator
+```go
+table, _ := tablewriter.NewCSV(os.Stdout, "testdata/test.csv", true)
+table.SetRowLine(true)         // Enable row line
+
+// Change table lines
+table.SetCenterSeparator("*")
+table.SetColumnSeparator("‡")
+table.SetRowSeparator("-")
+
+table.SetAlignment(tablewriter.ALIGN_LEFT)
+table.Render()
+```
+
+##### Output 4
+```
+*------------*-----------*---------*
+╪ FIRST NAME ╪ LAST NAME ╪   SSN   ╪
+*------------*-----------*---------*
+╪ John       ╪ Barry     ╪ 123456  ╪
+*------------*-----------*---------*
+╪ Kathy      ╪ Smith     ╪ 687987  ╪
+*------------*-----------*---------*
+╪ Bob        ╪ McCornick ╪ 3979870 ╪
+*------------*-----------*---------*
+```
+
+#### Example 5 - Markdown Format
+```go
+data := [][]string{
+	[]string{"1/1/2014", "Domain name", "2233", "$10.98"},
+	[]string{"1/1/2014", "January Hosting", "2233", "$54.95"},
+	[]string{"1/4/2014", "February Hosting", "2233", "$51.00"},
+	[]string{"1/4/2014", "February Extra Bandwidth", "2233", "$30.00"},
+}
+
+table := tablewriter.NewWriter(os.Stdout)
+table.SetHeader([]string{"Date", "Description", "CV2", "Amount"})
+table.SetBorders(tablewriter.Border{Left: true, Top: false, Right: true, Bottom: false})
+table.SetCenterSeparator("|")
+table.AppendBulk(data) // Add Bulk Data
+table.Render()
+```
+
+##### Output 5
+```
+|   DATE   |       DESCRIPTION        | CV2  | AMOUNT |
+|----------|--------------------------|------|--------|
+| 1/1/2014 | Domain name              | 2233 | $10.98 |
+| 1/1/2014 | January Hosting          | 2233 | $54.95 |
+| 1/4/2014 | February Hosting         | 2233 | $51.00 |
+| 1/4/2014 | February Extra Bandwidth | 2233 | $30.00 |
+```
+
+#### Example 6  - Identical cells merging
+```go
+data := [][]string{
+  []string{"1/1/2014", "Domain name", "1234", "$10.98"},
+  []string{"1/1/2014", "January Hosting", "2345", "$54.95"},
+  []string{"1/4/2014", "February Hosting", "3456", "$51.00"},
+  []string{"1/4/2014", "February Extra Bandwidth", "4567", "$30.00"},
+}
+
+table := tablewriter.NewWriter(os.Stdout)
+table.SetHeader([]string{"Date", "Description", "CV2", "Amount"})
+table.SetFooter([]string{"", "", "Total", "$146.93"})
+table.SetAutoMergeCells(true)
+table.SetRowLine(true)
+table.AppendBulk(data)
+table.Render()
+```
+
+##### Output 6
+```
++----------+--------------------------+-------+---------+
+|   DATE   |       DESCRIPTION        |  CV2  | AMOUNT  |
++----------+--------------------------+-------+---------+
+| 1/1/2014 | Domain name              |  1234 | $10.98  |
++          +--------------------------+-------+---------+
+|          | January Hosting          |  2345 | $54.95  |
++----------+--------------------------+-------+---------+
+| 1/4/2014 | February Hosting         |  3456 | $51.00  |
++          +--------------------------+-------+---------+
+|          | February Extra Bandwidth |  4567 | $30.00  |
++----------+--------------------------+-------+---------+
+|                                       TOTAL | $146 93 |
++----------+--------------------------+-------+---------+
+```
+
+
+#### Table with color
+```go
+data := [][]string{
+	[]string{"1/1/2014", "Domain name", "2233", "$10.98"},
+	[]string{"1/1/2014", "January Hosting", "2233", "$54.95"},
+	[]string{"1/4/2014", "February Hosting", "2233", "$51.00"},
+	[]string{"1/4/2014", "February Extra Bandwidth", "2233", "$30.00"},
+}
+
+table := tablewriter.NewWriter(os.Stdout)
+table.SetHeader([]string{"Date", "Description", "CV2", "Amount"})
+table.SetFooter([]string{"", "", "Total", "$146.93"}) // Add Footer
+table.SetBorder(false)                                // Set Border to false
+
+table.SetHeaderColor(tablewriter.Colors{tablewriter.Bold, tablewriter.BgGreenColor},
+	tablewriter.Colors{tablewriter.FgHiRedColor, tablewriter.Bold, tablewriter.BgBlackColor},
+	tablewriter.Colors{tablewriter.BgRedColor, tablewriter.FgWhiteColor},
+	tablewriter.Colors{tablewriter.BgCyanColor, tablewriter.FgWhiteColor})
+
+table.SetColumnColor(tablewriter.Colors{tablewriter.Bold, tablewriter.FgHiBlackColor},
+	tablewriter.Colors{tablewriter.Bold, tablewriter.FgHiRedColor},
+	tablewriter.Colors{tablewriter.Bold, tablewriter.FgHiBlackColor},
+	tablewriter.Colors{tablewriter.Bold, tablewriter.FgBlackColor})
+
+table.SetFooterColor(tablewriter.Colors{}, tablewriter.Colors{},
+	tablewriter.Colors{tablewriter.Bold},
+	tablewriter.Colors{tablewriter.FgHiRedColor})
+
+table.AppendBulk(data)
+table.Render()
+```
+
+#### Table with color Output
+![Table with Color](https://cloud.githubusercontent.com/assets/6460392/21101956/bbc7b356-c0a1-11e6-9f36-dba694746efc.png)
+
+#### Example 6 - Set table caption
+```go
+data := [][]string{
+    []string{"A", "The Good", "500"},
+    []string{"B", "The Very very Bad Man", "288"},
+    []string{"C", "The Ugly", "120"},
+    []string{"D", "The Gopher", "800"},
+}
+
+table := tablewriter.NewWriter(os.Stdout)
+table.SetHeader([]string{"Name", "Sign", "Rating"})
+table.SetCaption(true, "Movie ratings.")
+
+for _, v := range data {
+    table.Append(v)
+}
+table.Render() // Send output
+```
+
+Note: Caption text will wrap with total width of rendered table.
+
+##### Output 6
+```
++------+-----------------------+--------+
+| NAME |         SIGN          | RATING |
++------+-----------------------+--------+
+|  A   |       The Good        |    500 |
+|  B   | The Very very Bad Man |    288 |
+|  C   |       The Ugly        |    120 |
+|  D   |      The Gopher       |    800 |
++------+-----------------------+--------+
+Movie ratings.
+```
+
+#### TODO
+- ~~Import Directly from CSV~~  - `done`
+- ~~Support for `SetFooter`~~  - `done`
+- ~~Support for `SetBorder`~~  - `done`
+- ~~Support table with uneven rows~~ - `done`
+- ~~Support custom alignment~~
+- General Improvement & Optimisation
+- `NewHTML` Parse table from HTML
diff --git a/vendor/github.com/olekukonko/tablewriter/csv.go b/vendor/github.com/olekukonko/tablewriter/csv.go
new file mode 100644
index 0000000..9887830
--- /dev/null
+++ b/vendor/github.com/olekukonko/tablewriter/csv.go
@@ -0,0 +1,52 @@
+// Copyright 2014 Oleku Konko All rights reserved.
+// Use of this source code is governed by a MIT
+// license that can be found in the LICENSE file.
+
+// This module is a Table Writer  API for the Go Programming Language.
+// The protocols were written in pure Go and works on windows and unix systems
+
+package tablewriter
+
+import (
+	"encoding/csv"
+	"io"
+	"os"
+)
+
+// Start A new table by importing from a CSV file
+// Takes io.Writer and csv File name
+func NewCSV(writer io.Writer, fileName string, hasHeader bool) (*Table, error) {
+	file, err := os.Open(fileName)
+	if err != nil {
+		return &Table{}, err
+	}
+	defer file.Close()
+	csvReader := csv.NewReader(file)
+	t, err := NewCSVReader(writer, csvReader, hasHeader)
+	return t, err
+}
+
+//  Start a New Table Writer with csv.Reader
+// This enables customisation such as reader.Comma = ';'
+// See http://golang.org/src/pkg/encoding/csv/reader.go?s=3213:3671#L94
+func NewCSVReader(writer io.Writer, csvReader *csv.Reader, hasHeader bool) (*Table, error) {
+	t := NewWriter(writer)
+	if hasHeader {
+		// Read the first row
+		headers, err := csvReader.Read()
+		if err != nil {
+			return &Table{}, err
+		}
+		t.SetHeader(headers)
+	}
+	for {
+		record, err := csvReader.Read()
+		if err == io.EOF {
+			break
+		} else if err != nil {
+			return &Table{}, err
+		}
+		t.Append(record)
+	}
+	return t, nil
+}
diff --git a/vendor/github.com/olekukonko/tablewriter/csv2table/README.md b/vendor/github.com/olekukonko/tablewriter/csv2table/README.md
new file mode 100644
index 0000000..6cf5628
--- /dev/null
+++ b/vendor/github.com/olekukonko/tablewriter/csv2table/README.md
@@ -0,0 +1,43 @@
+ASCII Table Writer Tool
+=========
+
+Generate ASCII table on the fly via command line ...  Installation is simple as
+
+#### Get Tool
+
+    go get  github.com/olekukonko/tablewriter/csv2table
+
+#### Install Tool
+
+    go install  github.com/olekukonko/tablewriter/csv2table
+
+
+#### Usage
+
+    csv2table -f test.csv
+
+#### Support for Piping
+
+    cat test.csv | csv2table -p=true
+
+#### Output
+
+```
++------------+-----------+---------+
+| FIRST NAME | LAST NAME |   SSN   |
++------------+-----------+---------+
+|    John    |   Barry   |  123456 |
+|   Kathy    |   Smith   |  687987 |
+|    Bob     | McCornick | 3979870 |
++------------+-----------+---------+
+```
+
+#### Another Piping with Header set to `false`
+
+    echo dance,with,me | csv2table -p=true -h=false
+
+#### Output
+
+    +-------+------+-----+
+    | dance | with | me  |
+    +-------+------+-----+
diff --git a/vendor/github.com/olekukonko/tablewriter/csv2table/csv2table.go b/vendor/github.com/olekukonko/tablewriter/csv2table/csv2table.go
new file mode 100644
index 0000000..5e1d7f2
--- /dev/null
+++ b/vendor/github.com/olekukonko/tablewriter/csv2table/csv2table.go
@@ -0,0 +1,85 @@
+package main
+
+import (
+	"encoding/csv"
+	"flag"
+	"fmt"
+	"io"
+	"os"
+	"unicode/utf8"
+
+	"github.com/olekukonko/tablewriter"
+)
+
+var (
+	fileName  = flag.String("f", "", "Set file with  eg. sample.csv")
+	delimiter = flag.String("d", ",", "Set CSV File delimiter eg. ,|;|\t ")
+	header    = flag.Bool("h", true, "Set header options eg. true|false ")
+	align     = flag.String("a", "none", "Set aligmement with eg. none|left|right|center")
+	pipe      = flag.Bool("p", false, "Suport for Piping from STDIN")
+	border    = flag.Bool("b", true, "Enable / disable table border")
+)
+
+func main() {
+	flag.Parse()
+	fmt.Println()
+	if *pipe || hasArg("-p") {
+		process(os.Stdin)
+	} else {
+		if *fileName == "" {
+			fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
+			flag.PrintDefaults()
+			fmt.Println()
+			os.Exit(1)
+		}
+		processFile()
+	}
+	fmt.Println()
+}
+
+func hasArg(name string) bool {
+	for _, v := range os.Args {
+		if name == v {
+			return true
+		}
+	}
+	return false
+}
+func processFile() {
+	r, err := os.Open(*fileName)
+	if err != nil {
+		exit(err)
+	}
+	defer r.Close()
+	process(r)
+}
+func process(r io.Reader) {
+	csvReader := csv.NewReader(r)
+	rune, size := utf8.DecodeRuneInString(*delimiter)
+	if size == 0 {
+		rune = ','
+	}
+	csvReader.Comma = rune
+
+	table, err := tablewriter.NewCSVReader(os.Stdout, csvReader, *header)
+
+	if err != nil {
+		exit(err)
+	}
+
+	switch *align {
+	case "left":
+		table.SetAlignment(tablewriter.ALIGN_LEFT)
+	case "right":
+		table.SetAlignment(tablewriter.ALIGN_RIGHT)
+	case "center":
+		table.SetAlignment(tablewriter.ALIGN_CENTER)
+	}
+	table.SetBorder(*border)
+	table.Render()
+}
+
+func exit(err error) {
+	fmt.Fprintf(os.Stderr, "#Error : %s", err)
+	os.Exit(1)
+}
diff --git a/vendor/github.com/olekukonko/tablewriter/table.go b/vendor/github.com/olekukonko/tablewriter/table.go
new file mode 100644
index 0000000..6bbef96
--- /dev/null
+++ b/vendor/github.com/olekukonko/tablewriter/table.go
@@ -0,0 +1,839 @@
+// Copyright 2014 Oleku Konko All rights reserved.
+// Use of this source code is governed by a MIT
+// license that can be found in the LICENSE file.
+
+// This module is a Table Writer  API for the Go Programming Language.
+// The protocols were written in pure Go and works on windows and unix systems
+
+// Create & Generate text based table
+package tablewriter
+
+import (
+	"bytes"
+	"fmt"
+	"io"
+	"regexp"
+	"strings"
+)
+
+const (
+	MAX_ROW_WIDTH = 30
+)
+
+const (
+	CENTER  = "+"
+	ROW     = "-"
+	COLUMN  = "|"
+	SPACE   = " "
+	NEWLINE = "\n"
+)
+
+const (
+	ALIGN_DEFAULT = iota
+	ALIGN_CENTER
+	ALIGN_RIGHT
+	ALIGN_LEFT
+)
+
+var (
+	decimal = regexp.MustCompile(`^-*\d*\.?\d*$`)
+	percent = regexp.MustCompile(`^-*\d*\.?\d*$%$`)
+)
+
+type Border struct {
+	Left   bool
+	Right  bool
+	Top    bool
+	Bottom bool
+}
+
+type Table struct {
+	out            io.Writer
+	rows           [][]string
+	lines          [][][]string
+	cs             map[int]int
+	rs             map[int]int
+	headers        [][]string
+	footers        [][]string
+	caption        bool
+	captionText    string
+	autoFmt        bool
+	autoWrap       bool
+	reflowText     bool
+	mW             int
+	pCenter        string
+	pRow           string
+	pColumn        string
+	tColumn        int
+	tRow           int
+	hAlign         int
+	fAlign         int
+	align          int
+	newLine        string
+	rowLine        bool
+	autoMergeCells bool
+	hdrLine        bool
+	borders        Border
+	colSize        int
+	headerParams   []string
+	columnsParams  []string
+	footerParams   []string
+	columnsAlign   []int
+}
+
+// Start New Table
+// Take io.Writer Directly
+func NewWriter(writer io.Writer) *Table {
+	t := &Table{
+		out:           writer,
+		rows:          [][]string{},
+		lines:         [][][]string{},
+		cs:            make(map[int]int),
+		rs:            make(map[int]int),
+		headers:       [][]string{},
+		footers:       [][]string{},
+		caption:       false,
+		captionText:   "Table caption.",
+		autoFmt:       true,
+		autoWrap:      true,
+		reflowText:    true,
+		mW:            MAX_ROW_WIDTH,
+		pCenter:       CENTER,
+		pRow:          ROW,
+		pColumn:       COLUMN,
+		tColumn:       -1,
+		tRow:          -1,
+		hAlign:        ALIGN_DEFAULT,
+		fAlign:        ALIGN_DEFAULT,
+		align:         ALIGN_DEFAULT,
+		newLine:       NEWLINE,
+		rowLine:       false,
+		hdrLine:       true,
+		borders:       Border{Left: true, Right: true, Bottom: true, Top: true},
+		colSize:       -1,
+		headerParams:  []string{},
+		columnsParams: []string{},
+		footerParams:  []string{},
+		columnsAlign:  []int{}}
+	return t
+}
+
+// Render table output
+func (t *Table) Render() {
+	if t.borders.Top {
+		t.printLine(true)
+	}
+	t.printHeading()
+	if t.autoMergeCells {
+		t.printRowsMergeCells()
+	} else {
+		t.printRows()
+	}
+	if !t.rowLine && t.borders.Bottom {
+		t.printLine(true)
+	}
+	t.printFooter()
+
+	if t.caption {
+		t.printCaption()
+	}
+}
+
+const (
+	headerRowIdx = -1
+	footerRowIdx = -2
+)
+
+// Set table header
+func (t *Table) SetHeader(keys []string) {
+	t.colSize = len(keys)
+	for i, v := range keys {
+		lines := t.parseDimension(v, i, headerRowIdx)
+		t.headers = append(t.headers, lines)
+	}
+}
+
+// Set table Footer
+func (t *Table) SetFooter(keys []string) {
+	//t.colSize = len(keys)
+	for i, v := range keys {
+		lines := t.parseDimension(v, i, footerRowIdx)
+		t.footers = append(t.footers, lines)
+	}
+}
+
+// Set table Caption
+func (t *Table) SetCaption(caption bool, captionText ...string) {
+	t.caption = caption
+	if len(captionText) == 1 {
+		t.captionText = captionText[0]
+	}
+}
+
+// Turn header autoformatting on/off. Default is on (true).
+func (t *Table) SetAutoFormatHeaders(auto bool) {
+	t.autoFmt = auto
+}
+
+// Turn automatic multiline text adjustment on/off. Default is on (true).
+func (t *Table) SetAutoWrapText(auto bool) {
+	t.autoWrap = auto
+}
+
+// Turn automatic reflowing of multiline text when rewrapping. Default is on (true).
+func (t *Table) SetReflowDuringAutoWrap(auto bool) {
+	t.reflowText = auto
+}
+
+// Set the Default column width
+func (t *Table) SetColWidth(width int) {
+	t.mW = width
+}
+
+// Set the minimal width for a column
+func (t *Table) SetColMinWidth(column int, width int) {
+	t.cs[column] = width
+}
+
+// Set the Column Separator
+func (t *Table) SetColumnSeparator(sep string) {
+	t.pColumn = sep
+}
+
+// Set the Row Separator
+func (t *Table) SetRowSeparator(sep string) {
+	t.pRow = sep
+}
+
+// Set the center Separator
+func (t *Table) SetCenterSeparator(sep string) {
+	t.pCenter = sep
+}
+
+// Set Header Alignment
+func (t *Table) SetHeaderAlignment(hAlign int) {
+	t.hAlign = hAlign
+}
+
+// Set Footer Alignment
+func (t *Table) SetFooterAlignment(fAlign int) {
+	t.fAlign = fAlign
+}
+
+// Set Table Alignment
+func (t *Table) SetAlignment(align int) {
+	t.align = align
+}
+
+func (t *Table) SetColumnAlignment(keys []int) {
+	for _, v := range keys {
+		switch v {
+		case ALIGN_CENTER:
+			break
+		case ALIGN_LEFT:
+			break
+		case ALIGN_RIGHT:
+			break
+		default:
+			v = ALIGN_DEFAULT
+		}
+		t.columnsAlign = append(t.columnsAlign, v)
+	}
+}
+
+// Set New Line
+func (t *Table) SetNewLine(nl string) {
+	t.newLine = nl
+}
+
+// Set Header Line
+// This would enable / disable a line after the header
+func (t *Table) SetHeaderLine(line bool) {
+	t.hdrLine = line
+}
+
+// Set Row Line
+// This would enable / disable a line on each row of the table
+func (t *Table) SetRowLine(line bool) {
+	t.rowLine = line
+}
+
+// Set Auto Merge Cells
+// This would enable / disable the merge of cells with identical values
+func (t *Table) SetAutoMergeCells(auto bool) {
+	t.autoMergeCells = auto
+}
+
+// Set Table Border
+// This would enable / disable line around the table
+func (t *Table) SetBorder(border bool) {
+	t.SetBorders(Border{border, border, border, border})
+}
+
+func (t *Table) SetBorders(border Border) {
+	t.borders = border
+}
+
+// Append row to table
+func (t *Table) Append(row []string) {
+	rowSize := len(t.headers)
+	if rowSize > t.colSize {
+		t.colSize = rowSize
+	}
+
+	n := len(t.lines)
+	line := [][]string{}
+	for i, v := range row {
+
+		// Detect string  width
+		// Detect String height
+		// Break strings into words
+		out := t.parseDimension(v, i, n)
+
+		// Append broken words
+		line = append(line, out)
+	}
+	t.lines = append(t.lines, line)
+}
+
+// Allow Support for Bulk Append
+// Eliminates repeated for loops
+func (t *Table) AppendBulk(rows [][]string) {
+	for _, row := range rows {
+		t.Append(row)
+	}
+}
+
+// NumLines to get the number of lines
+func (t *Table) NumLines() int {
+	return len(t.lines)
+}
+
+// Clear rows
+func (t *Table) ClearRows() {
+	t.lines = [][][]string{}
+}
+
+// Clear footer
+func (t *Table) ClearFooter() {
+	t.footers = [][]string{}
+}
+
+// Print line based on row width
+func (t *Table) printLine(nl bool) {
+	fmt.Fprint(t.out, t.pCenter)
+	for i := 0; i < len(t.cs); i++ {
+		v := t.cs[i]
+		fmt.Fprintf(t.out, "%s%s%s%s",
+			t.pRow,
+			strings.Repeat(string(t.pRow), v),
+			t.pRow,
+			t.pCenter)
+	}
+	if nl {
+		fmt.Fprint(t.out, t.newLine)
+	}
+}
+
+// Print line based on row width with our without cell separator
+func (t *Table) printLineOptionalCellSeparators(nl bool, displayCellSeparator []bool) {
+	fmt.Fprint(t.out, t.pCenter)
+	for i := 0; i < len(t.cs); i++ {
+		v := t.cs[i]
+		if i > len(displayCellSeparator) || displayCellSeparator[i] {
+			// Display the cell separator
+			fmt.Fprintf(t.out, "%s%s%s%s",
+				t.pRow,
+				strings.Repeat(string(t.pRow), v),
+				t.pRow,
+				t.pCenter)
+		} else {
+			// Don't display the cell separator for this cell
+			fmt.Fprintf(t.out, "%s%s",
+				strings.Repeat(" ", v+2),
+				t.pCenter)
+		}
+	}
+	if nl {
+		fmt.Fprint(t.out, t.newLine)
+	}
+}
+
+// Return the PadRight function if align is left, PadLeft if align is right,
+// and Pad by default
+func pad(align int) func(string, string, int) string {
+	padFunc := Pad
+	switch align {
+	case ALIGN_LEFT:
+		padFunc = PadRight
+	case ALIGN_RIGHT:
+		padFunc = PadLeft
+	}
+	return padFunc
+}
+
+// Print heading information
+func (t *Table) printHeading() {
+	// Check if headers is available
+	if len(t.headers) < 1 {
+		return
+	}
+
+	// Identify last column
+	end := len(t.cs) - 1
+
+	// Get pad function
+	padFunc := pad(t.hAlign)
+
+	// Checking for ANSI escape sequences for header
+	is_esc_seq := false
+	if len(t.headerParams) > 0 {
+		is_esc_seq = true
+	}
+
+	// Maximum height.
+	max := t.rs[headerRowIdx]
+
+	// Print Heading
+	for x := 0; x < max; x++ {
+		// Check if border is set
+		// Replace with space if not set
+		fmt.Fprint(t.out, ConditionString(t.borders.Left, t.pColumn, SPACE))
+
+		for y := 0; y <= end; y++ {
+			v := t.cs[y]
+			h := ""
+			if y < len(t.headers) && x < len(t.headers[y]) {
+				h = t.headers[y][x]
+			}
+			if t.autoFmt {
+				h = Title(h)
+			}
+			pad := ConditionString((y == end && !t.borders.Left), SPACE, t.pColumn)
+
+			if is_esc_seq {
+				fmt.Fprintf(t.out, " %s %s",
+					format(padFunc(h, SPACE, v),
+						t.headerParams[y]), pad)
+			} else {
+				fmt.Fprintf(t.out, " %s %s",
+					padFunc(h, SPACE, v),
+					pad)
+			}
+		}
+		// Next line
+		fmt.Fprint(t.out, t.newLine)
+	}
+	if t.hdrLine {
+		t.printLine(true)
+	}
+}
+
+// Print heading information
+func (t *Table) printFooter() {
+	// Check if headers is available
+	if len(t.footers) < 1 {
+		return
+	}
+
+	// Only print line if border is not set
+	if !t.borders.Bottom {
+		t.printLine(true)
+	}
+
+	// Identify last column
+	end := len(t.cs) - 1
+
+	// Get pad function
+	padFunc := pad(t.fAlign)
+
+	// Checking for ANSI escape sequences for header
+	is_esc_seq := false
+	if len(t.footerParams) > 0 {
+		is_esc_seq = true
+	}
+
+	// Maximum height.
+	max := t.rs[footerRowIdx]
+
+	// Print Footer
+	erasePad := make([]bool, len(t.footers))
+	for x := 0; x < max; x++ {
+		// Check if border is set
+		// Replace with space if not set
+		fmt.Fprint(t.out, ConditionString(t.borders.Bottom, t.pColumn, SPACE))
+
+		for y := 0; y <= end; y++ {
+			v := t.cs[y]
+			f := ""
+			if y < len(t.footers) && x < len(t.footers[y]) {
+				f = t.footers[y][x]
+			}
+			if t.autoFmt {
+				f = Title(f)
+			}
+			pad := ConditionString((y == end && !t.borders.Top), SPACE, t.pColumn)
+
+			if erasePad[y] || (x == 0 && len(f) == 0) {
+				pad = SPACE
+				erasePad[y] = true
+			}
+
+			if is_esc_seq {
+				fmt.Fprintf(t.out, " %s %s",
+					format(padFunc(f, SPACE, v),
+						t.footerParams[y]), pad)
+			} else {
+				fmt.Fprintf(t.out, " %s %s",
+					padFunc(f, SPACE, v),
+					pad)
+			}
+
+			//fmt.Fprintf(t.out, " %s %s",
+			//	padFunc(f, SPACE, v),
+			//	pad)
+		}
+		// Next line
+		fmt.Fprint(t.out, t.newLine)
+		//t.printLine(true)
+	}
+
+	hasPrinted := false
+
+	for i := 0; i <= end; i++ {
+		v := t.cs[i]
+		pad := t.pRow
+		center := t.pCenter
+		length := len(t.footers[i][0])
+
+		if length > 0 {
+			hasPrinted = true
+		}
+
+		// Set center to be space if length is 0
+		if length == 0 && !t.borders.Right {
+			center = SPACE
+		}
+
+		// Print first junction
+		if i == 0 {
+			fmt.Fprint(t.out, center)
+		}
+
+		// Pad With space of length is 0
+		if length == 0 {
+			pad = SPACE
+		}
+		// Ignore left space of it has printed before
+		if hasPrinted || t.borders.Left {
+			pad = t.pRow
+			center = t.pCenter
+		}
+
+		// Change Center start position
+		if center == SPACE {
+			if i < end && len(t.footers[i+1][0]) != 0 {
+				center = t.pCenter
+			}
+		}
+
+		// Print the footer
+		fmt.Fprintf(t.out, "%s%s%s%s",
+			pad,
+			strings.Repeat(string(pad), v),
+			pad,
+			center)
+
+	}
+
+	fmt.Fprint(t.out, t.newLine)
+}
+
+// Print caption text
+func (t Table) printCaption() {
+	width := t.getTableWidth()
+	paragraph, _ := WrapString(t.captionText, width)
+	for linecount := 0; linecount < len(paragraph); linecount++ {
+		fmt.Fprintln(t.out, paragraph[linecount])
+	}
+}
+
+// Calculate the total number of characters in a row
+func (t Table) getTableWidth() int {
+	var chars int
+	for _, v := range t.cs {
+		chars += v
+	}
+
+	// Add chars, spaces, seperators to calculate the total width of the table.
+	// ncols := t.colSize
+	// spaces := ncols * 2
+	// seps := ncols + 1
+
+	return (chars + (3 * t.colSize) + 2)
+}
+
+func (t Table) printRows() {
+	for i, lines := range t.lines {
+		t.printRow(lines, i)
+	}
+}
+
+func (t *Table) fillAlignment(num int) {
+	if len(t.columnsAlign) < num {
+		t.columnsAlign = make([]int, num)
+		for i := range t.columnsAlign {
+			t.columnsAlign[i] = t.align
+		}
+	}
+}
+
+// Print Row Information
+// Adjust column alignment based on type
+
+func (t *Table) printRow(columns [][]string, rowIdx int) {
+	// Get Maximum Height
+	max := t.rs[rowIdx]
+	total := len(columns)
+
+	// TODO Fix uneven col size
+	// if total < t.colSize {
+	//	for n := t.colSize - total; n < t.colSize ; n++ {
+	//		columns = append(columns, []string{SPACE})
+	//		t.cs[n] = t.mW
+	//	}
+	//}
+
+	// Pad Each Height
+	pads := []int{}
+
+	// Checking for ANSI escape sequences for columns
+	is_esc_seq := false
+	if len(t.columnsParams) > 0 {
+		is_esc_seq = true
+	}
+	t.fillAlignment(total)
+
+	for i, line := range columns {
+		length := len(line)
+		pad := max - length
+		pads = append(pads, pad)
+		for n := 0; n < pad; n++ {
+			columns[i] = append(columns[i], "  ")
+		}
+	}
+	//fmt.Println(max, "\n")
+	for x := 0; x < max; x++ {
+		for y := 0; y < total; y++ {
+
+			// Check if border is set
+			fmt.Fprint(t.out, ConditionString((!t.borders.Left && y == 0), SPACE, t.pColumn))
+
+			fmt.Fprintf(t.out, SPACE)
+			str := columns[y][x]
+
+			// Embedding escape sequence with column value
+			if is_esc_seq {
+				str = format(str, t.columnsParams[y])
+			}
+
+			// This would print alignment
+			// Default alignment  would use multiple configuration
+			switch t.columnsAlign[y] {
+			case ALIGN_CENTER: //
+				fmt.Fprintf(t.out, "%s", Pad(str, SPACE, t.cs[y]))
+			case ALIGN_RIGHT:
+				fmt.Fprintf(t.out, "%s", PadLeft(str, SPACE, t.cs[y]))
+			case ALIGN_LEFT:
+				fmt.Fprintf(t.out, "%s", PadRight(str, SPACE, t.cs[y]))
+			default:
+				if decimal.MatchString(strings.TrimSpace(str)) || percent.MatchString(strings.TrimSpace(str)) {
+					fmt.Fprintf(t.out, "%s", PadLeft(str, SPACE, t.cs[y]))
+				} else {
+					fmt.Fprintf(t.out, "%s", PadRight(str, SPACE, t.cs[y]))
+
+					// TODO Custom alignment per column
+					//if max == 1 || pads[y] > 0 {
+					//	fmt.Fprintf(t.out, "%s", Pad(str, SPACE, t.cs[y]))
+					//} else {
+					//	fmt.Fprintf(t.out, "%s", PadRight(str, SPACE, t.cs[y]))
+					//}
+
+				}
+			}
+			fmt.Fprintf(t.out, SPACE)
+		}
+		// Check if border is set
+		// Replace with space if not set
+		fmt.Fprint(t.out, ConditionString(t.borders.Left, t.pColumn, SPACE))
+		fmt.Fprint(t.out, t.newLine)
+	}
+
+	if t.rowLine {
+		t.printLine(true)
+	}
+}
+
+// Print the rows of the table and merge the cells that are identical
+func (t *Table) printRowsMergeCells() {
+	var previousLine []string
+	var displayCellBorder []bool
+	var tmpWriter bytes.Buffer
+	for i, lines := range t.lines {
+		// We store the display of the current line in a tmp writer, as we need to know which border needs to be print above
+		previousLine, displayCellBorder = t.printRowMergeCells(&tmpWriter, lines, i, previousLine)
+		if i > 0 { //We don't need to print borders above first line
+			if t.rowLine {
+				t.printLineOptionalCellSeparators(true, displayCellBorder)
+			}
+		}
+		tmpWriter.WriteTo(t.out)
+	}
+	//Print the end of the table
+	if t.rowLine {
+		t.printLine(true)
+	}
+}
+
+// Print Row Information to a writer and merge identical cells.
+// Adjust column alignment based on type
+
+func (t *Table) printRowMergeCells(writer io.Writer, columns [][]string, rowIdx int, previousLine []string) ([]string, []bool) {
+	// Get Maximum Height
+	max := t.rs[rowIdx]
+	total := len(columns)
+
+	// Pad Each Height
+	pads := []int{}
+
+	for i, line := range columns {
+		length := len(line)
+		pad := max - length
+		pads = append(pads, pad)
+		for n := 0; n < pad; n++ {
+			columns[i] = append(columns[i], "  ")
+		}
+	}
+
+	var displayCellBorder []bool
+	t.fillAlignment(total)
+	for x := 0; x < max; x++ {
+		for y := 0; y < total; y++ {
+
+			// Check if border is set
+			fmt.Fprint(writer, ConditionString((!t.borders.Left && y == 0), SPACE, t.pColumn))
+
+			fmt.Fprintf(writer, SPACE)
+
+			str := columns[y][x]
+
+			if t.autoMergeCells {
+				//Store the full line to merge mutli-lines cells
+				fullLine := strings.Join(columns[y], " ")
+				if len(previousLine) > y && fullLine == previousLine[y] && fullLine != "" {
+					// If this cell is identical to the one above but not empty, we don't display the border and keep the cell empty.
+					displayCellBorder = append(displayCellBorder, false)
+					str = ""
+				} else {
+					// First line or different content, keep the content and print the cell border
+					displayCellBorder = append(displayCellBorder, true)
+				}
+			}
+
+			// This would print alignment
+			// Default alignment  would use multiple configuration
+			switch t.columnsAlign[y] {
+			case ALIGN_CENTER: //
+				fmt.Fprintf(writer, "%s", Pad(str, SPACE, t.cs[y]))
+			case ALIGN_RIGHT:
+				fmt.Fprintf(writer, "%s", PadLeft(str, SPACE, t.cs[y]))
+			case ALIGN_LEFT:
+				fmt.Fprintf(writer, "%s", PadRight(str, SPACE, t.cs[y]))
+			default:
+				if decimal.MatchString(strings.TrimSpace(str)) || percent.MatchString(strings.TrimSpace(str)) {
+					fmt.Fprintf(writer, "%s", PadLeft(str, SPACE, t.cs[y]))
+				} else {
+					fmt.Fprintf(writer, "%s", PadRight(str, SPACE, t.cs[y]))
+				}
+			}
+			fmt.Fprintf(writer, SPACE)
+		}
+		// Check if border is set
+		// Replace with space if not set
+		fmt.Fprint(writer, ConditionString(t.borders.Left, t.pColumn, SPACE))
+		fmt.Fprint(writer, t.newLine)
+	}
+
+	//The new previous line is the current one
+	previousLine = make([]string, total)
+	for y := 0; y < total; y++ {
+		previousLine[y] = strings.Join(columns[y], " ") //Store the full line for multi-lines cells
+	}
+	//Returns the newly added line and wether or not a border should be displayed above.
+	return previousLine, displayCellBorder
+}
+
+func (t *Table) parseDimension(str string, colKey, rowKey int) []string {
+	var (
+		raw      []string
+		maxWidth int
+	)
+
+	raw = getLines(str)
+	maxWidth = 0
+	for _, line := range raw {
+		if w := DisplayWidth(line); w > maxWidth {
+			maxWidth = w
+		}
+	}
+
+	// If wrapping, ensure that all paragraphs in the cell fit in the
+	// specified width.
+	if t.autoWrap {
+		// If there's a maximum allowed width for wrapping, use that.
+		if maxWidth > t.mW {
+			maxWidth = t.mW
+		}
+
+		// In the process of doing so, we need to recompute maxWidth. This
+		// is because perhaps a word in the cell is longer than the
+		// allowed maximum width in t.mW.
+		newMaxWidth := maxWidth
+		newRaw := make([]string, 0, len(raw))
+
+		if t.reflowText {
+			// Make a single paragraph of everything.
+			raw = []string{strings.Join(raw, " ")}
+		}
+		for i, para := range raw {
+			paraLines, _ := WrapString(para, maxWidth)
+			for _, line := range paraLines {
+				if w := DisplayWidth(line); w > newMaxWidth {
+					newMaxWidth = w
+				}
+			}
+			if i > 0 {
+				newRaw = append(newRaw, " ")
+			}
+			newRaw = append(newRaw, paraLines...)
+		}
+		raw = newRaw
+		maxWidth = newMaxWidth
+	}
+
+	// Store the new known maximum width.
+	v, ok := t.cs[colKey]
+	if !ok || v < maxWidth || v == 0 {
+		t.cs[colKey] = maxWidth
+	}
+
+	// Remember the number of lines for the row printer.
+	h := len(raw)
+	v, ok = t.rs[rowKey]
+
+	if !ok || v < h || v == 0 {
+		t.rs[rowKey] = h
+	}
+	//fmt.Printf("Raw %+v %d\n", raw, len(raw))
+	return raw
+}
diff --git a/vendor/github.com/olekukonko/tablewriter/table_test.go b/vendor/github.com/olekukonko/tablewriter/table_test.go
new file mode 100644
index 0000000..39fdb3c
--- /dev/null
+++ b/vendor/github.com/olekukonko/tablewriter/table_test.go
@@ -0,0 +1,1055 @@
+// Copyright 2014 Oleku Konko All rights reserved.
+// Use of this source code is governed by a MIT
+// license that can be found in the LICENSE file.
+
+// This module is a Table Writer  API for the Go Programming Language.
+// The protocols were written in pure Go and works on windows and unix systems
+
+package tablewriter
+
+import (
+	"bytes"
+	"fmt"
+	"io"
+	"os"
+	"reflect"
+	"strings"
+	"testing"
+)
+
+func checkEqual(t *testing.T, got, want interface{}, msgs ...interface{}) {
+	if !reflect.DeepEqual(got, want) {
+		buf := bytes.Buffer{}
+		buf.WriteString("got:\n[%v]\nwant:\n[%v]\n")
+		for _, v := range msgs {
+			buf.WriteString(v.(string))
+		}
+		t.Errorf(buf.String(), got, want)
+	}
+}
+
+func ExampleShort() {
+	data := [][]string{
+		{"A", "The Good", "500"},
+		{"B", "The Very very Bad Man", "288"},
+		{"C", "The Ugly", "120"},
+		{"D", "The Gopher", "800"},
+	}
+
+	table := NewWriter(os.Stdout)
+	table.SetHeader([]string{"Name", "Sign", "Rating"})
+
+	for _, v := range data {
+		table.Append(v)
+	}
+	table.Render()
+
+	// Output: +------+-----------------------+--------+
+	// | NAME |         SIGN          | RATING |
+	// +------+-----------------------+--------+
+	// | A    | The Good              |    500 |
+	// | B    | The Very very Bad Man |    288 |
+	// | C    | The Ugly              |    120 |
+	// | D    | The Gopher            |    800 |
+	// +------+-----------------------+--------+
+}
+
+func ExampleLong() {
+	data := [][]string{
+		{"Learn East has computers with adapted keyboards with enlarged print etc", "  Some Data  ", " Another Data"},
+		{"Instead of lining up the letters all ", "the way across, he splits the keyboard in two", "Like most ergonomic keyboards", "See Data"},
+	}
+
+	table := NewWriter(os.Stdout)
+	table.SetHeader([]string{"Name", "Sign", "Rating"})
+	table.SetCenterSeparator("*")
+	table.SetRowSeparator("=")
+
+	for _, v := range data {
+		table.Append(v)
+	}
+	table.Render()
+
+	// Output: *================================*================================*===============================*==========*
+	// |              NAME              |              SIGN              |            RATING             |          |
+	// *================================*================================*===============================*==========*
+	// | Learn East has computers       |   Some Data                    |  Another Data                 |
+	// | with adapted keyboards with    |                                |                               |
+	// | enlarged print etc             |                                |                               |
+	// | Instead of lining up the       | the way across, he splits the  | Like most ergonomic keyboards | See Data |
+	// | letters all                    | keyboard in two                |                               |          |
+	// *================================*================================*===============================*==========*
+}
+
+func ExampleCSV() {
+	table, _ := NewCSV(os.Stdout, "testdata/test.csv", true)
+	table.SetCenterSeparator("*")
+	table.SetRowSeparator("=")
+
+	table.Render()
+
+	// Output: *============*===========*=========*
+	// | FIRST NAME | LAST NAME |   SSN   |
+	// *============*===========*=========*
+	// | John       | Barry     |  123456 |
+	// | Kathy      | Smith     |  687987 |
+	// | Bob        | McCornick | 3979870 |
+	// *============*===========*=========*
+}
+
+// TestNumLines to test the numbers of lines
+func TestNumLines(t *testing.T) {
+	data := [][]string{
+		{"A", "The Good", "500"},
+		{"B", "The Very very Bad Man", "288"},
+		{"C", "The Ugly", "120"},
+		{"D", "The Gopher", "800"},
+	}
+
+	buf := &bytes.Buffer{}
+	table := NewWriter(buf)
+	table.SetHeader([]string{"Name", "Sign", "Rating"})
+
+	for i, v := range data {
+		table.Append(v)
+		checkEqual(t, table.NumLines(), i+1, "Number of lines failed")
+	}
+
+	checkEqual(t, table.NumLines(), len(data), "Number of lines failed")
+}
+
+func TestCSVInfo(t *testing.T) {
+	buf := &bytes.Buffer{}
+	table, err := NewCSV(buf, "testdata/test_info.csv", true)
+	if err != nil {
+		t.Error(err)
+		return
+	}
+	table.SetAlignment(ALIGN_LEFT)
+	table.SetBorder(false)
+	table.Render()
+
+	got := buf.String()
+	want := `   FIELD   |     TYPE     | NULL | KEY | DEFAULT |     EXTRA       
++----------+--------------+------+-----+---------+----------------+
+  user_id  | smallint(5)  | NO   | PRI | NULL    | auto_increment  
+  username | varchar(10)  | NO   |     | NULL    |                 
+  password | varchar(100) | NO   |     | NULL    |                 
+`
+	checkEqual(t, got, want, "CSV info failed")
+}
+
+func TestCSVSeparator(t *testing.T) {
+	buf := &bytes.Buffer{}
+	table, err := NewCSV(buf, "testdata/test.csv", true)
+	if err != nil {
+		t.Error(err)
+		return
+	}
+	table.SetRowLine(true)
+	table.SetCenterSeparator("+")
+	table.SetColumnSeparator("|")
+	table.SetRowSeparator("-")
+	table.SetAlignment(ALIGN_LEFT)
+	table.Render()
+
+	want := `+------------+-----------+---------+
+| FIRST NAME | LAST NAME |   SSN   |
++------------+-----------+---------+
+| John       | Barry     | 123456  |
++------------+-----------+---------+
+| Kathy      | Smith     | 687987  |
++------------+-----------+---------+
+| Bob        | McCornick | 3979870 |
++------------+-----------+---------+
+`
+
+	checkEqual(t, buf.String(), want, "CSV info failed")
+}
+
+func TestNoBorder(t *testing.T) {
+	data := [][]string{
+		{"1/1/2014", "Domain name", "2233", "$10.98"},
+		{"1/1/2014", "January Hosting", "2233", "$54.95"},
+		{"", "    (empty)\n    (empty)", "", ""},
+		{"1/4/2014", "February Hosting", "2233", "$51.00"},
+		{"1/4/2014", "February Extra Bandwidth", "2233", "$30.00"},
+		{"1/4/2014", "    (Discount)", "2233", "-$1.00"},
+	}
+
+	var buf bytes.Buffer
+	table := NewWriter(&buf)
+	table.SetAutoWrapText(false)
+	table.SetHeader([]string{"Date", "Description", "CV2", "Amount"})
+	table.SetFooter([]string{"", "", "Total", "$145.93"}) // Add Footer
+	table.SetBorder(false)                                // Set Border to false
+	table.AppendBulk(data)                                // Add Bulk Data
+	table.Render()
+
+	want := `    DATE   |       DESCRIPTION        |  CV2  | AMOUNT   
++----------+--------------------------+-------+---------+
+  1/1/2014 | Domain name              |  2233 | $10.98   
+  1/1/2014 | January Hosting          |  2233 | $54.95   
+           |     (empty)              |       |          
+           |     (empty)              |       |          
+  1/4/2014 | February Hosting         |  2233 | $51.00   
+  1/4/2014 | February Extra Bandwidth |  2233 | $30.00   
+  1/4/2014 |     (Discount)           |  2233 | -$1.00   
++----------+--------------------------+-------+---------+
+                                        TOTAL | $145 93  
+                                      +-------+---------+
+`
+
+	checkEqual(t, buf.String(), want, "border table rendering failed")
+}
+
+func TestWithBorder(t *testing.T) {
+	data := [][]string{
+		{"1/1/2014", "Domain name", "2233", "$10.98"},
+		{"1/1/2014", "January Hosting", "2233", "$54.95"},
+		{"", "    (empty)\n    (empty)", "", ""},
+		{"1/4/2014", "February Hosting", "2233", "$51.00"},
+		{"1/4/2014", "February Extra Bandwidth", "2233", "$30.00"},
+		{"1/4/2014", "    (Discount)", "2233", "-$1.00"},
+	}
+
+	var buf bytes.Buffer
+	table := NewWriter(&buf)
+	table.SetAutoWrapText(false)
+	table.SetHeader([]string{"Date", "Description", "CV2", "Amount"})
+	table.SetFooter([]string{"", "", "Total", "$145.93"}) // Add Footer
+	table.AppendBulk(data)                                // Add Bulk Data
+	table.Render()
+
+	want := `+----------+--------------------------+-------+---------+
+|   DATE   |       DESCRIPTION        |  CV2  | AMOUNT  |
++----------+--------------------------+-------+---------+
+| 1/1/2014 | Domain name              |  2233 | $10.98  |
+| 1/1/2014 | January Hosting          |  2233 | $54.95  |
+|          |     (empty)              |       |         |
+|          |     (empty)              |       |         |
+| 1/4/2014 | February Hosting         |  2233 | $51.00  |
+| 1/4/2014 | February Extra Bandwidth |  2233 | $30.00  |
+| 1/4/2014 |     (Discount)           |  2233 | -$1.00  |
++----------+--------------------------+-------+---------+
+|                                       TOTAL | $145 93 |
++----------+--------------------------+-------+---------+
+`
+
+	checkEqual(t, buf.String(), want, "border table rendering failed")
+}
+
+func TestPrintingInMarkdown(t *testing.T) {
+	data := [][]string{
+		{"1/1/2014", "Domain name", "2233", "$10.98"},
+		{"1/1/2014", "January Hosting", "2233", "$54.95"},
+		{"1/4/2014", "February Hosting", "2233", "$51.00"},
+		{"1/4/2014", "February Extra Bandwidth", "2233", "$30.00"},
+	}
+
+	var buf bytes.Buffer
+	table := NewWriter(&buf)
+	table.SetHeader([]string{"Date", "Description", "CV2", "Amount"})
+	table.AppendBulk(data) // Add Bulk Data
+	table.SetBorders(Border{Left: true, Top: false, Right: true, Bottom: false})
+	table.SetCenterSeparator("|")
+	table.Render()
+
+	want := `|   DATE   |       DESCRIPTION        | CV2  | AMOUNT |
+|----------|--------------------------|------|--------|
+| 1/1/2014 | Domain name              | 2233 | $10.98 |
+| 1/1/2014 | January Hosting          | 2233 | $54.95 |
+| 1/4/2014 | February Hosting         | 2233 | $51.00 |
+| 1/4/2014 | February Extra Bandwidth | 2233 | $30.00 |
+`
+	checkEqual(t, buf.String(), want, "border table rendering failed")
+}
+
+func TestPrintHeading(t *testing.T) {
+	var buf bytes.Buffer
+	table := NewWriter(&buf)
+	table.SetHeader([]string{"1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c"})
+	table.printHeading()
+	want := `| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C |
++---+---+---+---+---+---+---+---+---+---+---+---+
+`
+	checkEqual(t, buf.String(), want, "header rendering failed")
+}
+
+func TestPrintHeadingWithoutAutoFormat(t *testing.T) {
+	var buf bytes.Buffer
+	table := NewWriter(&buf)
+	table.SetHeader([]string{"1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c"})
+	table.SetAutoFormatHeaders(false)
+	table.printHeading()
+	want := `| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | a | b | c |
++---+---+---+---+---+---+---+---+---+---+---+---+
+`
+	checkEqual(t, buf.String(), want, "header rendering failed")
+}
+
+func TestPrintFooter(t *testing.T) {
+	var buf bytes.Buffer
+	table := NewWriter(&buf)
+	table.SetHeader([]string{"1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c"})
+	table.SetFooter([]string{"1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c"})
+	table.printFooter()
+	want := `| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C |
++---+---+---+---+---+---+---+---+---+---+---+---+
+`
+	checkEqual(t, buf.String(), want, "footer rendering failed")
+}
+
+func TestPrintFooterWithoutAutoFormat(t *testing.T) {
+	var buf bytes.Buffer
+	table := NewWriter(&buf)
+	table.SetAutoFormatHeaders(false)
+	table.SetHeader([]string{"1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c"})
+	table.SetFooter([]string{"1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c"})
+	table.printFooter()
+	want := `| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | a | b | c |
++---+---+---+---+---+---+---+---+---+---+---+---+
+`
+	checkEqual(t, buf.String(), want, "footer rendering failed")
+}
+
+func TestPrintShortCaption(t *testing.T) {
+	var buf bytes.Buffer
+	data := [][]string{
+		{"A", "The Good", "500"},
+		{"B", "The Very very Bad Man", "288"},
+		{"C", "The Ugly", "120"},
+		{"D", "The Gopher", "800"},
+	}
+
+	table := NewWriter(&buf)
+	table.SetHeader([]string{"Name", "Sign", "Rating"})
+	table.SetCaption(true, "Short caption.")
+
+	for _, v := range data {
+		table.Append(v)
+	}
+	table.Render()
+
+	want := `+------+-----------------------+--------+
+| NAME |         SIGN          | RATING |
++------+-----------------------+--------+
+| A    | The Good              |    500 |
+| B    | The Very very Bad Man |    288 |
+| C    | The Ugly              |    120 |
+| D    | The Gopher            |    800 |
++------+-----------------------+--------+
+Short caption.
+`
+	checkEqual(t, buf.String(), want, "long caption for short example rendering failed")
+}
+
+func TestPrintLongCaptionWithShortExample(t *testing.T) {
+	var buf bytes.Buffer
+	data := [][]string{
+		{"A", "The Good", "500"},
+		{"B", "The Very very Bad Man", "288"},
+		{"C", "The Ugly", "120"},
+		{"D", "The Gopher", "800"},
+	}
+
+	table := NewWriter(&buf)
+	table.SetHeader([]string{"Name", "Sign", "Rating"})
+	table.SetCaption(true, "This is a very long caption. The text should wrap. If not, we have a problem that needs to be solved.")
+
+	for _, v := range data {
+		table.Append(v)
+	}
+	table.Render()
+
+	want := `+------+-----------------------+--------+
+| NAME |         SIGN          | RATING |
++------+-----------------------+--------+
+| A    | The Good              |    500 |
+| B    | The Very very Bad Man |    288 |
+| C    | The Ugly              |    120 |
+| D    | The Gopher            |    800 |
++------+-----------------------+--------+
+This is a very long caption. The text
+should wrap. If not, we have a problem
+that needs to be solved.
+`
+	checkEqual(t, buf.String(), want, "long caption for short example rendering failed")
+}
+
+func TestPrintCaptionWithFooter(t *testing.T) {
+	data := [][]string{
+		{"1/1/2014", "Domain name", "2233", "$10.98"},
+		{"1/1/2014", "January Hosting", "2233", "$54.95"},
+		{"1/4/2014", "February Hosting", "2233", "$51.00"},
+		{"1/4/2014", "February Extra Bandwidth", "2233", "$30.00"},
+	}
+
+	var buf bytes.Buffer
+	table := NewWriter(&buf)
+	table.SetHeader([]string{"Date", "Description", "CV2", "Amount"})
+	table.SetFooter([]string{"", "", "Total", "$146.93"})                                                  // Add Footer
+	table.SetCaption(true, "This is a very long caption. The text should wrap to the width of the table.") // Add caption
+	table.SetBorder(false)                                                                                 // Set Border to false
+	table.AppendBulk(data)                                                                                 // Add Bulk Data
+	table.Render()
+
+	want := `    DATE   |       DESCRIPTION        |  CV2  | AMOUNT   
++----------+--------------------------+-------+---------+
+  1/1/2014 | Domain name              |  2233 | $10.98   
+  1/1/2014 | January Hosting          |  2233 | $54.95   
+  1/4/2014 | February Hosting         |  2233 | $51.00   
+  1/4/2014 | February Extra Bandwidth |  2233 | $30.00   
++----------+--------------------------+-------+---------+
+                                        TOTAL | $146 93  
+                                      +-------+---------+
+This is a very long caption. The text should wrap to the
+width of the table.
+`
+	checkEqual(t, buf.String(), want, "border table rendering failed")
+}
+
+func TestPrintLongCaptionWithLongExample(t *testing.T) {
+	var buf bytes.Buffer
+	data := [][]string{
+		{"Learn East has computers with adapted keyboards with enlarged print etc", "Some Data", "Another Data"},
+		{"Instead of lining up the letters all", "the way across, he splits the keyboard in two", "Like most ergonomic keyboards"},
+	}
+
+	table := NewWriter(&buf)
+	table.SetCaption(true, "This is a very long caption. The text should wrap. If not, we have a problem that needs to be solved.")
+	table.SetHeader([]string{"Name", "Sign", "Rating"})
+
+	for _, v := range data {
+		table.Append(v)
+	}
+	table.Render()
+
+	want := `+--------------------------------+--------------------------------+-------------------------------+
+|              NAME              |              SIGN              |            RATING             |
++--------------------------------+--------------------------------+-------------------------------+
+| Learn East has computers       | Some Data                      | Another Data                  |
+| with adapted keyboards with    |                                |                               |
+| enlarged print etc             |                                |                               |
+| Instead of lining up the       | the way across, he splits the  | Like most ergonomic keyboards |
+| letters all                    | keyboard in two                |                               |
++--------------------------------+--------------------------------+-------------------------------+
+This is a very long caption. The text should wrap. If not, we have a problem that needs to be
+solved.
+`
+	checkEqual(t, buf.String(), want, "long caption for long example rendering failed")
+}
+
+func Example_autowrap() {
+	var multiline = `A multiline
+string with some lines being really long.`
+
+	const (
+		testRow = iota
+		testHeader
+		testFooter
+		testFooter2
+	)
+	for mode := testRow; mode <= testFooter2; mode++ {
+		for _, autoFmt := range []bool{false, true} {
+			if mode == testRow && autoFmt {
+				// Nothing special to test, skip
+				continue
+			}
+			for _, autoWrap := range []bool{false, true} {
+				for _, reflow := range []bool{false, true} {
+					if !autoWrap && reflow {
+						// Invalid configuration, skip
+						continue
+					}
+					fmt.Println("mode", mode, "autoFmt", autoFmt, "autoWrap", autoWrap, "reflow", reflow)
+					t := NewWriter(os.Stdout)
+					t.SetAutoFormatHeaders(autoFmt)
+					t.SetAutoWrapText(autoWrap)
+					t.SetReflowDuringAutoWrap(reflow)
+					if mode == testHeader {
+						t.SetHeader([]string{"woo", multiline})
+					} else {
+						t.SetHeader([]string{"woo", "waa"})
+					}
+					if mode == testRow {
+						t.Append([]string{"woo", multiline})
+					} else {
+						t.Append([]string{"woo", "waa"})
+					}
+					if mode == testFooter {
+						t.SetFooter([]string{"woo", multiline})
+					} else if mode == testFooter2 {
+						t.SetFooter([]string{"", multiline})
+					} else {
+						t.SetFooter([]string{"woo", "waa"})
+					}
+					t.Render()
+				}
+			}
+		}
+		fmt.Println()
+	}
+
+	// Output:
+	// mode 0 autoFmt false autoWrap false reflow false
+	// +-----+-------------------------------------------+
+	// | woo |                    waa                    |
+	// +-----+-------------------------------------------+
+	// | woo | A multiline                               |
+	// |     | string with some lines being really long. |
+	// +-----+-------------------------------------------+
+	// | woo |                    waa                    |
+	// +-----+-------------------------------------------+
+	// mode 0 autoFmt false autoWrap true reflow false
+	// +-----+--------------------------------+
+	// | woo |              waa               |
+	// +-----+--------------------------------+
+	// | woo | A multiline                    |
+	// |     |                                |
+	// |     | string with some lines being   |
+	// |     | really long.                   |
+	// +-----+--------------------------------+
+	// | woo |              waa               |
+	// +-----+--------------------------------+
+	// mode 0 autoFmt false autoWrap true reflow true
+	// +-----+--------------------------------+
+	// | woo |              waa               |
+	// +-----+--------------------------------+
+	// | woo | A multiline string with some   |
+	// |     | lines being really long.       |
+	// +-----+--------------------------------+
+	// | woo |              waa               |
+	// +-----+--------------------------------+
+	//
+	// mode 1 autoFmt false autoWrap false reflow false
+	// +-----+-------------------------------------------+
+	// | woo |                A multiline                |
+	// |     | string with some lines being really long. |
+	// +-----+-------------------------------------------+
+	// | woo | waa                                       |
+	// +-----+-------------------------------------------+
+	// | woo |                    waa                    |
+	// +-----+-------------------------------------------+
+	// mode 1 autoFmt false autoWrap true reflow false
+	// +-----+--------------------------------+
+	// | woo |          A multiline           |
+	// |     |                                |
+	// |     |  string with some lines being  |
+	// |     |          really long.          |
+	// +-----+--------------------------------+
+	// | woo | waa                            |
+	// +-----+--------------------------------+
+	// | woo |              waa               |
+	// +-----+--------------------------------+
+	// mode 1 autoFmt false autoWrap true reflow true
+	// +-----+--------------------------------+
+	// | woo |  A multiline string with some  |
+	// |     |    lines being really long.    |
+	// +-----+--------------------------------+
+	// | woo | waa                            |
+	// +-----+--------------------------------+
+	// | woo |              waa               |
+	// +-----+--------------------------------+
+	// mode 1 autoFmt true autoWrap false reflow false
+	// +-----+-------------------------------------------+
+	// | WOO |                A MULTILINE                |
+	// |     | STRING WITH SOME LINES BEING REALLY LONG  |
+	// +-----+-------------------------------------------+
+	// | woo | waa                                       |
+	// +-----+-------------------------------------------+
+	// | WOO |                    WAA                    |
+	// +-----+-------------------------------------------+
+	// mode 1 autoFmt true autoWrap true reflow false
+	// +-----+--------------------------------+
+	// | WOO |          A MULTILINE           |
+	// |     |                                |
+	// |     |  STRING WITH SOME LINES BEING  |
+	// |     |          REALLY LONG           |
+	// +-----+--------------------------------+
+	// | woo | waa                            |
+	// +-----+--------------------------------+
+	// | WOO |              WAA               |
+	// +-----+--------------------------------+
+	// mode 1 autoFmt true autoWrap true reflow true
+	// +-----+--------------------------------+
+	// | WOO |  A MULTILINE STRING WITH SOME  |
+	// |     |    LINES BEING REALLY LONG     |
+	// +-----+--------------------------------+
+	// | woo | waa                            |
+	// +-----+--------------------------------+
+	// | WOO |              WAA               |
+	// +-----+--------------------------------+
+	//
+	// mode 2 autoFmt false autoWrap false reflow false
+	// +-----+-------------------------------------------+
+	// | woo |                    waa                    |
+	// +-----+-------------------------------------------+
+	// | woo | waa                                       |
+	// +-----+-------------------------------------------+
+	// | woo |                A multiline                |
+	// |     | string with some lines being really long. |
+	// +-----+-------------------------------------------+
+	// mode 2 autoFmt false autoWrap true reflow false
+	// +-----+--------------------------------+
+	// | woo |              waa               |
+	// +-----+--------------------------------+
+	// | woo | waa                            |
+	// +-----+--------------------------------+
+	// | woo |          A multiline           |
+	// |     |                                |
+	// |     |  string with some lines being  |
+	// |     |          really long.          |
+	// +-----+--------------------------------+
+	// mode 2 autoFmt false autoWrap true reflow true
+	// +-----+--------------------------------+
+	// | woo |              waa               |
+	// +-----+--------------------------------+
+	// | woo | waa                            |
+	// +-----+--------------------------------+
+	// | woo |  A multiline string with some  |
+	// |     |    lines being really long.    |
+	// +-----+--------------------------------+
+	// mode 2 autoFmt true autoWrap false reflow false
+	// +-----+-------------------------------------------+
+	// | WOO |                    WAA                    |
+	// +-----+-------------------------------------------+
+	// | woo | waa                                       |
+	// +-----+-------------------------------------------+
+	// | WOO |                A MULTILINE                |
+	// |     | STRING WITH SOME LINES BEING REALLY LONG  |
+	// +-----+-------------------------------------------+
+	// mode 2 autoFmt true autoWrap true reflow false
+	// +-----+--------------------------------+
+	// | WOO |              WAA               |
+	// +-----+--------------------------------+
+	// | woo | waa                            |
+	// +-----+--------------------------------+
+	// | WOO |          A MULTILINE           |
+	// |     |                                |
+	// |     |  STRING WITH SOME LINES BEING  |
+	// |     |          REALLY LONG           |
+	// +-----+--------------------------------+
+	// mode 2 autoFmt true autoWrap true reflow true
+	// +-----+--------------------------------+
+	// | WOO |              WAA               |
+	// +-----+--------------------------------+
+	// | woo | waa                            |
+	// +-----+--------------------------------+
+	// | WOO |  A MULTILINE STRING WITH SOME  |
+	// |     |    LINES BEING REALLY LONG     |
+	// +-----+--------------------------------+
+	//
+	// mode 3 autoFmt false autoWrap false reflow false
+	// +-----+-------------------------------------------+
+	// | woo |                    waa                    |
+	// +-----+-------------------------------------------+
+	// | woo | waa                                       |
+	// +-----+-------------------------------------------+
+	// |                      A multiline                |
+	// |       string with some lines being really long. |
+	// +-----+-------------------------------------------+
+	// mode 3 autoFmt false autoWrap true reflow false
+	// +-----+--------------------------------+
+	// | woo |              waa               |
+	// +-----+--------------------------------+
+	// | woo | waa                            |
+	// +-----+--------------------------------+
+	// |                A multiline           |
+	// |                                      |
+	// |        string with some lines being  |
+	// |                really long.          |
+	// +-----+--------------------------------+
+	// mode 3 autoFmt false autoWrap true reflow true
+	// +-----+--------------------------------+
+	// | woo |              waa               |
+	// +-----+--------------------------------+
+	// | woo | waa                            |
+	// +-----+--------------------------------+
+	// |        A multiline string with some  |
+	// |          lines being really long.    |
+	// +-----+--------------------------------+
+	// mode 3 autoFmt true autoWrap false reflow false
+	// +-----+-------------------------------------------+
+	// | WOO |                    WAA                    |
+	// +-----+-------------------------------------------+
+	// | woo | waa                                       |
+	// +-----+-------------------------------------------+
+	// |                      A MULTILINE                |
+	// |       STRING WITH SOME LINES BEING REALLY LONG  |
+	// +-----+-------------------------------------------+
+	// mode 3 autoFmt true autoWrap true reflow false
+	// +-----+--------------------------------+
+	// | WOO |              WAA               |
+	// +-----+--------------------------------+
+	// | woo | waa                            |
+	// +-----+--------------------------------+
+	// |                A MULTILINE           |
+	// |                                      |
+	// |        STRING WITH SOME LINES BEING  |
+	// |                REALLY LONG           |
+	// +-----+--------------------------------+
+	// mode 3 autoFmt true autoWrap true reflow true
+	// +-----+--------------------------------+
+	// | WOO |              WAA               |
+	// +-----+--------------------------------+
+	// | woo | waa                            |
+	// +-----+--------------------------------+
+	// |        A MULTILINE STRING WITH SOME  |
+	// |          LINES BEING REALLY LONG     |
+	// +-----+--------------------------------+
+}
+
+func TestPrintLine(t *testing.T) {
+	header := make([]string, 12)
+	val := " "
+	want := ""
+	for i := range header {
+		header[i] = val
+		want = fmt.Sprintf("%s+-%s-", want, strings.Replace(val, " ", "-", -1))
+		val = val + " "
+	}
+	want = want + "+"
+	var buf bytes.Buffer
+	table := NewWriter(&buf)
+	table.SetHeader(header)
+	table.printLine(false)
+	checkEqual(t, buf.String(), want, "line rendering failed")
+}
+
+func TestAnsiStrip(t *testing.T) {
+	header := make([]string, 12)
+	val := " "
+	want := ""
+	for i := range header {
+		header[i] = "\033[43;30m" + val + "\033[00m"
+		want = fmt.Sprintf("%s+-%s-", want, strings.Replace(val, " ", "-", -1))
+		val = val + " "
+	}
+	want = want + "+"
+	var buf bytes.Buffer
+	table := NewWriter(&buf)
+	table.SetHeader(header)
+	table.printLine(false)
+	checkEqual(t, buf.String(), want, "line rendering failed")
+}
+
+func NewCustomizedTable(out io.Writer) *Table {
+	table := NewWriter(out)
+	table.SetCenterSeparator("")
+	table.SetColumnSeparator("")
+	table.SetRowSeparator("")
+	table.SetBorder(false)
+	table.SetAlignment(ALIGN_LEFT)
+	table.SetHeader([]string{})
+	return table
+}
+
+func TestSubclass(t *testing.T) {
+	buf := new(bytes.Buffer)
+	table := NewCustomizedTable(buf)
+
+	data := [][]string{
+		{"A", "The Good", "500"},
+		{"B", "The Very very Bad Man", "288"},
+		{"C", "The Ugly", "120"},
+		{"D", "The Gopher", "800"},
+	}
+
+	for _, v := range data {
+		table.Append(v)
+	}
+	table.Render()
+
+	want := `  A  The Good               500  
+  B  The Very very Bad Man  288  
+  C  The Ugly               120  
+  D  The Gopher             800  
+`
+	checkEqual(t, buf.String(), want, "test subclass failed")
+}
+
+func TestAutoMergeRows(t *testing.T) {
+	data := [][]string{
+		{"A", "The Good", "500"},
+		{"A", "The Very very Bad Man", "288"},
+		{"B", "The Very very Bad Man", "120"},
+		{"B", "The Very very Bad Man", "200"},
+	}
+	var buf bytes.Buffer
+	table := NewWriter(&buf)
+	table.SetHeader([]string{"Name", "Sign", "Rating"})
+
+	for _, v := range data {
+		table.Append(v)
+	}
+	table.SetAutoMergeCells(true)
+	table.Render()
+	want := `+------+-----------------------+--------+
+| NAME |         SIGN          | RATING |
++------+-----------------------+--------+
+| A    | The Good              |    500 |
+|      | The Very very Bad Man |    288 |
+| B    |                       |    120 |
+|      |                       |    200 |
++------+-----------------------+--------+
+`
+	got := buf.String()
+	if got != want {
+		t.Errorf("\ngot:\n%s\nwant:\n%s\n", got, want)
+	}
+
+	buf.Reset()
+	table = NewWriter(&buf)
+	table.SetHeader([]string{"Name", "Sign", "Rating"})
+
+	for _, v := range data {
+		table.Append(v)
+	}
+	table.SetAutoMergeCells(true)
+	table.SetRowLine(true)
+	table.Render()
+	want = `+------+-----------------------+--------+
+| NAME |         SIGN          | RATING |
++------+-----------------------+--------+
+| A    | The Good              |    500 |
++      +-----------------------+--------+
+|      | The Very very Bad Man |    288 |
++------+                       +--------+
+| B    |                       |    120 |
++      +                       +--------+
+|      |                       |    200 |
++------+-----------------------+--------+
+`
+	checkEqual(t, buf.String(), want)
+
+	buf.Reset()
+	table = NewWriter(&buf)
+	table.SetHeader([]string{"Name", "Sign", "Rating"})
+
+	dataWithlongText := [][]string{
+		{"A", "The Good", "500"},
+		{"A", "The Very very very very very Bad Man", "288"},
+		{"B", "The Very very very very very Bad Man", "120"},
+		{"C", "The Very very Bad Man", "200"},
+	}
+	table.AppendBulk(dataWithlongText)
+	table.SetAutoMergeCells(true)
+	table.SetRowLine(true)
+	table.Render()
+	want = `+------+--------------------------------+--------+
+| NAME |              SIGN              | RATING |
++------+--------------------------------+--------+
+| A    | The Good                       |    500 |
++------+--------------------------------+--------+
+| A    | The Very very very very very   |    288 |
+|      | Bad Man                        |        |
++------+                                +--------+
+| B    |                                |    120 |
+|      |                                |        |
++------+--------------------------------+--------+
+| C    | The Very very Bad Man          |    200 |
++------+--------------------------------+--------+
+`
+	checkEqual(t, buf.String(), want)
+}
+
+func TestClearRows(t *testing.T) {
+	data := [][]string{
+		{"1/1/2014", "Domain name", "2233", "$10.98"},
+	}
+
+	var buf bytes.Buffer
+	table := NewWriter(&buf)
+	table.SetAutoWrapText(false)
+	table.SetHeader([]string{"Date", "Description", "CV2", "Amount"})
+	table.SetFooter([]string{"", "", "Total", "$145.93"}) // Add Footer
+	table.AppendBulk(data)                                // Add Bulk Data
+	table.Render()
+
+	originalWant := `+----------+-------------+-------+---------+
+|   DATE   | DESCRIPTION |  CV2  | AMOUNT  |
++----------+-------------+-------+---------+
+| 1/1/2014 | Domain name |  2233 | $10.98  |
++----------+-------------+-------+---------+
+|                          TOTAL | $145 93 |
++----------+-------------+-------+---------+
+`
+	want := originalWant
+
+	checkEqual(t, buf.String(), want, "table clear rows failed")
+
+	buf.Reset()
+	table.ClearRows()
+	table.Render()
+
+	want = `+----------+-------------+-------+---------+
+|   DATE   | DESCRIPTION |  CV2  | AMOUNT  |
++----------+-------------+-------+---------+
++----------+-------------+-------+---------+
+|                          TOTAL | $145 93 |
++----------+-------------+-------+---------+
+`
+
+	checkEqual(t, buf.String(), want, "table clear rows failed")
+
+	buf.Reset()
+	table.AppendBulk(data) // Add Bulk Data
+	table.Render()
+
+	want = `+----------+-------------+-------+---------+
+|   DATE   | DESCRIPTION |  CV2  | AMOUNT  |
++----------+-------------+-------+---------+
+| 1/1/2014 | Domain name |  2233 | $10.98  |
++----------+-------------+-------+---------+
+|                          TOTAL | $145 93 |
++----------+-------------+-------+---------+
+`
+
+	checkEqual(t, buf.String(), want, "table clear rows failed")
+}
+
+func TestClearFooters(t *testing.T) {
+	data := [][]string{
+		{"1/1/2014", "Domain name", "2233", "$10.98"},
+	}
+
+	var buf bytes.Buffer
+	table := NewWriter(&buf)
+	table.SetAutoWrapText(false)
+	table.SetHeader([]string{"Date", "Description", "CV2", "Amount"})
+	table.SetFooter([]string{"", "", "Total", "$145.93"}) // Add Footer
+	table.AppendBulk(data)                                // Add Bulk Data
+	table.Render()
+
+	buf.Reset()
+	table.ClearFooter()
+	table.Render()
+
+	want := `+----------+-------------+-------+---------+
+|   DATE   | DESCRIPTION |  CV2  | AMOUNT  |
++----------+-------------+-------+---------+
+| 1/1/2014 | Domain name |  2233 | $10.98  |
++----------+-------------+-------+---------+
+`
+
+	checkEqual(t, buf.String(), want)
+}
+
+func TestMoreDataColumnsThanHeaders(t *testing.T) {
+	var (
+		buf    = &bytes.Buffer{}
+		table  = NewWriter(buf)
+		header = []string{"A", "B", "C"}
+		data   = [][]string{
+			{"a", "b", "c", "d"},
+			{"1", "2", "3", "4"},
+		}
+		want = `+---+---+---+---+
+| A | B | C |   |
++---+---+---+---+
+| a | b | c | d |
+| 1 | 2 | 3 | 4 |
++---+---+---+---+
+`
+	)
+	table.SetHeader(header)
+	// table.SetFooter(ctx.tableCtx.footer)
+	table.AppendBulk(data)
+	table.Render()
+
+	checkEqual(t, buf.String(), want)
+}
+
+func TestMoreFooterColumnsThanHeaders(t *testing.T) {
+	var (
+		buf    = &bytes.Buffer{}
+		table  = NewWriter(buf)
+		header = []string{"A", "B", "C"}
+		data   = [][]string{
+			{"a", "b", "c", "d"},
+			{"1", "2", "3", "4"},
+		}
+		footer = []string{"a", "b", "c", "d", "e"}
+		want   = `+---+---+---+---+---+
+| A | B | C |   |   |
++---+---+---+---+---+
+| a | b | c | d |
+| 1 | 2 | 3 | 4 |
++---+---+---+---+---+
+| A | B | C | D | E |
++---+---+---+---+---+
+`
+	)
+	table.SetHeader(header)
+	table.SetFooter(footer)
+	table.AppendBulk(data)
+	table.Render()
+
+	checkEqual(t, buf.String(), want)
+}
+
+func TestSetColMinWidth(t *testing.T) {
+	var (
+		buf    = &bytes.Buffer{}
+		table  = NewWriter(buf)
+		header = []string{"AAA", "BBB", "CCC"}
+		data   = [][]string{
+			{"a", "b", "c"},
+			{"1", "2", "3"},
+		}
+		footer = []string{"a", "b", "cccc"}
+		want   = `+-----+-----+-------+
+| AAA | BBB |  CCC  |
++-----+-----+-------+
+| a   | b   | c     |
+|   1 |   2 |     3 |
++-----+-----+-------+
+|  A  |  B  | CCCC  |
++-----+-----+-------+
+`
+	)
+	table.SetHeader(header)
+	table.SetFooter(footer)
+	table.AppendBulk(data)
+	table.SetColMinWidth(2, 5)
+	table.Render()
+
+	checkEqual(t, buf.String(), want)
+}
+
+func TestWrapString(t *testing.T) {
+	want := []string{"ああああああああああああああああああああああああ", "あああああああ"}
+	got, _ := WrapString("ああああああああああああああああああああああああ あああああああ", 55)
+	checkEqual(t, got, want)
+}
+
+func TestCustomAlign(t *testing.T) {
+	var (
+		buf    = &bytes.Buffer{}
+		table  = NewWriter(buf)
+		header = []string{"AAA", "BBB", "CCC"}
+		data   = [][]string{
+			{"a", "b", "c"},
+			{"1", "2", "3"},
+		}
+		footer = []string{"a", "b", "cccc"}
+		want   = `+-----+-----+-------+
+| AAA | BBB |  CCC  |
++-----+-----+-------+
+| a   |  b  |     c |
+| 1   |  2  |     3 |
++-----+-----+-------+
+|  A  |  B  | CCCC  |
++-----+-----+-------+
+`
+	)
+	table.SetHeader(header)
+	table.SetFooter(footer)
+	table.AppendBulk(data)
+	table.SetColMinWidth(2, 5)
+	table.SetColumnAlignment([]int{ALIGN_LEFT, ALIGN_CENTER, ALIGN_RIGHT})
+	table.Render()
+
+	checkEqual(t, buf.String(), want)
+}
diff --git a/vendor/github.com/olekukonko/tablewriter/table_with_color.go b/vendor/github.com/olekukonko/tablewriter/table_with_color.go
new file mode 100644
index 0000000..5a4a53e
--- /dev/null
+++ b/vendor/github.com/olekukonko/tablewriter/table_with_color.go
@@ -0,0 +1,134 @@
+package tablewriter
+
+import (
+	"fmt"
+	"strconv"
+	"strings"
+)
+
+const ESC = "\033"
+const SEP = ";"
+
+const (
+	BgBlackColor int = iota + 40
+	BgRedColor
+	BgGreenColor
+	BgYellowColor
+	BgBlueColor
+	BgMagentaColor
+	BgCyanColor
+	BgWhiteColor
+)
+
+const (
+	FgBlackColor int = iota + 30
+	FgRedColor
+	FgGreenColor
+	FgYellowColor
+	FgBlueColor
+	FgMagentaColor
+	FgCyanColor
+	FgWhiteColor
+)
+
+const (
+	BgHiBlackColor int = iota + 100
+	BgHiRedColor
+	BgHiGreenColor
+	BgHiYellowColor
+	BgHiBlueColor
+	BgHiMagentaColor
+	BgHiCyanColor
+	BgHiWhiteColor
+)
+
+const (
+	FgHiBlackColor int = iota + 90
+	FgHiRedColor
+	FgHiGreenColor
+	FgHiYellowColor
+	FgHiBlueColor
+	FgHiMagentaColor
+	FgHiCyanColor
+	FgHiWhiteColor
+)
+
+const (
+	Normal          = 0
+	Bold            = 1
+	UnderlineSingle = 4
+	Italic
+)
+
+type Colors []int
+
+func startFormat(seq string) string {
+	return fmt.Sprintf("%s[%sm", ESC, seq)
+}
+
+func stopFormat() string {
+	return fmt.Sprintf("%s[%dm", ESC, Normal)
+}
+
+// Making the SGR (Select Graphic Rendition) sequence.
+func makeSequence(codes []int) string {
+	codesInString := []string{}
+	for _, code := range codes {
+		codesInString = append(codesInString, strconv.Itoa(code))
+	}
+	return strings.Join(codesInString, SEP)
+}
+
+// Adding ANSI escape  sequences before and after string
+func format(s string, codes interface{}) string {
+	var seq string
+
+	switch v := codes.(type) {
+
+	case string:
+		seq = v
+	case []int:
+		seq = makeSequence(v)
+	default:
+		return s
+	}
+
+	if len(seq) == 0 {
+		return s
+	}
+	return startFormat(seq) + s + stopFormat()
+}
+
+// Adding header colors (ANSI codes)
+func (t *Table) SetHeaderColor(colors ...Colors) {
+	if t.colSize != len(colors) {
+		panic("Number of header colors must be equal to number of headers.")
+	}
+	for i := 0; i < len(colors); i++ {
+		t.headerParams = append(t.headerParams, makeSequence(colors[i]))
+	}
+}
+
+// Adding column colors (ANSI codes)
+func (t *Table) SetColumnColor(colors ...Colors) {
+	if t.colSize != len(colors) {
+		panic("Number of column colors must be equal to number of headers.")
+	}
+	for i := 0; i < len(colors); i++ {
+		t.columnsParams = append(t.columnsParams, makeSequence(colors[i]))
+	}
+}
+
+// Adding column colors (ANSI codes)
+func (t *Table) SetFooterColor(colors ...Colors) {
+	if len(t.footers) != len(colors) {
+		panic("Number of footer colors must be equal to number of footer.")
+	}
+	for i := 0; i < len(colors); i++ {
+		t.footerParams = append(t.footerParams, makeSequence(colors[i]))
+	}
+}
+
+func Color(colors ...int) []int {
+	return colors
+}
diff --git a/vendor/github.com/olekukonko/tablewriter/util.go b/vendor/github.com/olekukonko/tablewriter/util.go
new file mode 100644
index 0000000..dea3c7a
--- /dev/null
+++ b/vendor/github.com/olekukonko/tablewriter/util.go
@@ -0,0 +1,78 @@
+// Copyright 2014 Oleku Konko All rights reserved.
+// Use of this source code is governed by a MIT
+// license that can be found in the LICENSE file.
+
+// This module is a Table Writer  API for the Go Programming Language.
+// The protocols were written in pure Go and works on windows and unix systems
+
+package tablewriter
+
+import (
+	"math"
+	"regexp"
+	"strings"
+
+	"github.com/mattn/go-runewidth"
+)
+
+var ansi = regexp.MustCompile("\033\\[(?:[0-9]{1,3}(?:;[0-9]{1,3})*)?[m|K]")
+
+func DisplayWidth(str string) int {
+	return runewidth.StringWidth(ansi.ReplaceAllLiteralString(str, ""))
+}
+
+// Simple Condition for string
+// Returns value based on condition
+func ConditionString(cond bool, valid, inValid string) string {
+	if cond {
+		return valid
+	}
+	return inValid
+}
+
+// Format Table Header
+// Replace _ , . and spaces
+func Title(name string) string {
+	origLen := len(name)
+	name = strings.Replace(name, "_", " ", -1)
+	name = strings.Replace(name, ".", " ", -1)
+	name = strings.TrimSpace(name)
+	if len(name) == 0 && origLen > 0 {
+		// Keep at least one character. This is important to preserve
+		// empty lines in multi-line headers/footers.
+		name = " "
+	}
+	return strings.ToUpper(name)
+}
+
+// Pad String
+// Attempts to play string in the center
+func Pad(s, pad string, width int) string {
+	gap := width - DisplayWidth(s)
+	if gap > 0 {
+		gapLeft := int(math.Ceil(float64(gap / 2)))
+		gapRight := gap - gapLeft
+		return strings.Repeat(string(pad), gapLeft) + s + strings.Repeat(string(pad), gapRight)
+	}
+	return s
+}
+
+// Pad String Right position
+// This would pace string at the left side fo the screen
+func PadRight(s, pad string, width int) string {
+	gap := width - DisplayWidth(s)
+	if gap > 0 {
+		return s + strings.Repeat(string(pad), gap)
+	}
+	return s
+}
+
+// Pad String Left position
+// This would pace string at the right side fo the screen
+func PadLeft(s, pad string, width int) string {
+	gap := width - DisplayWidth(s)
+	if gap > 0 {
+		return strings.Repeat(string(pad), gap) + s
+	}
+	return s
+}
diff --git a/vendor/github.com/olekukonko/tablewriter/wrap.go b/vendor/github.com/olekukonko/tablewriter/wrap.go
new file mode 100644
index 0000000..a092ee1
--- /dev/null
+++ b/vendor/github.com/olekukonko/tablewriter/wrap.go
@@ -0,0 +1,99 @@
+// Copyright 2014 Oleku Konko All rights reserved.
+// Use of this source code is governed by a MIT
+// license that can be found in the LICENSE file.
+
+// This module is a Table Writer  API for the Go Programming Language.
+// The protocols were written in pure Go and works on windows and unix systems
+
+package tablewriter
+
+import (
+	"math"
+	"strings"
+
+	"github.com/mattn/go-runewidth"
+)
+
+var (
+	nl = "\n"
+	sp = " "
+)
+
+const defaultPenalty = 1e5
+
+// Wrap wraps s into a paragraph of lines of length lim, with minimal
+// raggedness.
+func WrapString(s string, lim int) ([]string, int) {
+	words := strings.Split(strings.Replace(s, nl, sp, -1), sp)
+	var lines []string
+	max := 0
+	for _, v := range words {
+		max = runewidth.StringWidth(v)
+		if max > lim {
+			lim = max
+		}
+	}
+	for _, line := range WrapWords(words, 1, lim, defaultPenalty) {
+		lines = append(lines, strings.Join(line, sp))
+	}
+	return lines, lim
+}
+
+// WrapWords is the low-level line-breaking algorithm, useful if you need more
+// control over the details of the text wrapping process. For most uses,
+// WrapString will be sufficient and more convenient.
+//
+// WrapWords splits a list of words into lines with minimal "raggedness",
+// treating each rune as one unit, accounting for spc units between adjacent
+// words on each line, and attempting to limit lines to lim units. Raggedness
+// is the total error over all lines, where error is the square of the
+// difference of the length of the line and lim. Too-long lines (which only
+// happen when a single word is longer than lim units) have pen penalty units
+// added to the error.
+func WrapWords(words []string, spc, lim, pen int) [][]string {
+	n := len(words)
+
+	length := make([][]int, n)
+	for i := 0; i < n; i++ {
+		length[i] = make([]int, n)
+		length[i][i] = runewidth.StringWidth(words[i])
+		for j := i + 1; j < n; j++ {
+			length[i][j] = length[i][j-1] + spc + runewidth.StringWidth(words[j])
+		}
+	}
+	nbrk := make([]int, n)
+	cost := make([]int, n)
+	for i := range cost {
+		cost[i] = math.MaxInt32
+	}
+	for i := n - 1; i >= 0; i-- {
+		if length[i][n-1] <= lim {
+			cost[i] = 0
+			nbrk[i] = n
+		} else {
+			for j := i + 1; j < n; j++ {
+				d := lim - length[i][j-1]
+				c := d*d + cost[j]
+				if length[i][j-1] > lim {
+					c += pen // too-long lines get a worse penalty
+				}
+				if c < cost[i] {
+					cost[i] = c
+					nbrk[i] = j
+				}
+			}
+		}
+	}
+	var lines [][]string
+	i := 0
+	for i < n {
+		lines = append(lines, words[i:nbrk[i]])
+		i = nbrk[i]
+	}
+	return lines
+}
+
+// getLines decomposes a multiline string into a slice of strings.
+func getLines(s string) []string {
+	return strings.Split(s, nl)
+}
diff --git a/vendor/github.com/olekukonko/tablewriter/wrap_test.go b/vendor/github.com/olekukonko/tablewriter/wrap_test.go
new file mode 100644
index 0000000..a03f9fc
--- /dev/null
+++ b/vendor/github.com/olekukonko/tablewriter/wrap_test.go
@@ -0,0 +1,58 @@
+// Copyright 2014 Oleku Konko All rights reserved.
+// Use of this source code is governed by a MIT
+// license that can be found in the LICENSE file.
+
+// This module is a Table Writer  API for the Go Programming Language.
+// The protocols were written in pure Go and works on windows and unix systems
+
+package tablewriter
+
+import (
+	"strings"
+	"testing"
+
+	"github.com/mattn/go-runewidth"
+)
+
+var text = "The quick brown fox jumps over the lazy dog."
+
+func TestWrap(t *testing.T) {
+	exp := []string{
+		"The", "quick", "brown", "fox",
+		"jumps", "over", "the", "lazy", "dog."}
+
+	got, _ := WrapString(text, 6)
+	checkEqual(t, len(got), len(exp))
+}
+
+func TestWrapOneLine(t *testing.T) {
+	exp := "The quick brown fox jumps over the lazy dog."
+	words, _ := WrapString(text, 500)
+	checkEqual(t, strings.Join(words, string(sp)), exp)
+
+}
+
+func TestUnicode(t *testing.T) {
+	input := "Česká řeřicha"
+	var wordsUnicode []string
+	if runewidth.IsEastAsian() {
+		wordsUnicode, _ = WrapString(input, 14)
+	} else {
+		wordsUnicode, _ = WrapString(input, 13)
+	}
+	// input contains 13 (or 14 for CJK) runes, so it fits on one line.
+	checkEqual(t, len(wordsUnicode), 1)
+}
+
+func TestDisplayWidth(t *testing.T) {
+	input := "Česká řeřicha"
+	want := 13
+	if runewidth.IsEastAsian() {
+		want = 14
+	}
+	if n := DisplayWidth(input); n != want {
+		t.Errorf("Wants: %d Got: %d", want, n)
+	}
+	input = "\033[43;30m" + input + "\033[00m"
+	checkEqual(t, DisplayWidth(input), want)
+}

-- 
To stop receiving notification emails like this one, please contact
rohit@apache.org.