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.