mirror of https://github.com/fatedier/frp
packages: add package github.com/rodaine/table
parent
584e098e8e
commit
afde0c515c
|
@ -1,5 +1,5 @@
|
|||
hash: 03ff8b71f63e9038c0182a4ef2a55aa9349782f4813c331e2d1f02f3dd15b4f8
|
||||
updated: 2017-11-01T16:16:18.577622991+08:00
|
||||
hash: 188e1149e415ff9cefab8db2cded030efae57558a0b9551795c5c7d0b0572a7b
|
||||
updated: 2018-01-17T01:14:34.435613+08:00
|
||||
imports:
|
||||
- name: github.com/armon/go-socks5
|
||||
version: e75332964ef517daa070d7c38a9466a0d687e0a5
|
||||
|
@ -33,6 +33,8 @@ imports:
|
|||
version: 274df120e9065bdd08eb1120e0375e3dc1ae8465
|
||||
subpackages:
|
||||
- fs
|
||||
- name: github.com/rodaine/table
|
||||
version: 212a2ad1c462ed4d5b5511ea2b480a573281dbbd
|
||||
- name: github.com/stretchr/testify
|
||||
version: 2402e8e7a02fc811447d11f881aa9746cdc57983
|
||||
subpackages:
|
||||
|
|
|
@ -71,3 +71,5 @@ import:
|
|||
- internal/iana
|
||||
- internal/socket
|
||||
- ipv4
|
||||
- package: github.com/rodaine/table
|
||||
version: v1.0.0
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
sudo: false
|
||||
language: go
|
||||
go: 1.8
|
||||
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
|
||||
install: go get -t ./... github.com/golang/lint/golint
|
||||
script: make lint test
|
|
@ -0,0 +1,9 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Chris Roche (rodaine+github@gmail.com)
|
||||
|
||||
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.
|
|
@ -0,0 +1,9 @@
|
|||
.PHONY: lint
|
||||
lint:
|
||||
gofmt -d -s .
|
||||
golint -set_exit_status ./...
|
||||
go tool vet -all -shadow -shadowstrict .
|
||||
|
||||
.PHONY: test
|
||||
test:
|
||||
go test -v -cover -race ./...
|
|
@ -0,0 +1,61 @@
|
|||
# table <br/> [![GoDoc](https://godoc.org/github.com/rodaine/table?status.svg)](https://godoc.org/github.com/rodaine/table) [![Build Status](https://travis-ci.org/rodaine/table.svg)](https://travis-ci.org/rodaine/table)
|
||||
|
||||
![Example Table Output With ANSI Colors](http://res.cloudinary.com/rodaine/image/upload/v1442524799/go-table-example0.png)
|
||||
|
||||
Package table provides a convenient way to generate tabular output of any data, primarily useful for CLI tools.
|
||||
|
||||
## Features
|
||||
|
||||
- Accepts all data types (`string`, `int`, `interface{}`, everything!) and will use the `String() string` method of a type if available.
|
||||
- Can specify custom formatting for the header and first column cells for better readability.
|
||||
- Columns are left-aligned and sized to fit the data, with customizable padding.
|
||||
- The printed output can be sent to any `io.Writer`, defaulting to `os.Stdout`.
|
||||
- Built to an interface, so you can roll your own `Table` implementation.
|
||||
- Works well with ANSI colors ([fatih/color](https://github.com/fatih/color) in the example)!
|
||||
- Can provide a custom `WidthFunc` to accomodate multi- and zero-width characters (such as [runewidth](https://github.com/mattn/go-runewidth))
|
||||
|
||||
## Usage
|
||||
|
||||
**Download the package:**
|
||||
|
||||
```sh
|
||||
go get -u github.com/rodaine/table
|
||||
```
|
||||
|
||||
**Example:**
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/rodaine/table"
|
||||
)
|
||||
|
||||
func main() {
|
||||
headerFmt := color.New(color.FgGreen, color.Underline).SprintfFunc()
|
||||
columnFmt := color.New(color.FgYellow).SprintfFunc()
|
||||
|
||||
tbl := table.New("ID", "Name", "Score", "Added")
|
||||
tbl.WithHeaderFormatter(headerFmt).WithFirstColumnFormatter(columnFmt)
|
||||
|
||||
for _, widget := range getWidgets() {
|
||||
tbl.AddRow(widget.ID, widget.Name, widget.Cost, widget.Added)
|
||||
}
|
||||
|
||||
tbl.Print()
|
||||
}
|
||||
```
|
||||
|
||||
_Consult the [documentation](https://godoc.org/github.com/rodaine/table) for further examples and usage information_
|
||||
|
||||
## Contributing
|
||||
|
||||
Please feel free to submit an [issue](https://github.com/rodaine/table/issues) or [PR](https://github.com/rodaine/table/pulls) to this repository for features or bugs. All submitted code must pass the scripts specified within [.travis.yml](https://github.com/rodaine/table/blob/master/.travis.yml) and should include tests to back up the changes.
|
||||
|
||||
## License
|
||||
|
||||
table is released under the MIT License (Expat). See the [full license](https://github.com/rodaine/table/blob/master/license).
|
|
@ -0,0 +1,267 @@
|
|||
// Package table provides a convenient way to generate tabular output of any
|
||||
// data, primarily useful for CLI tools.
|
||||
//
|
||||
// Columns are left-aligned and padded to accomodate the largest cell in that
|
||||
// column.
|
||||
//
|
||||
// Source: https://github.com/rodaine/table
|
||||
//
|
||||
// table.DefaultHeaderFormatter = func(format string, vals ...interface{}) string {
|
||||
// return strings.ToUpper(fmt.Sprintf(format, vals...))
|
||||
// }
|
||||
//
|
||||
// tbl := table.New("ID", "Name", "Cost ($)")
|
||||
//
|
||||
// for _, widget := range Widgets {
|
||||
// tbl.AddRow(widget.ID, widget.Name, widget.Cost)
|
||||
// }
|
||||
//
|
||||
// tbl.Print()
|
||||
//
|
||||
// // Output:
|
||||
// // ID NAME COST ($)
|
||||
// // 1 Foobar 1.23
|
||||
// // 2 Fizzbuzz 4.56
|
||||
// // 3 Gizmo 78.90
|
||||
package table
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// These are the default properties for all Tables created from this package
|
||||
// and can be modified.
|
||||
var (
|
||||
// DefaultPadding specifies the number of spaces between columns in a table.
|
||||
DefaultPadding = 2
|
||||
|
||||
// DefaultWriter specifies the output io.Writer for the Table.Print method.
|
||||
DefaultWriter io.Writer = os.Stdout
|
||||
|
||||
// DefaultHeaderFormatter specifies the default Formatter for the table header.
|
||||
DefaultHeaderFormatter Formatter
|
||||
|
||||
// DefaultFirstColumnFormatter specifies the default Formatter for the first column cells.
|
||||
DefaultFirstColumnFormatter Formatter
|
||||
|
||||
// DefaultWidthFunc specifies the default WidthFunc for calculating column widths
|
||||
DefaultWidthFunc WidthFunc = utf8.RuneCountInString
|
||||
)
|
||||
|
||||
// Formatter functions expose a fmt.Sprintf signature that can be used to modify
|
||||
// the display of the text in either the header or first column of a Table.
|
||||
// The formatter should not change the width of original text as printed since
|
||||
// column widths are calculated pre-formatting (though this issue can be mitigated
|
||||
// with increased padding).
|
||||
//
|
||||
// tbl.WithHeaderFormatter(func(format string, vals ...interface{}) string {
|
||||
// return strings.ToUpper(fmt.Sprintf(format, vals...))
|
||||
// })
|
||||
//
|
||||
// A good use case for formatters is to use ANSI escape codes to color the cells
|
||||
// for a nicer interface. The package color (https://github.com/fatih/color) makes
|
||||
// it easy to generate these automatically: http://godoc.org/github.com/fatih/color#Color.SprintfFunc
|
||||
type Formatter func(string, ...interface{}) string
|
||||
|
||||
// A WidthFunc calculates the width of a string. By default, the number of runes
|
||||
// is used but this may not be appropriate for certain character sets. The
|
||||
// package runewidth (https://github.com/mattn/go-runewidth) could be used to
|
||||
// accomodate multi-cell characters (such as emoji or CJK characters).
|
||||
type WidthFunc func(string) int
|
||||
|
||||
// Table describes the interface for building up a tabular representation of data.
|
||||
// It exposes fluent/chainable methods for convenient table building.
|
||||
//
|
||||
// WithHeaderFormatter and WithFirstColumnFormatter sets the Formatter for the
|
||||
// header and first column, respectively. If nil is passed in (the default), no
|
||||
// formatting will be applied.
|
||||
//
|
||||
// New("foo", "bar").WithFirstColumnFormatter(func(f string, v ...interface{}) string {
|
||||
// return strings.ToUpper(fmt.Sprintf(f, v...))
|
||||
// })
|
||||
//
|
||||
// WithPadding specifies the minimum padding between cells in a row and defaults
|
||||
// to DefaultPadding. Padding values less than or equal to zero apply no extra
|
||||
// padding between the columns.
|
||||
//
|
||||
// New("foo", "bar").WithPadding(3)
|
||||
//
|
||||
// WithWriter modifies the writer which Print outputs to, defaulting to DefaultWriter
|
||||
// when instantiated. If nil is passed, os.Stdout will be used.
|
||||
//
|
||||
// New("foo", "bar").WithWriter(os.Stderr)
|
||||
//
|
||||
// WithWidthFunc sets the function used to calculate the width of the string in
|
||||
// a column. By default, the number of utf8 runes in the string is used.
|
||||
//
|
||||
// AddRow adds another row of data to the table. Any values can be passed in and
|
||||
// will be output as its string representation as described in the fmt standard
|
||||
// package. Rows can have less cells than the total number of columns in the table;
|
||||
// subsequent cells will be rendered empty. Rows with more cells than the total
|
||||
// number of columns will be truncated. References to the data are not held, so
|
||||
// the passed in values can be modified without affecting the table's output.
|
||||
//
|
||||
// New("foo", "bar").AddRow("fizz", "buzz").AddRow(time.Now()).AddRow(1, 2, 3).Print()
|
||||
// // Output:
|
||||
// // foo bar
|
||||
// // fizz buzz
|
||||
// // 2006-01-02 15:04:05.0 -0700 MST
|
||||
// // 1 2
|
||||
//
|
||||
// Print writes the string representation of the table to the provided writer.
|
||||
// Print can be called multiple times, even after subsequent mutations of the
|
||||
// provided data. The output is always preceded and followed by a new line.
|
||||
type Table interface {
|
||||
WithHeaderFormatter(f Formatter) Table
|
||||
WithFirstColumnFormatter(f Formatter) Table
|
||||
WithPadding(p int) Table
|
||||
WithWriter(w io.Writer) Table
|
||||
WithWidthFunc(f WidthFunc) Table
|
||||
|
||||
AddRow(vals ...interface{}) Table
|
||||
Print()
|
||||
}
|
||||
|
||||
// New creates a Table instance with the specified header(s) provided. The number
|
||||
// of columns is fixed at this point to len(columnHeaders) and the defined defaults
|
||||
// are set on the instance.
|
||||
func New(columnHeaders ...interface{}) Table {
|
||||
t := table{header: make([]string, len(columnHeaders))}
|
||||
|
||||
t.WithPadding(DefaultPadding)
|
||||
t.WithWriter(DefaultWriter)
|
||||
t.WithHeaderFormatter(DefaultHeaderFormatter)
|
||||
t.WithFirstColumnFormatter(DefaultFirstColumnFormatter)
|
||||
t.WithWidthFunc(DefaultWidthFunc)
|
||||
|
||||
for i, col := range columnHeaders {
|
||||
t.header[i] = fmt.Sprint(col)
|
||||
}
|
||||
|
||||
return &t
|
||||
}
|
||||
|
||||
type table struct {
|
||||
FirstColumnFormatter Formatter
|
||||
HeaderFormatter Formatter
|
||||
Padding int
|
||||
Writer io.Writer
|
||||
Width WidthFunc
|
||||
|
||||
header []string
|
||||
rows [][]string
|
||||
widths []int
|
||||
}
|
||||
|
||||
func (t *table) WithHeaderFormatter(f Formatter) Table {
|
||||
t.HeaderFormatter = f
|
||||
return t
|
||||
}
|
||||
|
||||
func (t *table) WithFirstColumnFormatter(f Formatter) Table {
|
||||
t.FirstColumnFormatter = f
|
||||
return t
|
||||
}
|
||||
|
||||
func (t *table) WithPadding(p int) Table {
|
||||
if p < 0 {
|
||||
p = 0
|
||||
}
|
||||
|
||||
t.Padding = p
|
||||
return t
|
||||
}
|
||||
|
||||
func (t *table) WithWriter(w io.Writer) Table {
|
||||
if w == nil {
|
||||
w = os.Stdout
|
||||
}
|
||||
|
||||
t.Writer = w
|
||||
return t
|
||||
}
|
||||
|
||||
func (t *table) WithWidthFunc(f WidthFunc) Table {
|
||||
t.Width = f
|
||||
return t
|
||||
}
|
||||
|
||||
func (t *table) AddRow(vals ...interface{}) Table {
|
||||
row := make([]string, len(t.header))
|
||||
for i, val := range vals {
|
||||
if i >= len(t.header) {
|
||||
break
|
||||
}
|
||||
row[i] = fmt.Sprint(val)
|
||||
}
|
||||
t.rows = append(t.rows, row)
|
||||
|
||||
return t
|
||||
}
|
||||
|
||||
func (t *table) Print() {
|
||||
format := strings.Repeat("%s", len(t.header)) + "\n"
|
||||
t.calculateWidths()
|
||||
fmt.Fprintln(t.Writer)
|
||||
t.printHeader(format)
|
||||
for _, row := range t.rows {
|
||||
t.printRow(format, row)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *table) printHeader(format string) {
|
||||
vals := t.applyWidths(t.header, t.widths)
|
||||
if t.HeaderFormatter != nil {
|
||||
txt := t.HeaderFormatter(format, vals...)
|
||||
fmt.Fprint(t.Writer, txt)
|
||||
} else {
|
||||
fmt.Fprintf(t.Writer, format, vals...)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *table) printRow(format string, row []string) {
|
||||
vals := t.applyWidths(row, t.widths)
|
||||
|
||||
if t.FirstColumnFormatter != nil {
|
||||
vals[0] = t.FirstColumnFormatter("%s", vals[0])
|
||||
}
|
||||
|
||||
fmt.Fprintf(t.Writer, format, vals...)
|
||||
}
|
||||
|
||||
func (t *table) calculateWidths() {
|
||||
t.widths = make([]int, len(t.header))
|
||||
for _, row := range t.rows {
|
||||
for i, v := range row {
|
||||
if w := t.Width(v) + t.Padding; w > t.widths[i] {
|
||||
t.widths[i] = w
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for i, v := range t.header {
|
||||
if w := t.Width(v) + t.Padding; w > t.widths[i] {
|
||||
t.widths[i] = w
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *table) applyWidths(row []string, widths []int) []interface{} {
|
||||
out := make([]interface{}, len(row))
|
||||
for i, s := range row {
|
||||
out[i] = s + t.lenOffset(s, widths[i])
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (t *table) lenOffset(s string, w int) string {
|
||||
l := w - t.Width(s)
|
||||
if l <= 0 {
|
||||
return ""
|
||||
}
|
||||
return strings.Repeat(" ", l)
|
||||
}
|
|
@ -0,0 +1,181 @@
|
|||
package table
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/mattn/go-runewidth"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestFormatter(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var formatter Formatter
|
||||
|
||||
fn := func(a string, b ...interface{}) string { return "" }
|
||||
f := Formatter(fn)
|
||||
|
||||
assert.IsType(t, formatter, f)
|
||||
}
|
||||
|
||||
func TestTable_New(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
buf := bytes.Buffer{}
|
||||
New("foo", "bar").WithWriter(&buf).Print()
|
||||
out := buf.String()
|
||||
|
||||
assert.Contains(t, out, "foo")
|
||||
assert.Contains(t, out, "bar")
|
||||
|
||||
buf.Reset()
|
||||
New().WithWriter(&buf).Print()
|
||||
out = buf.String()
|
||||
|
||||
assert.Empty(t, strings.TrimSpace(out))
|
||||
}
|
||||
|
||||
func TestTable_WithHeaderFormatter(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
uppercase := func(f string, v ...interface{}) string {
|
||||
return strings.ToUpper(fmt.Sprintf(f, v...))
|
||||
}
|
||||
buf := bytes.Buffer{}
|
||||
|
||||
tbl := New("foo", "bar").WithWriter(&buf).WithHeaderFormatter(uppercase)
|
||||
tbl.Print()
|
||||
out := buf.String()
|
||||
|
||||
assert.Contains(t, out, "FOO")
|
||||
assert.Contains(t, out, "BAR")
|
||||
|
||||
buf.Reset()
|
||||
tbl.WithHeaderFormatter(nil).Print()
|
||||
out = buf.String()
|
||||
|
||||
assert.Contains(t, out, "foo")
|
||||
assert.Contains(t, out, "bar")
|
||||
}
|
||||
|
||||
func TestTable_WithFirstColumnFormatter(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
uppercase := func(f string, v ...interface{}) string {
|
||||
return strings.ToUpper(fmt.Sprintf(f, v...))
|
||||
}
|
||||
|
||||
buf := bytes.Buffer{}
|
||||
|
||||
tbl := New("foo", "bar").WithWriter(&buf).WithFirstColumnFormatter(uppercase).AddRow("fizz", "buzz")
|
||||
tbl.Print()
|
||||
out := buf.String()
|
||||
|
||||
assert.Contains(t, out, "foo")
|
||||
assert.Contains(t, out, "bar")
|
||||
assert.Contains(t, out, "FIZZ")
|
||||
assert.Contains(t, out, "buzz")
|
||||
|
||||
buf.Reset()
|
||||
tbl.WithFirstColumnFormatter(nil).Print()
|
||||
out = buf.String()
|
||||
|
||||
assert.Contains(t, out, "fizz")
|
||||
assert.Contains(t, out, "buzz")
|
||||
}
|
||||
|
||||
func TestTable_WithPadding(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// zero value
|
||||
buf := bytes.Buffer{}
|
||||
tbl := New("foo", "bar").WithWriter(&buf).WithPadding(0)
|
||||
tbl.Print()
|
||||
out := buf.String()
|
||||
assert.Contains(t, out, "foobar")
|
||||
|
||||
// positive value
|
||||
buf.Reset()
|
||||
tbl.WithPadding(4).Print()
|
||||
out = buf.String()
|
||||
assert.Contains(t, out, "foo bar ")
|
||||
|
||||
// negative value
|
||||
buf.Reset()
|
||||
tbl.WithPadding(-1).Print()
|
||||
out = buf.String()
|
||||
assert.Contains(t, out, "foobar")
|
||||
}
|
||||
|
||||
func TestTable_WithWriter(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// not that we haven't been using it in all these tests but:
|
||||
buf := bytes.Buffer{}
|
||||
New("foo", "bar").WithWriter(&buf).Print()
|
||||
assert.NotEmpty(t, buf.String())
|
||||
|
||||
stdout := os.Stdout
|
||||
temp, _ := ioutil.TempFile("", "")
|
||||
os.Stdout = temp
|
||||
defer func() {
|
||||
os.Stdout = stdout
|
||||
temp.Close()
|
||||
}()
|
||||
|
||||
New("foo", "bar").WithWriter(nil).Print()
|
||||
temp.Seek(0, 0)
|
||||
|
||||
out, _ := ioutil.ReadAll(temp)
|
||||
assert.NotEmpty(t, out)
|
||||
}
|
||||
|
||||
func TestTable_AddRow(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
buf := bytes.Buffer{}
|
||||
tbl := New("foo", "bar").WithWriter(&buf).AddRow("fizz", "buzz")
|
||||
tbl.Print()
|
||||
out := buf.String()
|
||||
assert.Contains(t, out, "fizz")
|
||||
assert.Contains(t, out, "buzz")
|
||||
lines := strings.Count(out, "\n")
|
||||
|
||||
// empty should add empty line
|
||||
buf.Reset()
|
||||
tbl.AddRow().Print()
|
||||
assert.Equal(t, lines+1, strings.Count(buf.String(), "\n"))
|
||||
|
||||
// less than one will fill left-to-right
|
||||
buf.Reset()
|
||||
tbl.AddRow("cat").Print()
|
||||
assert.Contains(t, buf.String(), "\ncat")
|
||||
|
||||
// more than initial length are truncated
|
||||
buf.Reset()
|
||||
tbl.AddRow("bippity", "boppity", "boo").Print()
|
||||
assert.NotContains(t, buf.String(), "boo")
|
||||
}
|
||||
|
||||
func TestTable_WithWidthFunc(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
buf := bytes.Buffer{}
|
||||
|
||||
New("", "").
|
||||
WithWriter(&buf).
|
||||
WithPadding(1).
|
||||
WithWidthFunc(runewidth.StringWidth).
|
||||
AddRow("请求", "alpha").
|
||||
AddRow("abc", "beta").
|
||||
Print()
|
||||
|
||||
actual := buf.String()
|
||||
assert.Contains(t, actual, "请求 alpha")
|
||||
assert.Contains(t, actual, "abc beta")
|
||||
}
|
Loading…
Reference in New Issue