Update vendor

This commit is contained in:
Ettore Di Giacinto
2020-11-23 19:14:07 +01:00
parent 7a10ff2742
commit 5b54aeb822
147 changed files with 28614 additions and 0 deletions

21
vendor/github.com/jedib0t/go-pretty/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 jedib0t
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.

354
vendor/github.com/jedib0t/go-pretty/table/README.md generated vendored Normal file
View File

@@ -0,0 +1,354 @@
# Table
[![GoDoc](https://godoc.org/github.com/jedib0t/go-pretty/table?status.svg)](https://godoc.org/github.com/jedib0t/go-pretty/table)
Pretty-print tables into ASCII/Unicode strings.
- Add Rows one-by-one or as a group
- Add Header(s) and Footer(s)
- Auto Index Rows (1, 2, 3 ...) and Columns (A, B, C, ...)
- Limit the length of the Rows; limit the length of individual Columns
- Page results by a specified number of Lines
- Alignment - Horizontal & Vertical
- Auto (horizontal) Align (numeric columns are aligned Right)
- Custom (horizontal) Align per column
- Custom (vertical) VAlign per column (and multi-line column support)
- Mirror output to an io.Writer object (like os.StdOut)
- Sort by any of the Columns (by Column Name or Number)
- Transformers to customize individual cell rendering
- Completely customizable styles
- Many ready-to-use styles: [style.go](style.go)
- Colorize Headers/Body/Footers using [../text/color.go](../text/color.go)
- Custom text-case for Headers/Body/Footers
- Enable separators between each row
- Render table without a Border
- Render as:
- (ASCII/Unicode) Table
- CSV
- HTML Table (with custom CSS Class)
- Markdown Table
```
+---------------------------------------------------------------------+
| Game of Thrones +
+-----+------------+-----------+--------+-----------------------------+
| # | FIRST NAME | LAST NAME | SALARY | |
+-----+------------+-----------+--------+-----------------------------+
| 1 | Arya | Stark | 3000 | |
| 20 | Jon | Snow | 2000 | You know nothing, Jon Snow! |
| 300 | Tyrion | Lannister | 5000 | |
+-----+------------+-----------+--------+-----------------------------+
| | | TOTAL | 10000 | |
+-----+------------+-----------+--------+-----------------------------+
```
A demonstration of all the capabilities can be found here:
[../cmd/demo-table](../cmd/demo-table)
If you want very specific examples, read ahead.
# Examples
All the examples below are going to start with the following block, although
nothing except a single Row is mandatory for the `Render()` function to render
something:
```go
package main
import (
"os"
"github.com/jedib0t/go-pretty/table"
)
func main() {
t := table.NewWriter()
t.SetOutputMirror(os.Stdout)
t.AppendHeader(table.Row{"#", "First Name", "Last Name", "Salary"})
t.AppendRows([]table.Row{
{1, "Arya", "Stark", 3000},
{20, "Jon", "Snow", 2000, "You know nothing, Jon Snow!"},
})
t.AppendRow([]interface{}{300, "Tyrion", "Lannister", 5000})
t.AppendFooter(table.Row{"", "", "Total", 10000})
t.Render()
}
```
Running the above will result in:
```
+-----+------------+-----------+--------+-----------------------------+
| # | FIRST NAME | LAST NAME | SALARY | |
+-----+------------+-----------+--------+-----------------------------+
| 1 | Arya | Stark | 3000 | |
| 20 | Jon | Snow | 2000 | You know nothing, Jon Snow! |
| 300 | Tyrion | Lannister | 5000 | |
+-----+------------+-----------+--------+-----------------------------+
| | | TOTAL | 10000 | |
+-----+------------+-----------+--------+-----------------------------+
```
## Styles
You can customize almost every single thing about the table above. The previous
example just defaulted to `StyleDefault` during `Render()`. You can use a
ready-to-use style (as in [style.go](style.go)) or customize it as you want.
### Ready-to-use Styles
Table comes with a bunch of ready-to-use Styles that make the table look really
good. Set or Change the style using:
```go
t.SetStyle(table.StyleLight)
t.Render()
```
to get:
```
┌─────┬────────────┬───────────┬────────┬─────────────────────────────┐
│ # │ FIRST NAME │ LAST NAME │ SALARY │ │
├─────┼────────────┼───────────┼────────┼─────────────────────────────┤
│ 1 │ Arya │ Stark │ 3000 │ │
│ 20 │ Jon │ Snow │ 2000 │ You know nothing, Jon Snow! │
│ 300 │ Tyrion │ Lannister │ 5000 │ │
├─────┼────────────┼───────────┼────────┼─────────────────────────────┤
│ │ │ TOTAL │ 10000 │ │
└─────┴────────────┴───────────┴────────┴─────────────────────────────┘
```
Or if you want to use a full-color mode, and don't care for boxes, use:
```go
t.SetStyle(table.StyleColoredBright)
t.Render()
```
to get:
<img src="images/table-StyleColoredBright.png" width="640px"/>
### Roll your own Style
You can also roll your own style:
```go
t.SetStyle(table.Style{
Name: "myNewStyle",
Box: table.BoxStyle{
BottomLeft: "\\",
BottomRight: "/",
BottomSeparator: "v",
Left: "[",
LeftSeparator: "{",
MiddleHorizontal: "-",
MiddleSeparator: "+",
MiddleVertical: "|",
PaddingLeft: "<",
PaddingRight: ">",
Right: "]",
RightSeparator: "}",
TopLeft: "(",
TopRight: ")",
TopSeparator: "^",
UnfinishedRow: " ~~~",
},
Color: table.ColorOptions{
AutoIndexColumn: nil,
FirstColumn: nil,
Footer: text.Colors{text.BgCyan, text.FgBlack},
Header: text.Colors{text.BgHiCyan, text.FgBlack},
Row: text.Colors{text.BgHiWhite, text.FgBlack},
RowAlternate: text.Colors{text.BgWhite, text.FgBlack},
},
Format: table.FormatOptions{
Footer: text.FormatUpper,
Header: text.FormatUpper,
Row: text.FormatDefault,
},
Options: table.Options{
DrawBorder: true,
SeparateColumns: true,
SeparateFooter: true,
SeparateHeader: true,
SeparateRows: false,
},
})
```
Or you can use one of the ready-to-use Styles, and just make a few tweaks:
```go
t.SetStyle(table.StyleLight)
t.Style().Color.Header = text.Colors{text.BgHiCyan, text.FgBlack}
t.Style().Format.Footer = text.FormatLower
t.Style().Options.DrawBorder = false
```
## Paging
You can limit then number of lines rendered in a single "Page". This logic
can handle rows with multiple lines too. Here is a simple example:
```go
t.SetPageSize(1)
t.Render()
```
to get:
```
+-----+------------+-----------+--------+-----------------------------+
| # | FIRST NAME | LAST NAME | SALARY | |
+-----+------------+-----------+--------+-----------------------------+
| 1 | Arya | Stark | 3000 | |
+-----+------------+-----------+--------+-----------------------------+
| | | TOTAL | 10000 | |
+-----+------------+-----------+--------+-----------------------------+
+-----+------------+-----------+--------+-----------------------------+
| # | FIRST NAME | LAST NAME | SALARY | |
+-----+------------+-----------+--------+-----------------------------+
| 20 | Jon | Snow | 2000 | You know nothing, Jon Snow! |
+-----+------------+-----------+--------+-----------------------------+
| | | TOTAL | 10000 | |
+-----+------------+-----------+--------+-----------------------------+
+-----+------------+-----------+--------+-----------------------------+
| # | FIRST NAME | LAST NAME | SALARY | |
+-----+------------+-----------+--------+-----------------------------+
| 300 | Tyrion | Lannister | 5000 | |
+-----+------------+-----------+--------+-----------------------------+
| | | TOTAL | 10000 | |
+-----+------------+-----------+--------+-----------------------------+
```
## Wrapping (or) Row/Column Width restrictions
You can restrict the maximum (text) width for a Row:
```go
t.SetAllowedRowLength(50)
t.Render()
```
to get:
```
+-----+------------+-----------+--------+------- ~
| # | FIRST NAME | LAST NAME | SALARY | ~
+-----+------------+-----------+--------+------- ~
| 1 | Arya | Stark | 3000 | ~
| 20 | Jon | Snow | 2000 | You kn ~
| 300 | Tyrion | Lannister | 5000 | ~
+-----+------------+-----------+--------+------- ~
| | | TOTAL | 10000 | ~
+-----+------------+-----------+--------+------- ~
```
## Column Control - Alignment, Colors, Width and more
You can control a lot of things about individual cells/columns which overrides
global properties/styles using the `SetColumnConfig()` interface:
- Alignment (horizontal & vertical)
- Colorization
- Transform individual cells based on the content
- Width (minimum & maximum)
```go
nameTransformer := text.Transformer(func(val interface{}) string {
return text.Bold.Sprint(val)
})
t.SetColumnConfigs([]ColumnConfig{
{
Name: "First Name",
Align: text.AlignLeft,
AlignFooter: text.AlignLeft,
AlignHeader: text.AlignLeft,
Colors: text.Colors{text.BgBlack, text.FgRed},
ColorsHeader: text.Colors{text.BgRed, text.FgBlack, text.Bold},
ColorsFooter: text.Colors{text.BgRed, text.FgBlack},
Transformer: nameTransformer,
TransformerFooter: nameTransformer,
TransformerHeader: nameTransformer,
VAlign: text.VAlignMiddle,
VAlignFooter: text.VAlignTop,
VAlignHeader: text.VAlignBottom,
WidthMin: 6,
WidthMax: 64,
}
})
```
## Render As ...
Tables can be rendered in other common formats such as:
### ... CSV
```go
t.RenderCSV()
```
to get:
```
,First Name,Last Name,Salary,
1,Arya,Stark,3000,
20,Jon,Snow,2000,"You know nothing\, Jon Snow!"
300,Tyrion,Lannister,5000,
,,Total,10000,
```
### ... HTML Table
```go
t.RenderHTML()
```
to get:
```html
<table class="go-pretty-table">
<thead>
<tr>
<th align="right">#</th>
<th>First Name</th>
<th>Last Name</th>
<th align="right">Salary</th>
<th>&nbsp;</th>
</tr>
</thead>
<tbody>
<tr>
<td align="right">1</td>
<td>Arya</td>
<td>Stark</td>
<td align="right">3000</td>
<td>&nbsp;</td>
</tr>
<tr>
<td align="right">20</td>
<td>Jon</td>
<td>Snow</td>
<td align="right">2000</td>
<td>You know nothing, Jon Snow!</td>
</tr>
<tr>
<td align="right">300</td>
<td>Tyrion</td>
<td>Lannister</td>
<td align="right">5000</td>
<td>&nbsp;</td>
</tr>
</tbody>
<tfoot>
<tr>
<td align="right">&nbsp;</td>
<td>&nbsp;</td>
<td>Total</td>
<td align="right">10000</td>
<td>&nbsp;</td>
</tr>
</tfoot>
</table>
```
### ... Markdown Table
```go
t.RenderMarkdown()
```
to get:
```markdown
| # | First Name | Last Name | Salary | |
| ---:| --- | --- | ---:| --- |
| 1 | Arya | Stark | 3000 | |
| 20 | Jon | Snow | 2000 | You know nothing, Jon Snow! |
| 300 | Tyrion | Lannister | 5000 | |
| | | Total | 10000 | |
```

52
vendor/github.com/jedib0t/go-pretty/table/config.go generated vendored Normal file
View File

@@ -0,0 +1,52 @@
package table
import (
"github.com/jedib0t/go-pretty/text"
)
// ColumnConfig contains configurations that determine and modify the way the
// contents of the column get rendered.
type ColumnConfig struct {
// Name is the name of the Column as it appears in the first Header row.
// If a Header is not provided, or the name is not found in the header, this
// will not work.
Name string
// Number is the Column # from left. When specified, it overrides the Name
// property. If you know the exact Column number, use this instead of Name.
Number int
// Align defines the horizontal alignment
Align text.Align
// AlignFooter defines the horizontal alignment of Footer rows
AlignFooter text.Align
// AlignHeader defines the horizontal alignment of Header rows
AlignHeader text.Align
// Colors defines the colors to be used on the column
Colors text.Colors
// ColorsFooter defines the colors to be used on the column in Footer rows
ColorsFooter text.Colors
// ColorsHeader defines the colors to be used on the column in Header rows
ColorsHeader text.Colors
// Transformer is a custom-function that changes the way the value gets
// rendered to the console. Refer to text/transformer.go for ready-to-use
// Transformer functions.
Transformer text.Transformer
// TransformerFooter is like Transformer but for Footer rows
TransformerFooter text.Transformer
// TransformerHeader is like Transformer but for Header rows
TransformerHeader text.Transformer
// VAlign defines the vertical alignment
VAlign text.VAlign
// VAlignFooter defines the vertical alignment in Footer rows
VAlignFooter text.VAlign
// VAlignHeader defines the vertical alignment in Header rows
VAlignHeader text.VAlign
// WidthMin defines the minimum character length of the column
WidthMin int
// WidthMax defines the maximum character length of the column
WidthMax int
}

385
vendor/github.com/jedib0t/go-pretty/table/render.go generated vendored Normal file
View File

@@ -0,0 +1,385 @@
package table
import (
"fmt"
"strings"
"unicode/utf8"
"github.com/jedib0t/go-pretty/text"
)
// Render renders the Table in a human-readable "pretty" format. Example:
// ┌─────┬────────────┬───────────┬────────┬─────────────────────────────┐
// │ # │ FIRST NAME │ LAST NAME │ SALARY │ │
// ├─────┼────────────┼───────────┼────────┼─────────────────────────────┤
// │ 1 │ Arya │ Stark │ 3000 │ │
// │ 20 │ Jon │ Snow │ 2000 │ You know nothing, Jon Snow! │
// │ 300 │ Tyrion │ Lannister │ 5000 │ │
// ├─────┼────────────┼───────────┼────────┼─────────────────────────────┤
// │ │ │ TOTAL │ 10000 │ │
// └─────┴────────────┴───────────┴────────┴─────────────────────────────┘
func (t *Table) Render() string {
t.initForRender()
var out strings.Builder
if t.numColumns > 0 {
t.renderTitle(&out)
// top-most border
t.renderRowsBorderTop(&out)
// header rows
t.renderRowsHeader(&out)
// (data) rows
t.renderRows(&out, t.rows, renderHint{})
// footer rows
t.renderRowsFooter(&out)
// bottom-most border
t.renderRowsBorderBottom(&out)
// caption
if t.caption != "" {
out.WriteRune('\n')
out.WriteString(t.caption)
}
}
return t.render(&out)
}
func (t *Table) renderColumn(out *strings.Builder, row rowStr, colIdx int, maxColumnLength int, hint renderHint) {
// when working on the first column, and autoIndex is true, insert a new
// column with the row number on it.
if colIdx == 0 && t.autoIndex {
t.renderColumnAutoIndex(out, hint)
}
// when working on column number 2 or more, render the column separator
if colIdx > 0 {
t.renderColumnSeparator(out, hint)
}
// extract the text, convert-case if not-empty and align horizontally
var colStr string
if colIdx < len(row) {
colStr = t.getFormat(hint).Apply(row[colIdx])
}
colStr = t.getAlign(colIdx, hint).Apply(colStr, maxColumnLength)
// pad both sides of the column (when not a separator row)
if !hint.isSeparatorRow {
colStr = t.style.Box.PaddingLeft + colStr + t.style.Box.PaddingRight
}
t.renderColumnColorized(out, colIdx, colStr, hint)
}
func (t *Table) renderColumnAutoIndex(out *strings.Builder, hint renderHint) {
var outAutoIndex strings.Builder
outAutoIndex.Grow(t.maxColumnLengths[0])
if hint.isSeparatorRow {
numChars := t.autoIndexVIndexMaxLength + utf8.RuneCountInString(t.style.Box.PaddingLeft) +
utf8.RuneCountInString(t.style.Box.PaddingRight)
outAutoIndex.WriteString(text.RepeatAndTrim(t.style.Box.MiddleHorizontal, numChars))
} else {
outAutoIndex.WriteString(t.style.Box.PaddingLeft)
rowNumStr := fmt.Sprint(hint.rowNumber)
if hint.isHeaderRow || hint.isFooterRow || hint.rowLineNumber > 1 {
rowNumStr = strings.Repeat(" ", t.autoIndexVIndexMaxLength)
}
outAutoIndex.WriteString(text.AlignRight.Apply(rowNumStr, t.autoIndexVIndexMaxLength))
outAutoIndex.WriteString(t.style.Box.PaddingRight)
}
if t.style.Color.IndexColumn != nil {
colors := t.style.Color.IndexColumn
if hint.isFooterRow {
colors = t.style.Color.Footer
}
out.WriteString(colors.Sprint(outAutoIndex.String()))
} else {
out.WriteString(outAutoIndex.String())
}
hint.isAutoIndexColumn = true
t.renderColumnSeparator(out, hint)
}
func (t *Table) renderColumnColorized(out *strings.Builder, colIdx int, colStr string, hint renderHint) {
colors := t.getColumnColors(colIdx, hint)
if colors != nil {
out.WriteString(colors.Sprint(colStr))
} else if hint.isHeaderRow && t.style.Color.Header != nil {
out.WriteString(t.style.Color.Header.Sprint(colStr))
} else if hint.isFooterRow && t.style.Color.Footer != nil {
out.WriteString(t.style.Color.Footer.Sprint(colStr))
} else if hint.isRegularRow() {
if colIdx == t.indexColumn-1 && t.style.Color.IndexColumn != nil {
out.WriteString(t.style.Color.IndexColumn.Sprint(colStr))
} else if hint.rowNumber%2 == 0 && t.style.Color.RowAlternate != nil {
out.WriteString(t.style.Color.RowAlternate.Sprint(colStr))
} else if t.style.Color.Row != nil {
out.WriteString(t.style.Color.Row.Sprint(colStr))
} else {
out.WriteString(colStr)
}
} else {
out.WriteString(colStr)
}
}
func (t *Table) renderColumnSeparator(out *strings.Builder, hint renderHint) {
if t.style.Options.SeparateColumns {
separator := t.style.Box.MiddleVertical
if hint.isSeparatorRow {
if hint.isBorderTop {
separator = t.style.Box.TopSeparator
} else if hint.isBorderBottom {
separator = t.style.Box.BottomSeparator
} else {
separator = t.style.Box.MiddleSeparator
}
}
colors := t.getSeparatorColors(hint)
if colors.EscapeSeq() != "" {
out.WriteString(colors.Sprint(separator))
} else {
out.WriteString(separator)
}
}
}
func (t *Table) renderLine(out *strings.Builder, row rowStr, hint renderHint) {
// if the output has content, it means that this call is working on line
// number 2 or more; separate them with a newline
if out.Len() > 0 {
out.WriteRune('\n')
}
// use a brand new strings.Builder if a row length limit has been set
var outLine *strings.Builder
if t.allowedRowLength > 0 {
outLine = &strings.Builder{}
} else {
outLine = out
}
// grow the strings.Builder to the maximum possible row length
outLine.Grow(t.maxRowLength)
t.renderMarginLeft(outLine, hint)
for colIdx, maxColumnLength := range t.maxColumnLengths {
t.renderColumn(outLine, row, colIdx, maxColumnLength, hint)
}
t.renderMarginRight(outLine, hint)
// merge the strings.Builder objects if a new one was created earlier
if outLine != out {
outLineStr := outLine.String()
if text.RuneCount(outLineStr) > t.allowedRowLength {
trimLength := t.allowedRowLength - utf8.RuneCountInString(t.style.Box.UnfinishedRow)
if trimLength > 0 {
out.WriteString(text.Trim(outLineStr, trimLength))
out.WriteString(t.style.Box.UnfinishedRow)
}
} else {
out.WriteString(outLineStr)
}
}
// if a page size has been set, and said number of lines has already
// been rendered, and the header is not being rendered right now, render
// the header all over again with a spacing line
if hint.isRegularRow() {
t.numLinesRendered++
if t.pageSize > 0 && t.numLinesRendered%t.pageSize == 0 && !hint.isLastLineOfLastRow() {
t.renderRowsFooter(out)
t.renderRowsBorderBottom(out)
out.WriteString(t.style.Box.PageSeparator)
t.renderRowsBorderTop(out)
t.renderRowsHeader(out)
}
}
}
func (t *Table) renderMarginLeft(out *strings.Builder, hint renderHint) {
if t.style.Options.DrawBorder {
border := t.style.Box.Left
if hint.isBorderTop {
if t.title != "" {
border = t.style.Box.LeftSeparator
} else {
border = t.style.Box.TopLeft
}
} else if hint.isBorderBottom {
border = t.style.Box.BottomLeft
} else if hint.isSeparatorRow {
border = t.style.Box.LeftSeparator
}
colors := t.getBorderColors(hint)
if colors.EscapeSeq() != "" {
out.WriteString(colors.Sprint(border))
} else {
out.WriteString(border)
}
}
}
func (t *Table) renderMarginRight(out *strings.Builder, hint renderHint) {
if t.style.Options.DrawBorder {
border := t.style.Box.Right
if hint.isBorderTop {
if t.title != "" {
border = t.style.Box.RightSeparator
} else {
border = t.style.Box.TopRight
}
} else if hint.isBorderBottom {
border = t.style.Box.BottomRight
} else if hint.isSeparatorRow {
border = t.style.Box.RightSeparator
}
colors := t.getBorderColors(hint)
if colors.EscapeSeq() != "" {
out.WriteString(colors.Sprint(border))
} else {
out.WriteString(border)
}
}
}
func (t *Table) renderRow(out *strings.Builder, rowNum int, row rowStr, hint renderHint) {
if len(row) > 0 {
// fit every column into the allowedColumnLength/maxColumnLength limit
// and in the process find the max. number of lines in any column in
// this row
colMaxLines := 0
rowWrapped := make(rowStr, len(row))
for colIdx, colStr := range row {
rowWrapped[colIdx] = text.WrapText(colStr, t.maxColumnLengths[colIdx])
colNumLines := strings.Count(rowWrapped[colIdx], "\n") + 1
if colNumLines > colMaxLines {
colMaxLines = colNumLines
}
}
// if there is just 1 line in all columns, add the row as such; else
// split each column into individual lines and render them one-by-one
if colMaxLines == 1 {
hint.isLastLineOfRow = true
t.renderLine(out, row, hint)
} else {
// convert one row into N # of rows based on colMaxLines
rowLines := make([]rowStr, len(row))
for colIdx, colStr := range rowWrapped {
rowLines[colIdx] = t.getVAlign(colIdx, hint).ApplyStr(colStr, colMaxLines)
}
for colLineIdx := 0; colLineIdx < colMaxLines; colLineIdx++ {
rowLine := make(rowStr, len(rowLines))
for colIdx, colLines := range rowLines {
rowLine[colIdx] = colLines[colLineIdx]
}
hint.isLastLineOfRow = bool(colLineIdx == colMaxLines-1)
hint.rowLineNumber = colLineIdx + 1
t.renderLine(out, rowLine, hint)
}
}
}
}
func (t *Table) renderRowSeparator(out *strings.Builder, hint renderHint) {
if hint.isBorderTop || hint.isBorderBottom {
if !t.style.Options.DrawBorder {
return
}
} else if hint.isHeaderRow && !t.style.Options.SeparateHeader {
return
} else if hint.isFooterRow && !t.style.Options.SeparateFooter {
return
}
hint.isSeparatorRow = true
hint.rowNumber = -1
t.renderLine(out, t.rowSeparator, hint)
}
func (t *Table) renderRows(out *strings.Builder, rows []rowStr, hint renderHint) {
hintSeparator := hint
hintSeparator.isSeparatorRow = true
for idx, row := range rows {
hint.isFirstRow = bool(idx == 0)
hint.isLastRow = bool(idx == len(rows)-1)
hint.rowNumber = idx + 1
t.renderRow(out, idx+1, row, hint)
if t.style.Options.SeparateRows && idx < len(rows)-1 {
t.renderRowSeparator(out, hintSeparator)
}
}
}
func (t *Table) renderRowsBorderBottom(out *strings.Builder) {
t.renderRowSeparator(out, renderHint{isBorderBottom: true, isFooterRow: true})
}
func (t *Table) renderRowsBorderTop(out *strings.Builder) {
t.renderRowSeparator(out, renderHint{isBorderTop: true, isHeaderRow: true})
}
func (t *Table) renderRowsFooter(out *strings.Builder) {
if len(t.rowsFooter) > 0 {
t.renderRowSeparator(out, renderHint{isFooterRow: true, isSeparatorRow: true})
t.renderRows(out, t.rowsFooter, renderHint{isFooterRow: true})
}
}
func (t *Table) renderRowsHeader(out *strings.Builder) {
// header rows or auto-index row
if len(t.rowsHeader) > 0 || t.autoIndex {
if len(t.rowsHeader) > 0 {
t.renderRows(out, t.rowsHeader, renderHint{isHeaderRow: true})
} else if t.autoIndex {
t.renderRow(out, 0, t.getAutoIndexColumnIDs(), renderHint{isHeaderRow: true})
}
t.renderRowSeparator(out, renderHint{isHeaderRow: true, isSeparatorRow: true})
}
}
func (t *Table) renderTitle(out *strings.Builder) {
if t.title != "" {
if t.style.Options.DrawBorder {
lenBorder := t.maxRowLength - text.RuneCount(t.style.Box.TopLeft+t.style.Box.TopRight)
out.WriteString(t.style.Box.TopLeft)
out.WriteString(text.RepeatAndTrim(t.style.Box.MiddleHorizontal, lenBorder))
out.WriteString(t.style.Box.TopRight)
}
lenText := t.maxRowLength - text.RuneCount(t.style.Box.PaddingLeft+t.style.Box.PaddingRight)
if t.style.Options.DrawBorder {
lenText -= text.RuneCount(t.style.Box.Left + t.style.Box.Right)
}
titleText := text.WrapText(t.title, lenText)
for _, titleLine := range strings.Split(titleText, "\n") {
titleLine = strings.TrimSpace(titleLine)
titleLine = t.style.Title.Format.Apply(titleLine)
titleLine = t.style.Title.Align.Apply(titleLine, lenText)
titleLine = t.style.Box.PaddingLeft + titleLine + t.style.Box.PaddingRight
titleLine = t.style.Title.Colors.Sprint(titleLine)
if out.Len() > 0 {
out.WriteRune('\n')
}
if t.style.Options.DrawBorder {
out.WriteString(t.style.Box.Left)
}
out.WriteString(titleLine)
if t.style.Options.DrawBorder {
out.WriteString(t.style.Box.Right)
}
}
}
}

View File

@@ -0,0 +1,69 @@
package table
import (
"strings"
"unicode/utf8"
)
// RenderCSV renders the Table in CSV format. Example:
// #,First Name,Last Name,Salary,
// 1,Arya,Stark,3000,
// 20,Jon,Snow,2000,"You know nothing\, Jon Snow!"
// 300,Tyrion,Lannister,5000,
// ,,Total,10000,
func (t *Table) RenderCSV() string {
t.initForRender()
var out strings.Builder
if t.numColumns > 0 {
if t.title != "" {
out.WriteString(t.title)
}
t.csvRenderRows(&out, t.rowsHeader)
t.csvRenderRows(&out, t.rows)
t.csvRenderRows(&out, t.rowsFooter)
if t.caption != "" {
out.WriteRune('\n')
out.WriteString(t.caption)
}
}
return t.render(&out)
}
func (t *Table) csvFixCommas(str string) string {
return strings.Replace(str, ",", "\\,", -1)
}
func (t *Table) csvFixDoubleQuotes(str string) string {
return strings.Replace(str, "\"", "\\\"", -1)
}
func (t *Table) csvRenderRow(out *strings.Builder, row rowStr) {
// when working on line number 2 or more, insert a newline first
if out.Len() > 0 {
out.WriteRune('\n')
}
// generate the columns to render in CSV format and append to "out"
for colIdx, colStr := range row {
if colIdx > 0 {
out.WriteRune(',')
}
if strings.ContainsAny(colStr, "\",\n") {
out.WriteRune('"')
out.WriteString(t.csvFixCommas(t.csvFixDoubleQuotes(colStr)))
out.WriteRune('"')
} else if utf8.RuneCountInString(colStr) > 0 {
out.WriteString(colStr)
}
}
for colIdx := len(row); colIdx < t.numColumns; colIdx++ {
out.WriteRune(',')
}
}
func (t *Table) csvRenderRows(out *strings.Builder, rows []rowStr) {
for _, row := range rows {
t.csvRenderRow(out, row)
}
}

View File

@@ -0,0 +1,156 @@
package table
import (
"html"
"strings"
)
const (
// DefaultHTMLCSSClass stores the css-class to use when none-provided via
// SetHTMLCSSClass(cssClass string).
DefaultHTMLCSSClass = "go-pretty-table"
)
// RenderHTML renders the Table in HTML format. Example:
// <table class="go-pretty-table">
// <thead>
// <tr>
// <th align="right">#</th>
// <th>First Name</th>
// <th>Last Name</th>
// <th align="right">Salary</th>
// <th>&nbsp;</th>
// </tr>
// </thead>
// <tbody>
// <tr>
// <td align="right">1</td>
// <td>Arya</td>
// <td>Stark</td>
// <td align="right">3000</td>
// <td>&nbsp;</td>
// </tr>
// <tr>
// <td align="right">20</td>
// <td>Jon</td>
// <td>Snow</td>
// <td align="right">2000</td>
// <td>You know nothing, Jon Snow!</td>
// </tr>
// <tr>
// <td align="right">300</td>
// <td>Tyrion</td>
// <td>Lannister</td>
// <td align="right">5000</td>
// <td>&nbsp;</td>
// </tr>
// </tbody>
// <tfoot>
// <tr>
// <td align="right">&nbsp;</td>
// <td>&nbsp;</td>
// <td>Total</td>
// <td align="right">10000</td>
// <td>&nbsp;</td>
// </tr>
// </tfoot>
// </table>
func (t *Table) RenderHTML() string {
t.initForRender()
var out strings.Builder
if t.numColumns > 0 {
out.WriteString("<table class=\"")
if t.htmlCSSClass != "" {
out.WriteString(t.htmlCSSClass)
} else {
out.WriteString(DefaultHTMLCSSClass)
}
out.WriteString("\">\n")
t.htmlRenderRows(&out, t.rowsHeader, renderHint{isHeaderRow: true})
t.htmlRenderRows(&out, t.rows, renderHint{})
t.htmlRenderRows(&out, t.rowsFooter, renderHint{isFooterRow: true})
out.WriteString("</table>")
}
return t.render(&out)
}
func (t *Table) htmlRenderRow(out *strings.Builder, row rowStr, hint renderHint) {
out.WriteString(" <tr>\n")
for colIdx := 0; colIdx < t.numColumns; colIdx++ {
var colStr string
if colIdx < len(row) {
colStr = row[colIdx]
}
// header uses "th" instead of "td"
colTagName := "td"
if hint.isHeaderRow {
colTagName = "th"
}
// determine the HTML "align"/"valign" property values
align := t.getAlign(colIdx, hint).HTMLProperty()
vAlign := t.getVAlign(colIdx, hint).HTMLProperty()
// determine the HTML "class" property values for the colors
class := t.getColumnColors(colIdx, hint).HTMLProperty()
// write the row
out.WriteString(" <")
out.WriteString(colTagName)
if align != "" {
out.WriteRune(' ')
out.WriteString(align)
}
if class != "" {
out.WriteRune(' ')
out.WriteString(class)
}
if vAlign != "" {
out.WriteRune(' ')
out.WriteString(vAlign)
}
out.WriteString(">")
if len(colStr) > 0 {
out.WriteString(strings.Replace(html.EscapeString(colStr), "\n", "<br/>", -1))
} else {
out.WriteString("&nbsp;")
}
out.WriteString("</")
out.WriteString(colTagName)
out.WriteString(">\n")
}
out.WriteString(" </tr>\n")
}
func (t *Table) htmlRenderRows(out *strings.Builder, rows []rowStr, hint renderHint) {
if len(rows) > 0 {
// determine that tag to use based on the type of the row
rowsTag := "tbody"
if hint.isHeaderRow {
rowsTag = "thead"
} else if hint.isFooterRow {
rowsTag = "tfoot"
}
var renderedTagOpen, shouldRenderTagClose bool
for idx, row := range rows {
hint.rowNumber = idx + 1
if len(row) > 0 {
if !renderedTagOpen {
out.WriteString(" <")
out.WriteString(rowsTag)
out.WriteString(">\n")
renderedTagOpen = true
}
t.htmlRenderRow(out, row, hint)
shouldRenderTagClose = true
}
}
if shouldRenderTagClose {
out.WriteString(" </")
out.WriteString(rowsTag)
out.WriteString(">\n")
}
}
}

View File

@@ -0,0 +1,75 @@
package table
import "strings"
// RenderMarkdown renders the Table in Markdown format. Example:
// | # | First Name | Last Name | Salary | |
// | ---:| --- | --- | ---:| --- |
// | 1 | Arya | Stark | 3000 | |
// | 20 | Jon | Snow | 2000 | You know nothing, Jon Snow! |
// | 300 | Tyrion | Lannister | 5000 | |
// | | | Total | 10000 | |
func (t *Table) RenderMarkdown() string {
t.initForRender()
var out strings.Builder
if t.numColumns > 0 {
if t.title != "" {
out.WriteString("# ")
out.WriteString(t.title)
}
t.markdownRenderRows(&out, t.rowsHeader, true, false)
t.markdownRenderRows(&out, t.rows, false, false)
t.markdownRenderRows(&out, t.rowsFooter, false, true)
if t.caption != "" {
out.WriteRune('\n')
out.WriteRune('_')
out.WriteString(t.caption)
out.WriteRune('_')
}
}
return t.render(&out)
}
func (t *Table) markdownRenderRow(out *strings.Builder, row rowStr, isSeparator bool) {
if len(row) > 0 {
// when working on line number 2 or more, insert a newline first
if out.Len() > 0 {
out.WriteRune('\n')
}
// render each column up to the max. columns seen in all the rows
out.WriteRune('|')
for colIdx := 0; colIdx < t.numColumns; colIdx++ {
if isSeparator {
out.WriteString(t.getAlign(colIdx, renderHint{}).MarkdownProperty())
} else {
var colStr string
if colIdx < len(row) {
colStr = row[colIdx]
}
out.WriteRune(' ')
if strings.Contains(colStr, "|") {
colStr = strings.Replace(colStr, "|", "\\|", -1)
}
if strings.Contains(colStr, "\n") {
colStr = strings.Replace(colStr, "\n", "<br/>", -1)
}
out.WriteString(colStr)
out.WriteRune(' ')
}
out.WriteRune('|')
}
}
}
func (t *Table) markdownRenderRows(out *strings.Builder, rows []rowStr, isHeader bool, isFooter bool) {
if len(rows) > 0 {
for idx, row := range rows {
t.markdownRenderRow(out, row, false)
if idx == len(rows)-1 && isHeader {
t.markdownRenderRow(out, t.rowSeparator, true)
}
}
}
}

119
vendor/github.com/jedib0t/go-pretty/table/sort.go generated vendored Normal file
View File

@@ -0,0 +1,119 @@
package table
import (
"sort"
"strconv"
)
// SortBy defines What to sort (Column Name or Number), and How to sort (Mode).
type SortBy struct {
// Name is the name of the Column as it appears in the first Header row.
// If a Header is not provided, or the name is not found in the header, this
// will not work.
Name string
// Number is the Column # from left. When specified, it overrides the Name
// property. If you know the exact Column number, use this instead of Name.
Number int
// Mode tells the Writer how to Sort. Asc/Dsc/etc.
Mode SortMode
}
// SortMode defines How to sort.
type SortMode int
const (
// Asc sorts the column in Ascending order alphabetically.
Asc SortMode = iota
// AscNumeric sorts the column in Ascending order numerically.
AscNumeric
// Dsc sorts the column in Descending order alphabetically.
Dsc
// DscNumeric sorts the column in Descending order numerically.
DscNumeric
)
type rowsSorter struct {
rows []rowStr
sortBy []SortBy
sortedIndices []int
}
// getSortedRowIndices sorts and returns the row indices in Sorted order as
// directed by Table.sortBy which can be set using Table.SortBy(...)
func (t *Table) getSortedRowIndices() []int {
sortedIndices := make([]int, len(t.rows))
for idx := range t.rows {
sortedIndices[idx] = idx
}
if t.sortBy != nil && len(t.sortBy) > 0 {
sort.Sort(rowsSorter{
rows: t.rows,
sortBy: t.parseSortBy(t.sortBy),
sortedIndices: sortedIndices,
})
}
return sortedIndices
}
func (t *Table) parseSortBy(sortBy []SortBy) []SortBy {
var resSortBy []SortBy
for _, col := range sortBy {
colNum := 0
if col.Number > 0 && col.Number <= t.numColumns {
colNum = col.Number
} else if col.Name != "" && len(t.rowsHeader) > 0 {
for idx, colName := range t.rowsHeader[0] {
if col.Name == colName {
colNum = idx + 1
break
}
}
}
if colNum > 0 {
resSortBy = append(resSortBy, SortBy{
Name: col.Name,
Number: colNum,
Mode: col.Mode,
})
}
}
return resSortBy
}
func (rs rowsSorter) Len() int {
return len(rs.rows)
}
func (rs rowsSorter) Swap(i, j int) {
rs.sortedIndices[i], rs.sortedIndices[j] = rs.sortedIndices[j], rs.sortedIndices[i]
}
func (rs rowsSorter) Less(i, j int) bool {
realI, realJ := rs.sortedIndices[i], rs.sortedIndices[j]
for _, col := range rs.sortBy {
rowI, rowJ, colIdx := rs.rows[realI], rs.rows[realJ], col.Number-1
if colIdx < len(rowI) && colIdx < len(rowJ) {
if rowI[colIdx] == rowJ[colIdx] {
continue
} else if col.Mode == Asc {
return rowI[colIdx] < rowJ[colIdx]
} else if col.Mode == Dsc {
return rowI[colIdx] > rowJ[colIdx]
}
iVal, iErr := strconv.ParseFloat(rowI[colIdx], 64)
jVal, jErr := strconv.ParseFloat(rowJ[colIdx], 64)
if iErr == nil && jErr == nil {
if col.Mode == AscNumeric {
return iVal < jVal
} else if col.Mode == DscNumeric {
return jVal < iVal
}
}
}
}
return false
}

828
vendor/github.com/jedib0t/go-pretty/table/style.go generated vendored Normal file
View File

@@ -0,0 +1,828 @@
package table
import (
"github.com/jedib0t/go-pretty/text"
)
// Style declares how to render the Table and provides very fine-grained control
// on how the Table gets rendered on the Console.
type Style struct {
Name string // name of the Style
Box BoxStyle // characters to use for the boxes
Color ColorOptions // colors to use for the rows and columns
Format FormatOptions // formatting options for the rows and columns
Options Options // misc. options for the table
Title TitleOptions // formation options for the title text
}
var (
// StyleDefault renders a Table like below:
// +-----+------------+-----------+--------+-----------------------------+
// | # | FIRST NAME | LAST NAME | SALARY | |
// +-----+------------+-----------+--------+-----------------------------+
// | 1 | Arya | Stark | 3000 | |
// | 20 | Jon | Snow | 2000 | You know nothing, Jon Snow! |
// | 300 | Tyrion | Lannister | 5000 | |
// +-----+------------+-----------+--------+-----------------------------+
// | | | TOTAL | 10000 | |
// +-----+------------+-----------+--------+-----------------------------+
StyleDefault = Style{
Name: "StyleDefault",
Box: StyleBoxDefault,
Color: ColorOptionsDefault,
Format: FormatOptionsDefault,
Options: OptionsDefault,
Title: TitleOptionsDefault,
}
// StyleBold renders a Table like below:
// ┏━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ # ┃ FIRST NAME ┃ LAST NAME ┃ SALARY ┃ ┃
// ┣━━━━━╋━━━━━━━━━━━━╋━━━━━━━━━━━╋━━━━━━━━╋━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
// ┃ 1 ┃ Arya ┃ Stark ┃ 3000 ┃ ┃
// ┃ 20 ┃ Jon ┃ Snow ┃ 2000 ┃ You know nothing, Jon Snow! ┃
// ┃ 300 ┃ Tyrion ┃ Lannister ┃ 5000 ┃ ┃
// ┣━━━━━╋━━━━━━━━━━━━╋━━━━━━━━━━━╋━━━━━━━━╋━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
// ┃ ┃ ┃ TOTAL ┃ 10000 ┃ ┃
// ┗━━━━━┻━━━━━━━━━━━━┻━━━━━━━━━━━┻━━━━━━━━┻━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
StyleBold = Style{
Name: "StyleBold",
Box: StyleBoxBold,
Color: ColorOptionsDefault,
Format: FormatOptionsDefault,
Options: OptionsDefault,
Title: TitleOptionsDefault,
}
// StyleColoredBright renders a Table without any borders or separators,
// and with Black text on Cyan background for Header/Footer and
// White background for other rows.
StyleColoredBright = Style{
Name: "StyleColoredBright",
Box: StyleBoxDefault,
Color: ColorOptionsBright,
Format: FormatOptionsDefault,
Options: OptionsNoBordersAndSeparators,
Title: TitleOptionsDark,
}
// StyleColoredDark renders a Table without any borders or separators, and
// with Header/Footer in Cyan text and other rows with White text, all on
// Black background.
StyleColoredDark = Style{
Name: "StyleColoredDark",
Box: StyleBoxDefault,
Color: ColorOptionsDark,
Format: FormatOptionsDefault,
Options: OptionsNoBordersAndSeparators,
Title: TitleOptionsBright,
}
// StyleColoredBlackOnBlueWhite renders a Table without any borders or
// separators, and with Black text on Blue background for Header/Footer and
// White background for other rows.
StyleColoredBlackOnBlueWhite = Style{
Name: "StyleColoredBlackOnBlueWhite",
Box: StyleBoxDefault,
Color: ColorOptionsBlackOnBlueWhite,
Format: FormatOptionsDefault,
Options: OptionsNoBordersAndSeparators,
Title: TitleOptionsBlueOnBlack,
}
// StyleColoredBlackOnCyanWhite renders a Table without any borders or
// separators, and with Black text on Cyan background for Header/Footer and
// White background for other rows.
StyleColoredBlackOnCyanWhite = Style{
Name: "StyleColoredBlackOnCyanWhite",
Box: StyleBoxDefault,
Color: ColorOptionsBlackOnCyanWhite,
Format: FormatOptionsDefault,
Options: OptionsNoBordersAndSeparators,
Title: TitleOptionsCyanOnBlack,
}
// StyleColoredBlackOnGreenWhite renders a Table without any borders or
// separators, and with Black text on Green background for Header/Footer and
// White background for other rows.
StyleColoredBlackOnGreenWhite = Style{
Name: "StyleColoredBlackOnGreenWhite",
Box: StyleBoxDefault,
Color: ColorOptionsBlackOnGreenWhite,
Format: FormatOptionsDefault,
Options: OptionsNoBordersAndSeparators,
Title: TitleOptionsGreenOnBlack,
}
// StyleColoredBlackOnMagentaWhite renders a Table without any borders or
// separators, and with Black text on Magenta background for Header/Footer and
// White background for other rows.
StyleColoredBlackOnMagentaWhite = Style{
Name: "StyleColoredBlackOnMagentaWhite",
Box: StyleBoxDefault,
Color: ColorOptionsBlackOnMagentaWhite,
Format: FormatOptionsDefault,
Options: OptionsNoBordersAndSeparators,
Title: TitleOptionsMagentaOnBlack,
}
// StyleColoredBlackOnYellowWhite renders a Table without any borders or
// separators, and with Black text on Yellow background for Header/Footer and
// White background for other rows.
StyleColoredBlackOnYellowWhite = Style{
Name: "StyleColoredBlackOnYellowWhite",
Box: StyleBoxDefault,
Color: ColorOptionsBlackOnYellowWhite,
Format: FormatOptionsDefault,
Options: OptionsNoBordersAndSeparators,
Title: TitleOptionsYellowOnBlack,
}
// StyleColoredBlackOnRedWhite renders a Table without any borders or
// separators, and with Black text on Red background for Header/Footer and
// White background for other rows.
StyleColoredBlackOnRedWhite = Style{
Name: "StyleColoredBlackOnRedWhite",
Box: StyleBoxDefault,
Color: ColorOptionsBlackOnRedWhite,
Format: FormatOptionsDefault,
Options: OptionsNoBordersAndSeparators,
Title: TitleOptionsRedOnBlack,
}
// StyleColoredBlueWhiteOnBlack renders a Table without any borders or
// separators, and with Header/Footer in Blue text and other rows with
// White text, all on Black background.
StyleColoredBlueWhiteOnBlack = Style{
Name: "StyleColoredBlueWhiteOnBlack",
Box: StyleBoxDefault,
Color: ColorOptionsBlueWhiteOnBlack,
Format: FormatOptionsDefault,
Options: OptionsNoBordersAndSeparators,
Title: TitleOptionsBlackOnBlue,
}
// StyleColoredCyanWhiteOnBlack renders a Table without any borders or
// separators, and with Header/Footer in Cyan text and other rows with
// White text, all on Black background.
StyleColoredCyanWhiteOnBlack = Style{
Name: "StyleColoredCyanWhiteOnBlack",
Box: StyleBoxDefault,
Color: ColorOptionsCyanWhiteOnBlack,
Format: FormatOptionsDefault,
Options: OptionsNoBordersAndSeparators,
Title: TitleOptionsBlackOnCyan,
}
// StyleColoredGreenWhiteOnBlack renders a Table without any borders or
// separators, and with Header/Footer in Green text and other rows with
// White text, all on Black background.
StyleColoredGreenWhiteOnBlack = Style{
Name: "StyleColoredGreenWhiteOnBlack",
Box: StyleBoxDefault,
Color: ColorOptionsGreenWhiteOnBlack,
Format: FormatOptionsDefault,
Options: OptionsNoBordersAndSeparators,
Title: TitleOptionsBlackOnGreen,
}
// StyleColoredMagentaWhiteOnBlack renders a Table without any borders or
// separators, and with Header/Footer in Magenta text and other rows with
// White text, all on Black background.
StyleColoredMagentaWhiteOnBlack = Style{
Name: "StyleColoredMagentaWhiteOnBlack",
Box: StyleBoxDefault,
Color: ColorOptionsMagentaWhiteOnBlack,
Format: FormatOptionsDefault,
Options: OptionsNoBordersAndSeparators,
Title: TitleOptionsBlackOnMagenta,
}
// StyleColoredRedWhiteOnBlack renders a Table without any borders or
// separators, and with Header/Footer in Red text and other rows with
// White text, all on Black background.
StyleColoredRedWhiteOnBlack = Style{
Name: "StyleColoredRedWhiteOnBlack",
Box: StyleBoxDefault,
Color: ColorOptionsRedWhiteOnBlack,
Format: FormatOptionsDefault,
Options: OptionsNoBordersAndSeparators,
Title: TitleOptionsBlackOnRed,
}
// StyleColoredYellowWhiteOnBlack renders a Table without any borders or
// separators, and with Header/Footer in Yellow text and other rows with
// White text, all on Black background.
StyleColoredYellowWhiteOnBlack = Style{
Name: "StyleColoredYellowWhiteOnBlack",
Box: StyleBoxDefault,
Color: ColorOptionsYellowWhiteOnBlack,
Format: FormatOptionsDefault,
Options: OptionsNoBordersAndSeparators,
Title: TitleOptionsBlackOnYellow,
}
// StyleDouble renders a Table like below:
// ╔═════╦════════════╦═══════════╦════════╦═════════════════════════════╗
// ║ # ║ FIRST NAME ║ LAST NAME ║ SALARY ║ ║
// ╠═════╬════════════╬═══════════╬════════╬═════════════════════════════╣
// ║ 1 ║ Arya ║ Stark ║ 3000 ║ ║
// ║ 20 ║ Jon ║ Snow ║ 2000 ║ You know nothing, Jon Snow! ║
// ║ 300 ║ Tyrion ║ Lannister ║ 5000 ║ ║
// ╠═════╬════════════╬═══════════╬════════╬═════════════════════════════╣
// ║ ║ ║ TOTAL ║ 10000 ║ ║
// ╚═════╩════════════╩═══════════╩════════╩═════════════════════════════╝
StyleDouble = Style{
Name: "StyleDouble",
Box: StyleBoxDouble,
Color: ColorOptionsDefault,
Format: FormatOptionsDefault,
Options: OptionsDefault,
Title: TitleOptionsDefault,
}
// StyleLight renders a Table like below:
// ┌─────┬────────────┬───────────┬────────┬─────────────────────────────┐
// │ # │ FIRST NAME │ LAST NAME │ SALARY │ │
// ├─────┼────────────┼───────────┼────────┼─────────────────────────────┤
// │ 1 │ Arya │ Stark │ 3000 │ │
// │ 20 │ Jon │ Snow │ 2000 │ You know nothing, Jon Snow! │
// │ 300 │ Tyrion │ Lannister │ 5000 │ │
// ├─────┼────────────┼───────────┼────────┼─────────────────────────────┤
// │ │ │ TOTAL │ 10000 │ │
// └─────┴────────────┴───────────┴────────┴─────────────────────────────┘
StyleLight = Style{
Name: "StyleLight",
Box: StyleBoxLight,
Color: ColorOptionsDefault,
Format: FormatOptionsDefault,
Options: OptionsDefault,
Title: TitleOptionsDefault,
}
// StyleRounded renders a Table like below:
// ╭─────┬────────────┬───────────┬────────┬─────────────────────────────╮
// │ # │ FIRST NAME │ LAST NAME │ SALARY │ │
// ├─────┼────────────┼───────────┼────────┼─────────────────────────────┤
// │ 1 │ Arya │ Stark │ 3000 │ │
// │ 20 │ Jon │ Snow │ 2000 │ You know nothing, Jon Snow! │
// │ 300 │ Tyrion │ Lannister │ 5000 │ │
// ├─────┼────────────┼───────────┼────────┼─────────────────────────────┤
// │ │ │ TOTAL │ 10000 │ │
// ╰─────┴────────────┴───────────┴────────┴─────────────────────────────╯
StyleRounded = Style{
Name: "StyleRounded",
Box: StyleBoxRounded,
Color: ColorOptionsDefault,
Format: FormatOptionsDefault,
Options: OptionsDefault,
Title: TitleOptionsDefault,
}
// styleTest renders a Table like below:
// (-----^------------^-----------^--------^-----------------------------)
// [< #>|<FIRST NAME>|<LAST NAME>|<SALARY>|< >]
// {-----+------------+-----------+--------+-----------------------------}
// [< 1>|<Arya >|<Stark >|< 3000>|< >]
// [< 20>|<Jon >|<Snow >|< 2000>|<You know nothing, Jon Snow!>]
// [<300>|<Tyrion >|<Lannister>|< 5000>|< >]
// {-----+------------+-----------+--------+-----------------------------}
// [< >|< >|<TOTAL >|< 10000>|< >]
// \-----v------------v-----------v--------v-----------------------------/
styleTest = Style{
Name: "styleTest",
Box: styleBoxTest,
Color: ColorOptionsDefault,
Format: FormatOptionsDefault,
Options: OptionsDefault,
Title: TitleOptionsDefault,
}
)
// BoxStyle defines the characters/strings to use to render the borders and
// separators for the Table.
type BoxStyle struct {
BottomLeft string
BottomRight string
BottomSeparator string
Left string
LeftSeparator string
MiddleHorizontal string
MiddleSeparator string
MiddleVertical string
PaddingLeft string
PaddingRight string
PageSeparator string
Right string
RightSeparator string
TopLeft string
TopRight string
TopSeparator string
UnfinishedRow string
}
var (
// StyleBoxDefault defines a Boxed-Table like below:
// +-----+------------+-----------+--------+-----------------------------+
// | # | FIRST NAME | LAST NAME | SALARY | |
// +-----+------------+-----------+--------+-----------------------------+
// | 1 | Arya | Stark | 3000 | |
// | 20 | Jon | Snow | 2000 | You know nothing, Jon Snow! |
// | 300 | Tyrion | Lannister | 5000 | |
// +-----+------------+-----------+--------+-----------------------------+
// | | | TOTAL | 10000 | |
// +-----+------------+-----------+--------+-----------------------------+
StyleBoxDefault = BoxStyle{
BottomLeft: "+",
BottomRight: "+",
BottomSeparator: "+",
Left: "|",
LeftSeparator: "+",
MiddleHorizontal: "-",
MiddleSeparator: "+",
MiddleVertical: "|",
PaddingLeft: " ",
PaddingRight: " ",
PageSeparator: "\n",
Right: "|",
RightSeparator: "+",
TopLeft: "+",
TopRight: "+",
TopSeparator: "+",
UnfinishedRow: " ~",
}
// StyleBoxBold defines a Boxed-Table like below:
// ┏━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ # ┃ FIRST NAME ┃ LAST NAME ┃ SALARY ┃ ┃
// ┣━━━━━╋━━━━━━━━━━━━╋━━━━━━━━━━━╋━━━━━━━━╋━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
// ┃ 1 ┃ Arya ┃ Stark ┃ 3000 ┃ ┃
// ┃ 20 ┃ Jon ┃ Snow ┃ 2000 ┃ You know nothing, Jon Snow! ┃
// ┃ 300 ┃ Tyrion ┃ Lannister ┃ 5000 ┃ ┃
// ┣━━━━━╋━━━━━━━━━━━━╋━━━━━━━━━━━╋━━━━━━━━╋━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
// ┃ ┃ ┃ TOTAL ┃ 10000 ┃ ┃
// ┗━━━━━┻━━━━━━━━━━━━┻━━━━━━━━━━━┻━━━━━━━━┻━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
StyleBoxBold = BoxStyle{
BottomLeft: "┗",
BottomRight: "┛",
BottomSeparator: "┻",
Left: "┃",
LeftSeparator: "┣",
MiddleHorizontal: "━",
MiddleSeparator: "╋",
MiddleVertical: "┃",
PaddingLeft: " ",
PaddingRight: " ",
PageSeparator: "\n",
Right: "┃",
RightSeparator: "┫",
TopLeft: "┏",
TopRight: "┓",
TopSeparator: "┳",
UnfinishedRow: " ≈",
}
// StyleBoxDouble defines a Boxed-Table like below:
// ╔═════╦════════════╦═══════════╦════════╦═════════════════════════════╗
// ║ # ║ FIRST NAME ║ LAST NAME ║ SALARY ║ ║
// ╠═════╬════════════╬═══════════╬════════╬═════════════════════════════╣
// ║ 1 ║ Arya ║ Stark ║ 3000 ║ ║
// ║ 20 ║ Jon ║ Snow ║ 2000 ║ You know nothing, Jon Snow! ║
// ║ 300 ║ Tyrion ║ Lannister ║ 5000 ║ ║
// ╠═════╬════════════╬═══════════╬════════╬═════════════════════════════╣
// ║ ║ ║ TOTAL ║ 10000 ║ ║
// ╚═════╩════════════╩═══════════╩════════╩═════════════════════════════╝
StyleBoxDouble = BoxStyle{
BottomLeft: "╚",
BottomRight: "╝",
BottomSeparator: "╩",
Left: "║",
LeftSeparator: "╠",
MiddleHorizontal: "═",
MiddleSeparator: "╬",
MiddleVertical: "║",
PaddingLeft: " ",
PaddingRight: " ",
PageSeparator: "\n",
Right: "║",
RightSeparator: "╣",
TopLeft: "╔",
TopRight: "╗",
TopSeparator: "╦",
UnfinishedRow: " ≈",
}
// StyleBoxLight defines a Boxed-Table like below:
// ┌─────┬────────────┬───────────┬────────┬─────────────────────────────┐
// │ # │ FIRST NAME │ LAST NAME │ SALARY │ │
// ├─────┼────────────┼───────────┼────────┼─────────────────────────────┤
// │ 1 │ Arya │ Stark │ 3000 │ │
// │ 20 │ Jon │ Snow │ 2000 │ You know nothing, Jon Snow! │
// │ 300 │ Tyrion │ Lannister │ 5000 │ │
// ├─────┼────────────┼───────────┼────────┼─────────────────────────────┤
// │ │ │ TOTAL │ 10000 │ │
// └─────┴────────────┴───────────┴────────┴─────────────────────────────┘
StyleBoxLight = BoxStyle{
BottomLeft: "└",
BottomRight: "┘",
BottomSeparator: "┴",
Left: "│",
LeftSeparator: "├",
MiddleHorizontal: "─",
MiddleSeparator: "┼",
MiddleVertical: "│",
PaddingLeft: " ",
PaddingRight: " ",
PageSeparator: "\n",
Right: "│",
RightSeparator: "┤",
TopLeft: "┌",
TopRight: "┐",
TopSeparator: "┬",
UnfinishedRow: " ≈",
}
// StyleBoxRounded defines a Boxed-Table like below:
// ╭─────┬────────────┬───────────┬────────┬─────────────────────────────╮
// │ # │ FIRST NAME │ LAST NAME │ SALARY │ │
// ├─────┼────────────┼───────────┼────────┼─────────────────────────────┤
// │ 1 │ Arya │ Stark │ 3000 │ │
// │ 20 │ Jon │ Snow │ 2000 │ You know nothing, Jon Snow! │
// │ 300 │ Tyrion │ Lannister │ 5000 │ │
// ├─────┼────────────┼───────────┼────────┼─────────────────────────────┤
// │ │ │ TOTAL │ 10000 │ │
// ╰─────┴────────────┴───────────┴────────┴─────────────────────────────╯
StyleBoxRounded = BoxStyle{
BottomLeft: "╰",
BottomRight: "╯",
BottomSeparator: "┴",
Left: "│",
LeftSeparator: "├",
MiddleHorizontal: "─",
MiddleSeparator: "┼",
MiddleVertical: "│",
PaddingLeft: " ",
PaddingRight: " ",
PageSeparator: "\n",
Right: "│",
RightSeparator: "┤",
TopLeft: "╭",
TopRight: "╮",
TopSeparator: "┬",
UnfinishedRow: " ≈",
}
// styleBoxTest defines a Boxed-Table like below:
// (-----^------------^-----------^--------^-----------------------------)
// [< #>|<FIRST NAME>|<LAST NAME>|<SALARY>|< >]
// {-----+------------+-----------+--------+-----------------------------}
// [< 1>|<Arya >|<Stark >|< 3000>|< >]
// [< 20>|<Jon >|<Snow >|< 2000>|<You know nothing, Jon Snow!>]
// [<300>|<Tyrion >|<Lannister>|< 5000>|< >]
// {-----+------------+-----------+--------+-----------------------------}
// [< >|< >|<TOTAL >|< 10000>|< >]
// \-----v------------v-----------v--------v-----------------------------/
styleBoxTest = BoxStyle{
BottomLeft: "\\",
BottomRight: "/",
BottomSeparator: "v",
Left: "[",
LeftSeparator: "{",
MiddleHorizontal: "--",
MiddleSeparator: "+",
MiddleVertical: "|",
PaddingLeft: "<",
PaddingRight: ">",
PageSeparator: "\n",
Right: "]",
RightSeparator: "}",
TopLeft: "(",
TopRight: ")",
TopSeparator: "^",
UnfinishedRow: " ~~~",
}
)
// ColorOptions defines the ANSI colors to use for parts of the Table.
type ColorOptions struct {
IndexColumn text.Colors // index-column colors (row #, etc.)
Footer text.Colors // footer row(s) colors
Header text.Colors // header row(s) colors
Row text.Colors // regular row(s) colors
RowAlternate text.Colors // regular row(s) colors for the even-numbered rows
}
var (
// ColorOptionsDefault defines sensible ANSI color options - basically NONE.
ColorOptionsDefault = ColorOptions{}
// ColorOptionsBright renders dark text on bright background.
ColorOptionsBright = ColorOptionsBlackOnCyanWhite
// ColorOptionsDark renders bright text on dark background.
ColorOptionsDark = ColorOptionsCyanWhiteOnBlack
// ColorOptionsBlackOnBlueWhite renders Black text on Blue/White background.
ColorOptionsBlackOnBlueWhite = ColorOptions{
IndexColumn: text.Colors{text.BgHiBlue, text.FgBlack},
Footer: text.Colors{text.BgBlue, text.FgBlack},
Header: text.Colors{text.BgHiBlue, text.FgBlack},
Row: text.Colors{text.BgHiWhite, text.FgBlack},
RowAlternate: text.Colors{text.BgWhite, text.FgBlack},
}
// ColorOptionsBlackOnCyanWhite renders Black text on Cyan/White background.
ColorOptionsBlackOnCyanWhite = ColorOptions{
IndexColumn: text.Colors{text.BgHiCyan, text.FgBlack},
Footer: text.Colors{text.BgCyan, text.FgBlack},
Header: text.Colors{text.BgHiCyan, text.FgBlack},
Row: text.Colors{text.BgHiWhite, text.FgBlack},
RowAlternate: text.Colors{text.BgWhite, text.FgBlack},
}
// ColorOptionsBlackOnGreenWhite renders Black text on Green/White
// background.
ColorOptionsBlackOnGreenWhite = ColorOptions{
IndexColumn: text.Colors{text.BgHiGreen, text.FgBlack},
Footer: text.Colors{text.BgGreen, text.FgBlack},
Header: text.Colors{text.BgHiGreen, text.FgBlack},
Row: text.Colors{text.BgHiWhite, text.FgBlack},
RowAlternate: text.Colors{text.BgWhite, text.FgBlack},
}
// ColorOptionsBlackOnMagentaWhite renders Black text on Magenta/White
// background.
ColorOptionsBlackOnMagentaWhite = ColorOptions{
IndexColumn: text.Colors{text.BgHiMagenta, text.FgBlack},
Footer: text.Colors{text.BgMagenta, text.FgBlack},
Header: text.Colors{text.BgHiMagenta, text.FgBlack},
Row: text.Colors{text.BgHiWhite, text.FgBlack},
RowAlternate: text.Colors{text.BgWhite, text.FgBlack},
}
// ColorOptionsBlackOnRedWhite renders Black text on Red/White background.
ColorOptionsBlackOnRedWhite = ColorOptions{
IndexColumn: text.Colors{text.BgHiRed, text.FgBlack},
Footer: text.Colors{text.BgRed, text.FgBlack},
Header: text.Colors{text.BgHiRed, text.FgBlack},
Row: text.Colors{text.BgHiWhite, text.FgBlack},
RowAlternate: text.Colors{text.BgWhite, text.FgBlack},
}
// ColorOptionsBlackOnYellowWhite renders Black text on Yellow/White
// background.
ColorOptionsBlackOnYellowWhite = ColorOptions{
IndexColumn: text.Colors{text.BgHiYellow, text.FgBlack},
Footer: text.Colors{text.BgYellow, text.FgBlack},
Header: text.Colors{text.BgHiYellow, text.FgBlack},
Row: text.Colors{text.BgHiWhite, text.FgBlack},
RowAlternate: text.Colors{text.BgWhite, text.FgBlack},
}
// ColorOptionsBlueWhiteOnBlack renders Blue/White text on Black background.
ColorOptionsBlueWhiteOnBlack = ColorOptions{
IndexColumn: text.Colors{text.FgHiBlue, text.BgHiBlack},
Footer: text.Colors{text.FgBlue, text.BgHiBlack},
Header: text.Colors{text.FgHiBlue, text.BgHiBlack},
Row: text.Colors{text.FgHiWhite, text.BgBlack},
RowAlternate: text.Colors{text.FgWhite, text.BgBlack},
}
// ColorOptionsCyanWhiteOnBlack renders Cyan/White text on Black background.
ColorOptionsCyanWhiteOnBlack = ColorOptions{
IndexColumn: text.Colors{text.FgHiCyan, text.BgHiBlack},
Footer: text.Colors{text.FgCyan, text.BgHiBlack},
Header: text.Colors{text.FgHiCyan, text.BgHiBlack},
Row: text.Colors{text.FgHiWhite, text.BgBlack},
RowAlternate: text.Colors{text.FgWhite, text.BgBlack},
}
// ColorOptionsGreenWhiteOnBlack renders Green/White text on Black
// background.
ColorOptionsGreenWhiteOnBlack = ColorOptions{
IndexColumn: text.Colors{text.FgHiGreen, text.BgHiBlack},
Footer: text.Colors{text.FgGreen, text.BgHiBlack},
Header: text.Colors{text.FgHiGreen, text.BgHiBlack},
Row: text.Colors{text.FgHiWhite, text.BgBlack},
RowAlternate: text.Colors{text.FgWhite, text.BgBlack},
}
// ColorOptionsMagentaWhiteOnBlack renders Magenta/White text on Black
// background.
ColorOptionsMagentaWhiteOnBlack = ColorOptions{
IndexColumn: text.Colors{text.FgHiMagenta, text.BgHiBlack},
Footer: text.Colors{text.FgMagenta, text.BgHiBlack},
Header: text.Colors{text.FgHiMagenta, text.BgHiBlack},
Row: text.Colors{text.FgHiWhite, text.BgBlack},
RowAlternate: text.Colors{text.FgWhite, text.BgBlack},
}
// ColorOptionsRedWhiteOnBlack renders Red/White text on Black background.
ColorOptionsRedWhiteOnBlack = ColorOptions{
IndexColumn: text.Colors{text.FgHiRed, text.BgHiBlack},
Footer: text.Colors{text.FgRed, text.BgHiBlack},
Header: text.Colors{text.FgHiRed, text.BgHiBlack},
Row: text.Colors{text.FgHiWhite, text.BgBlack},
RowAlternate: text.Colors{text.FgWhite, text.BgBlack},
}
// ColorOptionsYellowWhiteOnBlack renders Yellow/White text on Black
// background.
ColorOptionsYellowWhiteOnBlack = ColorOptions{
IndexColumn: text.Colors{text.FgHiYellow, text.BgHiBlack},
Footer: text.Colors{text.FgYellow, text.BgHiBlack},
Header: text.Colors{text.FgHiYellow, text.BgHiBlack},
Row: text.Colors{text.FgHiWhite, text.BgBlack},
RowAlternate: text.Colors{text.FgWhite, text.BgBlack},
}
)
// FormatOptions defines the text-formatting to perform on parts of the Table.
type FormatOptions struct {
Footer text.Format // footer row(s) text format
Header text.Format // header row(s) text format
Row text.Format // (data) row(s) text format
}
var (
// FormatOptionsDefault defines sensible formatting options.
FormatOptionsDefault = FormatOptions{
Footer: text.FormatUpper,
Header: text.FormatUpper,
Row: text.FormatDefault,
}
)
// Options defines the global options that determine how the Table is
// rendered.
type Options struct {
// DrawBorder enables or disables drawing the border around the Table.
// Example of a table where it is disabled:
// # │ FIRST NAME │ LAST NAME │ SALARY │
// ─────┼────────────┼───────────┼────────┼─────────────────────────────
// 1 │ Arya │ Stark │ 3000 │
// 20 │ Jon │ Snow │ 2000 │ You know nothing, Jon Snow!
// 300 │ Tyrion │ Lannister │ 5000 │
// ─────┼────────────┼───────────┼────────┼─────────────────────────────
// │ │ TOTAL │ 10000 │
DrawBorder bool
// SeparateColumns enables or disable drawing border between columns.
// Example of a table where it is disabled:
// ┌─────────────────────────────────────────────────────────────────┐
// │ # FIRST NAME LAST NAME SALARY │
// ├─────────────────────────────────────────────────────────────────┤
// │ 1 Arya Stark 3000 │
// │ 20 Jon Snow 2000 You know nothing, Jon Snow! │
// │ 300 Tyrion Lannister 5000 │
// │ TOTAL 10000 │
// └─────────────────────────────────────────────────────────────────┘
SeparateColumns bool
// SeparateFooter enables or disable drawing border between the footer and
// the rows. Example of a table where it is disabled:
// ┌─────┬────────────┬───────────┬────────┬─────────────────────────────┐
// │ # │ FIRST NAME │ LAST NAME │ SALARY │ │
// ├─────┼────────────┼───────────┼────────┼─────────────────────────────┤
// │ 1 │ Arya │ Stark │ 3000 │ │
// │ 20 │ Jon │ Snow │ 2000 │ You know nothing, Jon Snow! │
// │ 300 │ Tyrion │ Lannister │ 5000 │ │
// │ │ │ TOTAL │ 10000 │ │
// └─────┴────────────┴───────────┴────────┴─────────────────────────────┘
SeparateFooter bool
// SeparateHeader enables or disable drawing border between the header and
// the rows. Example of a table where it is disabled:
// ┌─────┬────────────┬───────────┬────────┬─────────────────────────────┐
// │ # │ FIRST NAME │ LAST NAME │ SALARY │ │
// │ 1 │ Arya │ Stark │ 3000 │ │
// │ 20 │ Jon │ Snow │ 2000 │ You know nothing, Jon Snow! │
// │ 300 │ Tyrion │ Lannister │ 5000 │ │
// ├─────┼────────────┼───────────┼────────┼─────────────────────────────┤
// │ │ │ TOTAL │ 10000 │ │
// └─────┴────────────┴───────────┴────────┴─────────────────────────────┘
SeparateHeader bool
// SeparateRows enables or disables drawing separators between each row.
// Example of a table where it is enabled:
// ┌─────┬────────────┬───────────┬────────┬─────────────────────────────┐
// │ # │ FIRST NAME │ LAST NAME │ SALARY │ │
// ├─────┼────────────┼───────────┼────────┼─────────────────────────────┤
// │ 1 │ Arya │ Stark │ 3000 │ │
// ├─────┼────────────┼───────────┼────────┼─────────────────────────────┤
// │ 20 │ Jon │ Snow │ 2000 │ You know nothing, Jon Snow! │
// ├─────┼────────────┼───────────┼────────┼─────────────────────────────┤
// │ 300 │ Tyrion │ Lannister │ 5000 │ │
// ├─────┼────────────┼───────────┼────────┼─────────────────────────────┤
// │ │ │ TOTAL │ 10000 │ │
// └─────┴────────────┴───────────┴────────┴─────────────────────────────┘
SeparateRows bool
}
var (
// OptionsDefault defines sensible global options.
OptionsDefault = Options{
DrawBorder: true,
SeparateColumns: true,
SeparateFooter: true,
SeparateHeader: true,
SeparateRows: false,
}
// OptionsNoBorders sets up a table without any borders.
OptionsNoBorders = Options{
DrawBorder: false,
SeparateColumns: true,
SeparateFooter: true,
SeparateHeader: true,
SeparateRows: false,
}
// OptionsNoBordersAndSeparators sets up a table without any borders or
// separators.
OptionsNoBordersAndSeparators = Options{
DrawBorder: false,
SeparateColumns: false,
SeparateFooter: false,
SeparateHeader: false,
SeparateRows: false,
}
)
// TitleOptions defines the way the title text is to be rendered.
type TitleOptions struct {
Align text.Align
Colors text.Colors
Format text.Format
}
var (
// TitleOptionsDefault defines sensible title options - basically NONE.
TitleOptionsDefault = TitleOptions{}
// TitleOptionsBright renders Bright Bold text on Dark background.
TitleOptionsBright = TitleOptionsBlackOnCyan
// TitleOptionsDark renders Dark Bold text on Bright background.
TitleOptionsDark = TitleOptionsCyanOnBlack
// TitleOptionsBlackOnBlue renders Black text on Blue background.
TitleOptionsBlackOnBlue = TitleOptions{
Colors: append(ColorOptionsBlackOnBlueWhite.Header, text.Bold),
}
// TitleOptionsBlackOnCyan renders Black Bold text on Cyan background.
TitleOptionsBlackOnCyan = TitleOptions{
Colors: append(ColorOptionsBlackOnCyanWhite.Header, text.Bold),
}
// TitleOptionsBlackOnGreen renders Black Bold text onGreen background.
TitleOptionsBlackOnGreen = TitleOptions{
Colors: append(ColorOptionsBlackOnGreenWhite.Header, text.Bold),
}
// TitleOptionsBlackOnMagenta renders Black Bold text on Magenta background.
TitleOptionsBlackOnMagenta = TitleOptions{
Colors: append(ColorOptionsBlackOnMagentaWhite.Header, text.Bold),
}
// TitleOptionsBlackOnRed renders Black Bold text on Red background.
TitleOptionsBlackOnRed = TitleOptions{
Colors: append(ColorOptionsBlackOnRedWhite.Header, text.Bold),
}
// TitleOptionsBlackOnYellow renders Black Bold text on Yellow background.
TitleOptionsBlackOnYellow = TitleOptions{
Colors: append(ColorOptionsBlackOnYellowWhite.Header, text.Bold),
}
// TitleOptionsBlueOnBlack renders Blue Bold text on Black background.
TitleOptionsBlueOnBlack = TitleOptions{
Colors: append(ColorOptionsBlueWhiteOnBlack.Header, text.Bold),
}
// TitleOptionsCyanOnBlack renders Cyan Bold text on Black background.
TitleOptionsCyanOnBlack = TitleOptions{
Colors: append(ColorOptionsCyanWhiteOnBlack.Header, text.Bold),
}
// TitleOptionsGreenOnBlack renders Green Bold text on Black background.
TitleOptionsGreenOnBlack = TitleOptions{
Colors: append(ColorOptionsGreenWhiteOnBlack.Header, text.Bold),
}
// TitleOptionsMagentaOnBlack renders Magenta Bold text on Black background.
TitleOptionsMagentaOnBlack = TitleOptions{
Colors: append(ColorOptionsMagentaWhiteOnBlack.Header, text.Bold),
}
// TitleOptionsRedOnBlack renders Red Bold text on Black background.
TitleOptionsRedOnBlack = TitleOptions{
Colors: append(ColorOptionsRedWhiteOnBlack.Header, text.Bold),
}
// TitleOptionsYellowOnBlack renders Yellow Bold text on Black background.
TitleOptionsYellowOnBlack = TitleOptions{
Colors: append(ColorOptionsYellowWhiteOnBlack.Header, text.Bold),
}
)

687
vendor/github.com/jedib0t/go-pretty/table/table.go generated vendored Normal file
View File

@@ -0,0 +1,687 @@
package table
import (
"fmt"
"io"
"strings"
"github.com/jedib0t/go-pretty/text"
)
// Row defines a single row in the Table.
type Row []interface{}
// RowPainter is a custom function that takes a Row as input and returns the
// text.Colors{} to use on the entire row
type RowPainter func(row Row) text.Colors
// rowStr defines a single row in the Table comprised of just string objects.
type rowStr []string
// Table helps print a 2-dimensional array in a human readable pretty-table.
type Table struct {
// align describes the horizontal-align for each column
align []text.Align
// alignFooter describes the horizontal-align for each column in the footer
alignFooter []text.Align
// alignHeader describes the horizontal-align for each column in the header
alignHeader []text.Align
// allowedColumnLengths contains the max allowed length for each column
allowedColumnLengths []int
// allowedRowLength is the max allowed length for a row (or line of output)
allowedRowLength int
// enable automatic indexing of the rows and columns like a spreadsheet?
autoIndex bool
// autoIndexVIndexMaxLength denotes the length in chars for the last rownum
autoIndexVIndexMaxLength int
// caption stores the text to be rendered just below the table; and doesn't
// get used when rendered as a CSV
caption string
// colors contains Colorization options for the body
colors []text.Colors
// colorsFooter contains Colorization options for the footer
colorsFooter []text.Colors
// colorsHeader contains Colorization options for the header
colorsHeader []text.Colors
// columnIsNonNumeric stores if a column contains non-numbers in all rows
columnIsNonNumeric []bool
// columnConfigs stores the custom-configuration for 1 or more columns
columnConfigs []ColumnConfig
// columnConfigMap stores the custom-configuration by column
// number and is generated before rendering
columnConfigMap map[int]ColumnConfig
// htmlCSSClass stores the HTML CSS Class to use on the <table> node
htmlCSSClass string
// indexColumn stores the number of the column considered as the "index"
indexColumn int
// maxColumnLengths stores the length of the longest line in each column
maxColumnLengths []int
// maxRowLength stores the length of the longest row
maxRowLength int
// numColumns stores the (max.) number of columns seen
numColumns int
// numLinesRendered keeps track of the number of lines rendered and helps in
// paginating long tables
numLinesRendered int
// outputMirror stores an io.Writer where the "Render" functions would write
outputMirror io.Writer
// pageSize stores the maximum lines to render before rendering the header
// again (to denote a page break) - useful when you are dealing with really
// long tables
pageSize int
// rows stores the rows that make up the body (in string form)
rows []rowStr
// rowsColors stores the text.Colors over-rides for each row as defined by
// rowPainter
rowsColors []text.Colors
// rowsRaw stores the rows that make up the body
rowsRaw []Row
// rowsFooter stores the rows that make up the footer (in string form)
rowsFooter []rowStr
// rowsFooterRaw stores the rows that make up the footer
rowsFooterRaw []Row
// rowsHeader stores the rows that make up the header (in string form)
rowsHeader []rowStr
// rowsHeaderRaw stores the rows that make up the header
rowsHeaderRaw []Row
// rowPainter is a custom function that given a Row, returns the colors to
// use on the entire row
rowPainter RowPainter
// rowSeparator is a dummy row that contains the separator columns (dashes
// that make up the separator between header/body/footer
rowSeparator rowStr
// sortBy stores a map of Column
sortBy []SortBy
// style contains all the strings used to draw the table, and more
style *Style
// title contains the text to appear above the table
title string
// vAlign describes the vertical-align for each column
vAlign []text.VAlign
// vAlign describes the vertical-align for each column in the footer
vAlignFooter []text.VAlign
// vAlign describes the vertical-align for each column in the header
vAlignHeader []text.VAlign
}
// AppendFooter appends the row to the List of footers to render.
func (t *Table) AppendFooter(row Row) {
t.rowsFooterRaw = append(t.rowsFooterRaw, row)
}
// AppendHeader appends the row to the List of headers to render.
func (t *Table) AppendHeader(row Row) {
t.rowsHeaderRaw = append(t.rowsHeaderRaw, row)
}
// AppendRow appends the row to the List of rows to render.
func (t *Table) AppendRow(row Row) {
t.rowsRaw = append(t.rowsRaw, row)
}
// AppendRows appends the rows to the List of rows to render.
func (t *Table) AppendRows(rows []Row) {
for _, row := range rows {
t.AppendRow(row)
}
}
// Length returns the number of rows to be rendered.
func (t *Table) Length() int {
return len(t.rowsRaw)
}
// SetAlign sets the horizontal-align for each column in the (data) rows.
//
// Deprecated: Use SetColumnConfigs instead.
func (t *Table) SetAlign(align []text.Align) {
t.align = align
}
// SetAlignFooter sets the horizontal-align for each column in the footer.
//
// Deprecated: Use SetColumnConfigs instead.
func (t *Table) SetAlignFooter(align []text.Align) {
t.alignFooter = align
}
// SetAlignHeader sets the horizontal-align for each column in the header.
//
// Deprecated: Use SetColumnConfigs instead.
func (t *Table) SetAlignHeader(align []text.Align) {
t.alignHeader = align
}
// SetAllowedColumnLengths sets the maximum allowed length for each column in
// all the rows. Columns with content longer than the allowed limit will be
// wrapped to fit the length. Length has to be a positive value to take effect.
//
// Deprecated: Use SetColumnConfigs instead.
func (t *Table) SetAllowedColumnLengths(lengths []int) {
t.allowedColumnLengths = lengths
}
// SetAllowedRowLength sets the maximum allowed length or a row (or line of
// output) when rendered as a table. Rows that are longer than this limit will
// be "snipped" to the length. Length has to be a positive value to take effect.
func (t *Table) SetAllowedRowLength(length int) {
t.allowedRowLength = length
}
// SetAutoIndex adds a generated header with columns such as "A", "B", "C", etc.
// and a leading column with the row number similar to what you'd see on any
// spreadsheet application. NOTE: Appending a Header will void this
// functionality.
func (t *Table) SetAutoIndex(autoIndex bool) {
t.autoIndex = autoIndex
}
// SetCaption sets the text to be rendered just below the table. This will not
// show up when the Table is rendered as a CSV.
func (t *Table) SetCaption(format string, a ...interface{}) {
t.caption = fmt.Sprintf(format, a...)
}
// SetColors sets the colors for the rows in the Body.
//
// Deprecated: Use SetColumnConfigs instead.
func (t *Table) SetColors(colors []text.Colors) {
t.colors = colors
}
// SetColorsFooter sets the colors for the rows in the Footer.
//
// Deprecated: Use SetColumnConfigs instead.
func (t *Table) SetColorsFooter(colors []text.Colors) {
t.colorsFooter = colors
}
// SetColorsHeader sets the colors for the rows in the Header.
//
// Deprecated: Use SetColumnConfigs instead.
func (t *Table) SetColorsHeader(colors []text.Colors) {
t.colorsHeader = colors
}
// SetColumnConfigs sets the configs for each Column.
func (t *Table) SetColumnConfigs(configs []ColumnConfig) {
t.columnConfigs = configs
}
// SetHTMLCSSClass sets the the HTML CSS Class to use on the <table> node
// when rendering the Table in HTML format.
func (t *Table) SetHTMLCSSClass(cssClass string) {
t.htmlCSSClass = cssClass
}
// SetIndexColumn sets the given Column # as the column that has the row
// "Number". Valid values range from 1 to N. Note that this is not 0-indexed.
func (t *Table) SetIndexColumn(colNum int) {
t.indexColumn = colNum
}
// SetOutputMirror sets an io.Writer for all the Render functions to "Write" to
// in addition to returning a string.
func (t *Table) SetOutputMirror(mirror io.Writer) {
t.outputMirror = mirror
}
// SetPageSize sets the maximum number of lines to render before rendering the
// header rows again. This can be useful when dealing with tables containing a
// long list of rows that can span pages. Please note that the pagination logic
// will not consider Header/Footer lines for paging.
func (t *Table) SetPageSize(numLines int) {
t.pageSize = numLines
}
// SetRowPainter sets the RowPainter function which determines the colors to use
// on a row. Before rendering, this function is invoked on all rows and the
// color of each row is determined. This color takes precedence over other ways
// to set color (ColumnConfig.Color*, SetColor*()).
func (t *Table) SetRowPainter(painter RowPainter) {
t.rowPainter = painter
}
// SetStyle overrides the DefaultStyle with the provided one.
func (t *Table) SetStyle(style Style) {
t.style = &style
}
// SetTitle sets the title text to be rendered above the table.
func (t *Table) SetTitle(format string, a ...interface{}) {
t.title = fmt.Sprintf(format, a...)
}
// SetVAlign sets the vertical-align for each column in all the rows.
//
// Deprecated: Use SetColumnConfigs instead.
func (t *Table) SetVAlign(vAlign []text.VAlign) {
t.vAlign = vAlign
}
// SetVAlignFooter sets the horizontal-align for each column in the footer.
//
// Deprecated: Use SetColumnConfigs instead.
func (t *Table) SetVAlignFooter(vAlign []text.VAlign) {
t.vAlignFooter = vAlign
}
// SetVAlignHeader sets the horizontal-align for each column in the header.
//
// Deprecated: Use SetColumnConfigs instead.
func (t *Table) SetVAlignHeader(vAlign []text.VAlign) {
t.vAlignHeader = vAlign
}
// SortBy sets the rules for sorting the Rows in the order specified. i.e., the
// first SortBy instruction takes precedence over the second and so on. Any
// duplicate instructions on the same column will be discarded while sorting.
func (t *Table) SortBy(sortBy []SortBy) {
t.sortBy = sortBy
}
// Style returns the current style.
func (t *Table) Style() *Style {
if t.style == nil {
tempStyle := StyleDefault
t.style = &tempStyle
}
return t.style
}
func (t *Table) analyzeAndStringify(row Row, hint renderHint) rowStr {
// update t.numColumns if this row is the longest seen till now
if len(row) > t.numColumns {
// init the slice for the first time; and pad it the rest of the time
if t.numColumns == 0 {
t.columnIsNonNumeric = make([]bool, len(row))
} else {
t.columnIsNonNumeric = append(t.columnIsNonNumeric, make([]bool, len(row)-t.numColumns)...)
}
// update t.numColumns
t.numColumns = len(row)
}
// convert each column to string and figure out if it has non-numeric data
rowOut := make(rowStr, len(row))
for colIdx, col := range row {
// if the column is not a number, keep track of it
if !hint.isHeaderRow && !hint.isFooterRow && !t.columnIsNonNumeric[colIdx] && !isNumber(col) {
t.columnIsNonNumeric[colIdx] = true
}
// convert to a string and store it in the row
var colStr string
if transformer := t.getColumnTransformer(colIdx, hint); transformer != nil {
colStr = transformer(col)
} else if colStrVal, ok := col.(string); ok {
colStr = colStrVal
} else {
colStr = fmt.Sprint(col)
}
if strings.Contains(colStr, "\t") {
colStr = strings.Replace(colStr, "\t", " ", -1)
}
if strings.Contains(colStr, "\r") {
colStr = strings.Replace(colStr, "\r", "", -1)
}
rowOut[colIdx] = colStr
}
return rowOut
}
func (t *Table) getAlign(colIdx int, hint renderHint) text.Align {
align := text.AlignDefault
if cfg, ok := t.columnConfigMap[colIdx]; ok {
if hint.isHeaderRow {
align = cfg.AlignHeader
} else if hint.isFooterRow {
align = cfg.AlignFooter
} else {
align = cfg.Align
}
}
if align == text.AlignDefault {
align = t.getAlignOld(colIdx, hint)
}
if align == text.AlignDefault && !t.columnIsNonNumeric[colIdx] {
align = text.AlignRight
}
return align
}
func (t *Table) getAlignOld(colIdx int, hint renderHint) text.Align {
align := text.AlignDefault
if hint.isHeaderRow {
if colIdx < len(t.alignHeader) {
align = t.alignHeader[colIdx]
}
} else if hint.isFooterRow {
if colIdx < len(t.alignFooter) {
align = t.alignFooter[colIdx]
}
} else if colIdx < len(t.align) {
align = t.align[colIdx]
}
return align
}
func (t *Table) getAutoIndexColumnIDs() rowStr {
row := make(rowStr, t.numColumns)
for colIdx, maxColumnLength := range t.maxColumnLengths {
row[colIdx] = text.AlignCenter.Apply(AutoIndexColumnID(colIdx), maxColumnLength)
}
return row
}
func (t *Table) getBorderColors(hint renderHint) text.Colors {
if hint.isFooterRow {
return t.style.Color.Footer
} else if t.autoIndex {
return t.style.Color.IndexColumn
}
return t.style.Color.Header
}
func (t *Table) getColumnColors(colIdx int, hint renderHint) text.Colors {
if t.rowPainter != nil && hint.isRegularRow() && !t.isIndexColumn(colIdx, hint) {
colors := t.rowsColors[hint.rowNumber-1]
if colors != nil {
return colors
}
}
if cfg, ok := t.columnConfigMap[colIdx]; ok {
if hint.isSeparatorRow {
return nil
} else if hint.isHeaderRow {
return cfg.ColorsHeader
} else if hint.isFooterRow {
return cfg.ColorsFooter
}
return cfg.Colors
}
colors := t.getRowColors(hint)
if colIdx < len(colors) {
return colors[colIdx]
}
return nil
}
func (t *Table) getColumnTransformer(colIdx int, hint renderHint) text.Transformer {
var transformer text.Transformer
if cfg, ok := t.columnConfigMap[colIdx]; ok {
if hint.isHeaderRow {
transformer = cfg.TransformerHeader
} else if hint.isFooterRow {
transformer = cfg.TransformerFooter
} else {
transformer = cfg.Transformer
}
}
return transformer
}
func (t *Table) getColumnWidthMax(colIdx int) int {
if cfg, ok := t.columnConfigMap[colIdx]; ok {
return cfg.WidthMax
}
if colIdx < len(t.allowedColumnLengths) {
return t.allowedColumnLengths[colIdx]
}
return 0
}
func (t *Table) getColumnWidthMin(colIdx int) int {
if cfg, ok := t.columnConfigMap[colIdx]; ok {
return cfg.WidthMin
}
return 0
}
func (t *Table) getFormat(hint renderHint) text.Format {
if hint.isSeparatorRow {
return text.FormatDefault
} else if hint.isHeaderRow {
return t.style.Format.Header
} else if hint.isFooterRow {
return t.style.Format.Footer
}
return t.style.Format.Row
}
func (t *Table) getRowColors(hint renderHint) []text.Colors {
if hint.isSeparatorRow {
return nil
} else if hint.isHeaderRow {
return t.colorsHeader
} else if hint.isFooterRow {
return t.colorsFooter
}
return t.colors
}
func (t *Table) getSeparatorColors(hint renderHint) text.Colors {
if hint.isHeaderRow {
return t.style.Color.Header
} else if hint.isFooterRow {
return t.style.Color.Footer
} else if hint.isAutoIndexColumn {
return t.style.Color.IndexColumn
} else if hint.rowNumber > 0 && hint.rowNumber%2 == 0 {
return t.style.Color.RowAlternate
}
return t.style.Color.Row
}
func (t *Table) getVAlign(colIdx int, hint renderHint) text.VAlign {
vAlign := text.VAlignDefault
if cfg, ok := t.columnConfigMap[colIdx]; ok {
if hint.isHeaderRow {
vAlign = cfg.VAlignHeader
} else if hint.isFooterRow {
vAlign = cfg.VAlignFooter
} else {
vAlign = cfg.VAlign
}
}
if vAlign == text.VAlignDefault {
vAlign = t.getVAlignOld(colIdx, hint)
}
return vAlign
}
func (t *Table) getVAlignOld(colIdx int, hint renderHint) text.VAlign {
vAlign := text.VAlignDefault
if hint.isHeaderRow {
if colIdx < len(t.vAlignHeader) {
vAlign = t.vAlignHeader[colIdx]
}
} else if hint.isFooterRow {
if colIdx < len(t.vAlignFooter) {
vAlign = t.vAlignFooter[colIdx]
}
} else if colIdx < len(t.vAlign) {
vAlign = t.vAlign[colIdx]
}
return vAlign
}
func (t *Table) initForRender() {
// pick a default style
t.Style()
// auto-index: calc the index column's max length
t.autoIndexVIndexMaxLength = len(fmt.Sprint(len(t.rowsRaw)))
// initialize the column configs and normalize them
t.initForRenderColumnConfigs()
// initialize and stringify all the raw rows
t.initForRenderRows()
// find the longest continuous line in each column
t.initForRenderColumnLengths()
// generate a separator row and calculate maximum row length
t.initForRenderRowSeparator()
// reset the counter for the number of lines rendered
t.numLinesRendered = 0
}
func (t *Table) initForRenderColumnConfigs() {
findColumnNumber := func(row Row, colName string) int {
for colIdx, col := range row {
if fmt.Sprint(col) == colName {
return colIdx + 1
}
}
return 0
}
t.columnConfigMap = map[int]ColumnConfig{}
for _, colCfg := range t.columnConfigs {
// find the column number if none provided; this logic can work only if
// a header row is present and has a column with the given name
if colCfg.Number == 0 {
for _, row := range t.rowsHeaderRaw {
colCfg.Number = findColumnNumber(row, colCfg.Name)
if colCfg.Number > 0 {
break
}
}
}
if colCfg.Number > 0 {
t.columnConfigMap[colCfg.Number-1] = colCfg
}
}
fmt.Printf("")
}
func (t *Table) initForRenderColumnLengths() {
var findMaxColumnLengths = func(rows []rowStr) {
for _, row := range rows {
for colIdx, colStr := range row {
longestLineLen := text.LongestLineLen(colStr)
if longestLineLen > t.maxColumnLengths[colIdx] {
t.maxColumnLengths[colIdx] = longestLineLen
}
}
}
}
t.maxColumnLengths = make([]int, t.numColumns)
findMaxColumnLengths(t.rowsHeader)
findMaxColumnLengths(t.rows)
findMaxColumnLengths(t.rowsFooter)
// restrict the column lengths if any are over or under the limits
for colIdx := range t.maxColumnLengths {
maxWidth := t.getColumnWidthMax(colIdx)
if maxWidth > 0 && t.maxColumnLengths[colIdx] > maxWidth {
t.maxColumnLengths[colIdx] = maxWidth
}
minWidth := t.getColumnWidthMin(colIdx)
if minWidth > 0 && t.maxColumnLengths[colIdx] < minWidth {
t.maxColumnLengths[colIdx] = minWidth
}
}
}
func (t *Table) initForRenderRows() {
t.rowsColors = nil
if t.rowPainter != nil {
t.rowsColors = make([]text.Colors, len(t.rowsRaw))
}
t.rows = t.initForRenderRowsStringify(t.rowsRaw, renderHint{})
t.rowsFooter = t.initForRenderRowsStringify(t.rowsFooterRaw, renderHint{isFooterRow: true})
t.rowsHeader = t.initForRenderRowsStringify(t.rowsHeaderRaw, renderHint{isHeaderRow: true})
// sort rows and rowsColors
if len(t.sortBy) > 0 {
sortedRowIndices := t.getSortedRowIndices()
sortedRows := make([]rowStr, len(t.rows))
for idx := range t.rows {
sortedRows[idx] = t.rows[sortedRowIndices[idx]]
}
t.rows = sortedRows
if t.rowsColors != nil {
sortedRowsColors := make([]text.Colors, len(t.rows))
for idx := range t.rows {
sortedRowsColors[idx] = t.rowsColors[sortedRowIndices[idx]]
}
t.rowsColors = sortedRowsColors
}
}
}
func (t *Table) initForRenderRowsStringify(rows []Row, hint renderHint) []rowStr {
rowsStr := make([]rowStr, len(rows))
for idx, row := range rows {
if t.rowPainter != nil && hint.isRegularRow() {
t.rowsColors[idx] = t.rowPainter(row)
}
rowsStr[idx] = t.analyzeAndStringify(row, hint)
}
return rowsStr
}
func (t *Table) initForRenderRowSeparator() {
t.maxRowLength = 0
if t.autoIndex {
t.maxRowLength += text.RuneCount(t.style.Box.PaddingLeft)
t.maxRowLength += len(fmt.Sprint(len(t.rows)))
t.maxRowLength += text.RuneCount(t.style.Box.PaddingRight)
if t.style.Options.SeparateColumns {
t.maxRowLength += text.RuneCount(t.style.Box.MiddleSeparator)
}
}
if t.style.Options.SeparateColumns {
t.maxRowLength += text.RuneCount(t.style.Box.MiddleSeparator) * (t.numColumns - 1)
}
t.rowSeparator = make(rowStr, t.numColumns)
for colIdx, maxColumnLength := range t.maxColumnLengths {
maxColumnLength += text.RuneCount(t.style.Box.PaddingLeft + t.style.Box.PaddingRight)
t.maxRowLength += maxColumnLength
t.rowSeparator[colIdx] = text.RepeatAndTrim(t.style.Box.MiddleHorizontal, maxColumnLength)
}
if t.style.Options.DrawBorder {
t.maxRowLength += text.RuneCount(t.style.Box.Left + t.style.Box.Right)
}
}
func (t *Table) isIndexColumn(colIdx int, hint renderHint) bool {
return t.indexColumn == colIdx+1 || hint.isAutoIndexColumn
}
func (t *Table) render(out *strings.Builder) string {
outStr := out.String()
if t.outputMirror != nil && len(outStr) > 0 {
_, _ = t.outputMirror.Write([]byte(outStr))
_, _ = t.outputMirror.Write([]byte("\n"))
}
return outStr
}
// renderHint has hints for the Render*() logic
type renderHint struct {
isAutoIndexColumn bool // auto-index column?
isBorderBottom bool // bottom-border?
isBorderTop bool // top-border?
isFirstRow bool // first-row of header/footer/regular-rows?
isFooterRow bool // footer row?
isHeaderRow bool // header row?
isLastLineOfRow bool // last-line of the current row?
isLastRow bool // last-row of header/footer/regular-rows?
isSeparatorRow bool // separator row?
rowLineNumber int // the line number for a multi-line row
rowNumber int // the row number/index
}
func (h *renderHint) isRegularRow() bool {
return !h.isHeaderRow && !h.isFooterRow
}
func (h *renderHint) isLastLineOfLastRow() bool {
return h.isLastLineOfRow && h.isLastRow
}

27
vendor/github.com/jedib0t/go-pretty/table/util.go generated vendored Normal file
View File

@@ -0,0 +1,27 @@
package table
import "reflect"
// AutoIndexColumnID returns a unique Column ID/Name for the given Column Number.
// The functionality is similar to what you get in an Excel spreadsheet w.r.t.
// the Column ID/Name.
func AutoIndexColumnID(colIdx int) string {
charIdx := colIdx % 26
out := string(65 + charIdx)
colIdx = colIdx / 26
if colIdx > 0 {
return AutoIndexColumnID(colIdx-1) + out
}
return out
}
// isNumber returns true if the argument is a numeric type; false otherwise.
func isNumber(x interface{}) bool {
switch reflect.TypeOf(x).Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
reflect.Float32, reflect.Float64:
return true
}
return false
}

59
vendor/github.com/jedib0t/go-pretty/table/writer.go generated vendored Normal file
View File

@@ -0,0 +1,59 @@
package table
import (
"io"
"github.com/jedib0t/go-pretty/text"
)
// Writer declares the interfaces that can be used to setup and render a table.
type Writer interface {
AppendFooter(row Row)
AppendHeader(row Row)
AppendRow(row Row)
AppendRows(rows []Row)
Length() int
Render() string
RenderCSV() string
RenderHTML() string
RenderMarkdown() string
SetAllowedRowLength(length int)
SetAutoIndex(autoIndex bool)
SetCaption(format string, a ...interface{})
SetColumnConfigs(configs []ColumnConfig)
SetHTMLCSSClass(cssClass string)
SetIndexColumn(colNum int)
SetOutputMirror(mirror io.Writer)
SetPageSize(numLines int)
SetRowPainter(painter RowPainter)
SetStyle(style Style)
SetTitle(format string, a ...interface{})
SortBy(sortBy []SortBy)
Style() *Style
// deprecated; use SetColumnConfigs instead
SetAlign(align []text.Align)
// deprecated; use SetColumnConfigs instead
SetAlignFooter(align []text.Align)
// deprecated; use SetColumnConfigs instead
SetAlignHeader(align []text.Align)
// deprecated; use SetColumnConfigs instead
SetAllowedColumnLengths(lengths []int)
// deprecated; use SetColumnConfigs instead
SetColors(colors []text.Colors)
// deprecated; use SetColumnConfigs instead
SetColorsFooter(colors []text.Colors)
// deprecated; use SetColumnConfigs instead
SetColorsHeader(colors []text.Colors)
// deprecated; use SetColumnConfigs instead
SetVAlign(vAlign []text.VAlign)
// deprecated; use SetColumnConfigs instead
SetVAlignFooter(vAlign []text.VAlign)
// deprecated; use SetColumnConfigs instead
SetVAlignHeader(vAlign []text.VAlign)
}
// NewWriter initializes and returns a Writer.
func NewWriter() Writer {
return &Table{}
}

137
vendor/github.com/jedib0t/go-pretty/text/align.go generated vendored Normal file
View File

@@ -0,0 +1,137 @@
package text
import (
"fmt"
"strconv"
"strings"
"unicode/utf8"
)
// Align denotes how text is to be aligned horizontally.
type Align int
// Align enumerations
const (
AlignDefault Align = iota // same as AlignLeft
AlignLeft // "left "
AlignCenter // " center "
AlignJustify // "justify it"
AlignRight // " right"
)
// Apply aligns the text as directed. For ex.:
// * AlignDefault.Apply("Jon Snow", 12) returns "Jon Snow "
// * AlignLeft.Apply("Jon Snow", 12) returns "Jon Snow "
// * AlignCenter.Apply("Jon Snow", 12) returns " Jon Snow "
// * AlignJustify.Apply("Jon Snow", 12) returns "Jon Snow"
// * AlignRight.Apply("Jon Snow", 12) returns " Jon Snow"
func (a Align) Apply(text string, maxLength int) string {
text = a.trimString(text)
sLen := utf8.RuneCountInString(text)
sLenWoE := RuneCount(text)
numEscChars := sLen - sLenWoE
// now, align the text
switch a {
case AlignDefault, AlignLeft:
return fmt.Sprintf("%-"+strconv.Itoa(maxLength+numEscChars)+"s", text)
case AlignCenter:
if sLenWoE < maxLength {
// left pad with half the number of spaces needed before using %text
return fmt.Sprintf("%"+strconv.Itoa(maxLength+numEscChars)+"s",
text+strings.Repeat(" ", int((maxLength-sLenWoE)/2)))
}
case AlignJustify:
return a.justifyText(text, sLenWoE, maxLength)
}
return fmt.Sprintf("%"+strconv.Itoa(maxLength+numEscChars)+"s", text)
}
// HTMLProperty returns the equivalent HTML horizontal-align tag property.
func (a Align) HTMLProperty() string {
switch a {
case AlignLeft:
return "align=\"left\""
case AlignCenter:
return "align=\"center\""
case AlignJustify:
return "align=\"justify\""
case AlignRight:
return "align=\"right\""
default:
return ""
}
}
// MarkdownProperty returns the equivalent Markdown horizontal-align separator.
func (a Align) MarkdownProperty() string {
switch a {
case AlignLeft:
return ":--- "
case AlignCenter:
return ":---:"
case AlignRight:
return " ---:"
default:
return " --- "
}
}
func (a Align) justifyText(text string, textLength int, maxLength int) string {
// split the text into individual words
wordsUnfiltered := strings.Split(text, " ")
words := Filter(wordsUnfiltered, func(item string) bool {
return item != ""
})
// empty string implies spaces for maxLength
if len(words) == 0 {
return strings.Repeat(" ", maxLength)
}
// get the number of spaces to insert into the text
numSpacesNeeded := maxLength - textLength + strings.Count(text, " ")
numSpacesNeededBetweenWords := 0
if len(words) > 1 {
numSpacesNeededBetweenWords = numSpacesNeeded / (len(words) - 1)
}
// create the output string word by word with spaces in between
var outText strings.Builder
outText.Grow(maxLength)
for idx, word := range words {
if idx > 0 {
// insert spaces only after the first word
if idx == len(words)-1 {
// insert all the remaining space before the last word
outText.WriteString(strings.Repeat(" ", numSpacesNeeded))
numSpacesNeeded = 0
} else {
// insert the determined number of spaces between each word
outText.WriteString(strings.Repeat(" ", numSpacesNeededBetweenWords))
// and reduce the number of spaces needed after this
numSpacesNeeded -= numSpacesNeededBetweenWords
}
}
outText.WriteString(word)
if idx == len(words)-1 && numSpacesNeeded > 0 {
outText.WriteString(strings.Repeat(" ", numSpacesNeeded))
}
}
return outText.String()
}
func (a Align) trimString(text string) string {
switch a {
case AlignDefault, AlignLeft:
if strings.HasSuffix(text, " ") {
return strings.TrimRight(text, " ")
}
case AlignRight:
if strings.HasPrefix(text, " ") {
return strings.TrimLeft(text, " ")
}
default:
if strings.HasPrefix(text, " ") || strings.HasSuffix(text, " ") {
return strings.Trim(text, " ")
}
}
return text
}

55
vendor/github.com/jedib0t/go-pretty/text/ansi.go generated vendored Normal file
View File

@@ -0,0 +1,55 @@
package text
import "strings"
// ANSICodesSupported will be true on consoles where ANSI Escape Codes/Sequences
// are supported.
var ANSICodesSupported = areANSICodesSupported()
// Escape encodes the string with the ANSI Escape Sequence.
// For ex.:
// Escape("Ghost", "") == "Ghost"
// Escape("Ghost", "\x1b[91m") == "\x1b[91mGhost\x1b[0m"
// Escape("\x1b[94mGhost\x1b[0mLady", "\x1b[91m") == "\x1b[94mGhost\x1b[0m\x1b[91mLady\x1b[0m"
// Escape("Nymeria\x1b[94mGhost\x1b[0mLady", "\x1b[91m") == "\x1b[91mNymeria\x1b[94mGhost\x1b[0m\x1b[91mLady\x1b[0m"
// Escape("Nymeria \x1b[94mGhost\x1b[0m Lady", "\x1b[91m") == "\x1b[91mNymeria \x1b[94mGhost\x1b[0m\x1b[91m Lady\x1b[0m"
func Escape(str string, escapeSeq string) string {
out := ""
if !strings.HasPrefix(str, EscapeStart) {
out += escapeSeq
}
out += strings.Replace(str, EscapeReset, EscapeReset+escapeSeq, -1)
if !strings.HasSuffix(out, EscapeReset) {
out += EscapeReset
}
if strings.Contains(out, escapeSeq+EscapeReset) {
out = strings.Replace(out, escapeSeq+EscapeReset, "", -1)
}
return out
}
// StripEscape strips all ANSI Escape Sequence from the string.
// For ex.:
// StripEscape("Ghost") == "Ghost"
// StripEscape("\x1b[91mGhost\x1b[0m") == "Ghost"
// StripEscape("\x1b[94mGhost\x1b[0m\x1b[91mLady\x1b[0m") == "GhostLady"
// StripEscape("\x1b[91mNymeria\x1b[94mGhost\x1b[0m\x1b[91mLady\x1b[0m") == "NymeriaGhostLady"
// StripEscape("\x1b[91mNymeria \x1b[94mGhost\x1b[0m\x1b[91m Lady\x1b[0m") == "Nymeria Ghost Lady"
func StripEscape(str string) string {
var out strings.Builder
out.Grow(RuneCount(str))
isEscSeq := false
for _, sChr := range str {
if sChr == EscapeStartRune {
isEscSeq = true
}
if !isEscSeq {
out.WriteRune(sChr)
}
if isEscSeq && sChr == EscapeStopRune {
isEscSeq = false
}
}
return out.String()
}

View File

@@ -0,0 +1,7 @@
// +build !windows
package text
func areANSICodesSupported() bool {
return true
}

View File

@@ -0,0 +1,31 @@
// +build windows
package text
import (
"os"
"sync"
"golang.org/x/sys/windows"
)
var (
enableVTPMutex = sync.Mutex{}
)
func areANSICodesSupported() bool {
enableVTPMutex.Lock()
defer enableVTPMutex.Unlock()
outHandle := windows.Handle(os.Stdout.Fd())
var outMode uint32
if err := windows.GetConsoleMode(outHandle, &outMode); err == nil {
if outMode&windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING != 0 {
return true
}
if err := windows.SetConsoleMode(outHandle, outMode|windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING); err == nil {
return true
}
}
return false
}

183
vendor/github.com/jedib0t/go-pretty/text/color.go generated vendored Normal file
View File

@@ -0,0 +1,183 @@
package text
import (
"fmt"
"sort"
"strconv"
"strings"
"sync"
)
var (
colorsEnabled = areANSICodesSupported()
)
// DisableColors (forcefully) disables color coding globally.
func DisableColors() {
colorsEnabled = false
}
// EnableColors (forcefully) enables color coding globally.
func EnableColors() {
colorsEnabled = true
}
// The logic here is inspired from github.com/fatih/color; the following is
// the the bare minimum logic required to print Colored to the console.
// The differences:
// * This one caches the escape sequences for cases with multiple colors
// * This one handles cases where the incoming already has colors in the
// form of escape sequences; in which case, text that does not have any
// escape sequences are colored/escaped
// Color represents a single color to render with.
type Color int
// Base colors -- attributes in reality
const (
Reset Color = iota
Bold
Faint
Italic
Underline
BlinkSlow
BlinkRapid
ReverseVideo
Concealed
CrossedOut
)
// Foreground colors
const (
FgBlack Color = iota + 30
FgRed
FgGreen
FgYellow
FgBlue
FgMagenta
FgCyan
FgWhite
)
// Foreground Hi-Intensity colors
const (
FgHiBlack Color = iota + 90
FgHiRed
FgHiGreen
FgHiYellow
FgHiBlue
FgHiMagenta
FgHiCyan
FgHiWhite
)
// Background colors
const (
BgBlack Color = iota + 40
BgRed
BgGreen
BgYellow
BgBlue
BgMagenta
BgCyan
BgWhite
)
// Background Hi-Intensity colors
const (
BgHiBlack Color = iota + 100
BgHiRed
BgHiGreen
BgHiYellow
BgHiBlue
BgHiMagenta
BgHiCyan
BgHiWhite
)
// EscapeSeq returns the ANSI escape sequence for the color.
func (c Color) EscapeSeq() string {
return EscapeStart + strconv.Itoa(int(c)) + EscapeStop
}
// HTMLProperty returns the "class" attribute for the color.
func (c Color) HTMLProperty() string {
out := ""
if class, ok := colorCSSClassMap[c]; ok {
out = fmt.Sprintf("class=\"%s\"", class)
}
return out
}
// Sprint colorizes and prints the given string(s).
func (c Color) Sprint(a ...interface{}) string {
return colorize(fmt.Sprint(a...), c.EscapeSeq())
}
// Sprintf formats and colorizes and prints the given string(s).
func (c Color) Sprintf(format string, a ...interface{}) string {
return colorize(fmt.Sprintf(format, a...), c.EscapeSeq())
}
// Colors represents an array of Color objects to render with.
// Example: Colors{FgCyan, BgBlack}
type Colors []Color
var (
// colorsSeqMap caches the escape sequence for a set of colors
colorsSeqMap = sync.Map{}
)
// EscapeSeq returns the ANSI escape sequence for the colors set.
func (c Colors) EscapeSeq() string {
if len(c) == 0 {
return ""
}
colorsKey := fmt.Sprintf("%#v", c)
escapeSeq, ok := colorsSeqMap.Load(colorsKey)
if !ok || escapeSeq == "" {
colorNums := make([]string, len(c))
for idx, color := range c {
colorNums[idx] = strconv.Itoa(int(color))
}
escapeSeq = EscapeStart + strings.Join(colorNums, ";") + EscapeStop
colorsSeqMap.Store(colorsKey, escapeSeq)
}
return escapeSeq.(string)
}
// HTMLProperty returns the "class" attribute for the colors.
func (c Colors) HTMLProperty() string {
if len(c) == 0 {
return ""
}
var classes []string
for _, color := range c {
if class, ok := colorCSSClassMap[color]; ok {
classes = append(classes, class)
}
}
if len(classes) > 1 {
sort.Strings(classes)
}
return fmt.Sprintf("class=\"%s\"", strings.Join(classes, " "))
}
// Sprint colorizes and prints the given string(s).
func (c Colors) Sprint(a ...interface{}) string {
return colorize(fmt.Sprint(a...), c.EscapeSeq())
}
// Sprintf formats and colorizes and prints the given string(s).
func (c Colors) Sprintf(format string, a ...interface{}) string {
return colorize(fmt.Sprintf(format, a...), c.EscapeSeq())
}
func colorize(s string, escapeSeq string) string {
if !colorsEnabled || escapeSeq == "" {
return s
}
return Escape(s, escapeSeq)
}

48
vendor/github.com/jedib0t/go-pretty/text/color_html.go generated vendored Normal file
View File

@@ -0,0 +1,48 @@
package text
var (
// colorCSSClassMap contains the equivalent CSS-class for all colors
colorCSSClassMap = map[Color]string{
Bold: "bold",
Faint: "faint",
Italic: "italic",
Underline: "underline",
BlinkSlow: "blink-slow",
BlinkRapid: "blink-rapid",
ReverseVideo: "reverse-video",
Concealed: "concealed",
CrossedOut: "crossed-out",
FgBlack: "fg-black",
FgRed: "fg-red",
FgGreen: "fg-green",
FgYellow: "fg-yellow",
FgBlue: "fg-blue",
FgMagenta: "fg-magenta",
FgCyan: "fg-cyan",
FgWhite: "fg-white",
FgHiBlack: "fg-hi-black",
FgHiRed: "fg-hi-red",
FgHiGreen: "fg-hi-green",
FgHiYellow: "fg-hi-yellow",
FgHiBlue: "fg-hi-blue",
FgHiMagenta: "fg-hi-magenta",
FgHiCyan: "fg-hi-cyan",
FgHiWhite: "fg-hi-white",
BgBlack: "bg-black",
BgRed: "bg-red",
BgGreen: "bg-green",
BgYellow: "bg-yellow",
BgBlue: "bg-blue",
BgMagenta: "bg-magenta",
BgCyan: "bg-cyan",
BgWhite: "bg-white",
BgHiBlack: "bg-hi-black",
BgHiRed: "bg-hi-red",
BgHiGreen: "bg-hi-green",
BgHiYellow: "bg-hi-yellow",
BgHiBlue: "bg-hi-blue",
BgHiMagenta: "bg-hi-magenta",
BgHiCyan: "bg-hi-cyan",
BgHiWhite: "bg-hi-white",
}
)

39
vendor/github.com/jedib0t/go-pretty/text/cursor.go generated vendored Normal file
View File

@@ -0,0 +1,39 @@
package text
import (
"fmt"
)
// Cursor helps move the cursor on the console in multiple directions.
type Cursor rune
const (
// CursorDown helps move the Cursor Down X lines
CursorDown Cursor = 'B'
// CursorLeft helps move the Cursor Left X characters
CursorLeft Cursor = 'D'
// CursorRight helps move the Cursor Right X characters
CursorRight Cursor = 'C'
// CursorUp helps move the Cursor Up X lines
CursorUp Cursor = 'A'
// EraseLine helps erase all characters to the Right of the Cursor in the
// current line
EraseLine Cursor = 'K'
)
// Sprint prints the Escape Sequence to move the Cursor once.
func (c Cursor) Sprint() string {
return fmt.Sprintf("%s%c", EscapeStart, c)
}
// Sprintn prints the Escape Sequence to move the Cursor "n" times.
func (c Cursor) Sprintn(n int) string {
if c == EraseLine {
return c.Sprint()
}
return fmt.Sprintf("%s%d%c", EscapeStart, n, c)
}

12
vendor/github.com/jedib0t/go-pretty/text/filter.go generated vendored Normal file
View File

@@ -0,0 +1,12 @@
package text
// Filter filters the slice 's' to items which return truth when passed to 'f'.
func Filter(s []string, f func(string) bool) []string {
var out []string
for _, item := range s {
if f(item) {
out = append(out, item)
}
}
return out
}

28
vendor/github.com/jedib0t/go-pretty/text/format.go generated vendored Normal file
View File

@@ -0,0 +1,28 @@
package text
import "strings"
// Format denotes the "case" to use for text.
type Format int
// Format enumerations
const (
FormatDefault Format = iota // default_Case
FormatLower // lower
FormatTitle // Title
FormatUpper // UPPER
)
// Apply converts the text as directed.
func (tc Format) Apply(text string) string {
switch tc {
case FormatLower:
return strings.ToLower(text)
case FormatTitle:
return strings.Title(text)
case FormatUpper:
return strings.ToUpper(text)
default:
return text
}
}

203
vendor/github.com/jedib0t/go-pretty/text/string.go generated vendored Normal file
View File

@@ -0,0 +1,203 @@
package text
import (
"strings"
"unicode/utf8"
"github.com/mattn/go-runewidth"
)
// Constants
const (
EscapeReset = EscapeStart + "0" + EscapeStop
EscapeStart = "\x1b["
EscapeStartRune = rune(27) // \x1b
EscapeStop = "m"
EscapeStopRune = 'm'
)
// InsertEveryN inserts the rune every N characters in the string. For ex.:
// InsertEveryN("Ghost", '-', 1) == "G-h-o-s-t"
// InsertEveryN("Ghost", '-', 2) == "Gh-os-t"
// InsertEveryN("Ghost", '-', 3) == "Gho-st"
// InsertEveryN("Ghost", '-', 4) == "Ghos-t"
// InsertEveryN("Ghost", '-', 5) == "Ghost"
func InsertEveryN(str string, runeToInsert rune, n int) string {
if n <= 0 {
return str
}
sLen := RuneCount(str)
var out strings.Builder
out.Grow(sLen + (sLen / n))
outLen, isEscSeq := 0, false
for idx, c := range str {
if c == EscapeStartRune {
isEscSeq = true
}
if !isEscSeq && outLen > 0 && (outLen%n) == 0 && idx != sLen {
out.WriteRune(runeToInsert)
}
out.WriteRune(c)
if !isEscSeq {
outLen += RuneWidth(c)
}
if isEscSeq && c == EscapeStopRune {
isEscSeq = false
}
}
return out.String()
}
// LongestLineLen returns the length of the longest "line" within the
// argument string. For ex.:
// LongestLineLen("Ghost!\nCome back here!\nRight now!") == 15
func LongestLineLen(str string) int {
maxLength, currLength, isEscSeq := 0, 0, false
for _, c := range str {
if c == EscapeStartRune {
isEscSeq = true
} else if isEscSeq && c == EscapeStopRune {
isEscSeq = false
continue
}
if c == '\n' {
if currLength > maxLength {
maxLength = currLength
}
currLength = 0
} else if !isEscSeq {
currLength += RuneWidth(c)
}
}
if currLength > maxLength {
maxLength = currLength
}
return maxLength
}
// Pad pads the given string with as many characters as needed to make it as
// long as specified (maxLen). This function does not count escape sequences
// while calculating length of the string. Ex.:
// Pad("Ghost", 0, ' ') == "Ghost"
// Pad("Ghost", 3, ' ') == "Ghost"
// Pad("Ghost", 5, ' ') == "Ghost"
// Pad("Ghost", 7, ' ') == "Ghost "
// Pad("Ghost", 10, '.') == "Ghost....."
func Pad(str string, maxLen int, paddingChar rune) string {
strLen := RuneCount(str)
if strLen < maxLen {
str += strings.Repeat(string(paddingChar), maxLen-strLen)
}
return str
}
// RepeatAndTrim repeats the given string until it is as long as maxRunes.
// For ex.:
// RepeatAndTrim("Ghost", 0) == ""
// RepeatAndTrim("Ghost", 5) == "Ghost"
// RepeatAndTrim("Ghost", 7) == "GhostGh"
// RepeatAndTrim("Ghost", 10) == "GhostGhost"
func RepeatAndTrim(str string, maxRunes int) string {
if maxRunes == 0 {
return ""
} else if maxRunes == utf8.RuneCountInString(str) {
return str
}
repeatedS := strings.Repeat(str, int(maxRunes/utf8.RuneCountInString(str))+1)
return Trim(repeatedS, maxRunes)
}
// RuneCount is similar to utf8.RuneCountInString, except for the fact that it
// ignores escape sequences while counting. For ex.:
// RuneCount("") == 0
// RuneCount("Ghost") == 5
// RuneCount("\x1b[33mGhost\x1b[0m") == 5
// RuneCount("\x1b[33mGhost\x1b[0") == 5
func RuneCount(str string) int {
count, isEscSeq := 0, false
for _, c := range str {
if c == EscapeStartRune {
isEscSeq = true
} else if isEscSeq {
if c == EscapeStopRune {
isEscSeq = false
}
} else {
count += RuneWidth(c)
}
}
return count
}
// RuneWidth returns the mostly accurate character-width of the rune. This is
// not 100% accurate as the character width is usually dependant on the
// typeface (font) used in the console/terminal. For ex.:
// RuneWidth('A') == 1
// RuneWidth('ツ') == 2
// RuneWidth('⊙') == 1
// RuneWidth('︿') == 2
// RuneWidth(0x27) == 0
func RuneWidth(r rune) int {
return runewidth.RuneWidth(r)
}
// Snip returns the given string with a fixed length. For ex.:
// Snip("Ghost", 0, "~") == "Ghost"
// Snip("Ghost", 1, "~") == "~"
// Snip("Ghost", 3, "~") == "Gh~"
// Snip("Ghost", 5, "~") == "Ghost"
// Snip("Ghost", 7, "~") == "Ghost "
// Snip("\x1b[33mGhost\x1b[0m", 7, "~") == "\x1b[33mGhost\x1b[0m "
func Snip(str string, length int, snipIndicator string) string {
if length > 0 {
lenStr := RuneCount(str)
if lenStr > length {
lenStrFinal := length - RuneCount(snipIndicator)
return Trim(str, lenStrFinal) + snipIndicator
}
}
return str
}
// Trim trims a string to the given length while ignoring escape sequences. For
// ex.:
// Trim("Ghost", 3) == "Gho"
// Trim("Ghost", 6) == "Ghost"
// Trim("\x1b[33mGhost\x1b[0m", 3) == "\x1b[33mGho\x1b[0m"
// Trim("\x1b[33mGhost\x1b[0m", 6) == "\x1b[33mGhost\x1b[0m"
func Trim(str string, maxLen int) string {
if maxLen <= 0 {
return ""
}
var out strings.Builder
out.Grow(maxLen)
outLen, isEscSeq, lastEscSeq := 0, false, strings.Builder{}
for _, sChr := range str {
out.WriteRune(sChr)
if sChr == EscapeStartRune {
isEscSeq = true
lastEscSeq.Reset()
lastEscSeq.WriteRune(sChr)
} else if isEscSeq {
lastEscSeq.WriteRune(sChr)
if sChr == EscapeStopRune {
isEscSeq = false
}
} else {
outLen++
if outLen == maxLen {
break
}
}
}
if lastEscSeq.Len() > 0 && lastEscSeq.String() != EscapeReset {
out.WriteString(EscapeReset)
}
return out.String()
}

191
vendor/github.com/jedib0t/go-pretty/text/transformer.go generated vendored Normal file
View File

@@ -0,0 +1,191 @@
package text
import (
"bytes"
"encoding/json"
"fmt"
"strconv"
"strings"
"time"
"github.com/go-openapi/strfmt"
)
// Transformer related constants
const (
unixTimeMinMilliseconds = int64(10000000000)
unixTimeMinMicroseconds = unixTimeMinMilliseconds * 1000
unixTimeMinNanoSeconds = unixTimeMinMicroseconds * 1000
)
// Transformer related variables
var (
colorsNumberPositive = Colors{FgHiGreen}
colorsNumberNegative = Colors{FgHiRed}
colorsNumberZero = Colors{}
colorsURL = Colors{Underline, FgBlue}
)
// Transformer helps format the contents of an object to the user's liking.
type Transformer func(val interface{}) string
// NewNumberTransformer returns a number Transformer that:
// * transforms the number as directed by 'format' (ex.: %.2f)
// * colors negative values Red
// * colors positive values Green
func NewNumberTransformer(format string) Transformer {
return func(val interface{}) string {
if number, ok := val.(int); ok {
return transformInt(format, int64(number))
}
if number, ok := val.(int8); ok {
return transformInt(format, int64(number))
}
if number, ok := val.(int16); ok {
return transformInt(format, int64(number))
}
if number, ok := val.(int32); ok {
return transformInt(format, int64(number))
}
if number, ok := val.(int64); ok {
return transformInt(format, int64(number))
}
if number, ok := val.(uint); ok {
return transformUint(format, uint64(number))
}
if number, ok := val.(uint8); ok {
return transformUint(format, uint64(number))
}
if number, ok := val.(uint16); ok {
return transformUint(format, uint64(number))
}
if number, ok := val.(uint32); ok {
return transformUint(format, uint64(number))
}
if number, ok := val.(uint64); ok {
return transformUint(format, uint64(number))
}
if number, ok := val.(float32); ok {
return transformFloat(format, float64(number))
}
if number, ok := val.(float64); ok {
return transformFloat(format, float64(number))
}
return fmt.Sprint(val)
}
}
func transformInt(format string, val int64) string {
if val < 0 {
return colorsNumberNegative.Sprintf("-"+format, -val)
}
if val > 0 {
return colorsNumberPositive.Sprintf(format, val)
}
return colorsNumberZero.Sprintf(format, val)
}
func transformUint(format string, val uint64) string {
if val > 0 {
return colorsNumberPositive.Sprintf(format, val)
}
return colorsNumberZero.Sprintf(format, val)
}
func transformFloat(format string, val float64) string {
if val < 0 {
return colorsNumberNegative.Sprintf("-"+format, -val)
}
if val > 0 {
return colorsNumberPositive.Sprintf(format, val)
}
return colorsNumberZero.Sprintf(format, val)
}
// NewJSONTransformer returns a Transformer that can format a JSON string or an
// object into pretty-indented JSON-strings.
func NewJSONTransformer(prefix string, indent string) Transformer {
return func(val interface{}) string {
if valStr, ok := val.(string); ok {
var b bytes.Buffer
if err := json.Indent(&b, []byte(strings.TrimSpace(valStr)), prefix, indent); err == nil {
return string(b.Bytes())
}
} else if b, err := json.MarshalIndent(val, prefix, indent); err == nil {
return string(b)
}
return fmt.Sprintf("%#v", val)
}
}
// NewTimeTransformer returns a Transformer that can format a timestamp (a
// time.Time or strfmt.DateTime object) into a well-defined time format defined
// using the provided layout (ex.: time.RFC3339).
//
// If a non-nil location value is provided, the time will be localized to that
// location (use time.Local to get localized timestamps).
func NewTimeTransformer(layout string, location *time.Location) Transformer {
return func(val interface{}) string {
formatTime := func(t time.Time) string {
rsp := ""
if t.Unix() > 0 {
if location != nil {
t = t.In(location)
}
rsp = t.Format(layout)
}
return rsp
}
rsp := fmt.Sprint(val)
if valDate, ok := val.(strfmt.DateTime); ok {
rsp = formatTime(time.Time(valDate))
} else if valTime, ok := val.(time.Time); ok {
rsp = formatTime(valTime)
} else if valStr, ok := val.(string); ok {
if valTime, err := time.Parse(time.RFC3339, valStr); err == nil {
rsp = formatTime(valTime)
}
}
return rsp
}
}
// NewUnixTimeTransformer returns a Transformer that can format a unix-timestamp
// into a well-defined time format as defined by 'layout'. This can handle
// unix-time in Seconds, MilliSeconds, Microseconds and Nanoseconds.
//
// If a non-nil location value is provided, the time will be localized to that
// location (use time.Local to get localized timestamps).
func NewUnixTimeTransformer(layout string, location *time.Location) Transformer {
timeTransformer := NewTimeTransformer(layout, location)
formatUnixTime := func(unixTime int64) string {
if unixTime >= unixTimeMinNanoSeconds {
unixTime = unixTime / time.Second.Nanoseconds()
} else if unixTime >= unixTimeMinMicroseconds {
unixTime = unixTime / (time.Second.Nanoseconds() / 1000)
} else if unixTime >= unixTimeMinMilliseconds {
unixTime = unixTime / (time.Second.Nanoseconds() / 1000000)
}
return timeTransformer(time.Unix(unixTime, 0))
}
return func(val interface{}) string {
if unixTime, ok := val.(int64); ok {
return formatUnixTime(unixTime)
} else if unixTimeStr, ok := val.(string); ok {
if unixTime, err := strconv.ParseInt(unixTimeStr, 10, 64); err == nil {
return formatUnixTime(unixTime)
}
}
return fmt.Sprint(val)
}
}
// NewURLTransformer returns a Transformer that can format and pretty print a string
// that contains an URL (the text is underlined and colored Blue).
func NewURLTransformer() Transformer {
return func(val interface{}) string {
return colorsURL.Sprint(val)
}
}

67
vendor/github.com/jedib0t/go-pretty/text/valign.go generated vendored Normal file
View File

@@ -0,0 +1,67 @@
package text
import "strings"
// VAlign denotes how text is to be aligned vertically.
type VAlign int
// VAlign enumerations
const (
VAlignDefault VAlign = iota // same as VAlignTop
VAlignTop // "top\n\n"
VAlignMiddle // "\nmiddle\n"
VAlignBottom // "\n\nbottom"
)
// Apply aligns the lines vertically. For ex.:
// * VAlignTop.Apply({"Game", "Of", "Thrones"}, 5)
// returns {"Game", "Of", "Thrones", "", ""}
// * VAlignMiddle.Apply({"Game", "Of", "Thrones"}, 5)
// returns {"", "Game", "Of", "Thrones", ""}
// * VAlignBottom.Apply({"Game", "Of", "Thrones"}, 5)
// returns {"", "", "Game", "Of", "Thrones"}
func (va VAlign) Apply(lines []string, maxLines int) []string {
if len(lines) == maxLines {
return lines
} else if len(lines) > maxLines {
maxLines = len(lines)
}
insertIdx := 0
if va == VAlignMiddle {
insertIdx = int(maxLines-len(lines)) / 2
} else if va == VAlignBottom {
insertIdx = maxLines - len(lines)
}
linesOut := strings.Split(strings.Repeat("\n", maxLines-1), "\n")
for idx, line := range lines {
linesOut[idx+insertIdx] = line
}
return linesOut
}
// ApplyStr aligns the string (of 1 or more lines) vertically. For ex.:
// * VAlignTop.ApplyStr("Game\nOf\nThrones", 5)
// returns {"Game", "Of", "Thrones", "", ""}
// * VAlignMiddle.ApplyStr("Game\nOf\nThrones", 5)
// returns {"", "Game", "Of", "Thrones", ""}
// * VAlignBottom.ApplyStr("Game\nOf\nThrones", 5)
// returns {"", "", "Game", "Of", "Thrones"}
func (va VAlign) ApplyStr(text string, maxLines int) []string {
return va.Apply(strings.Split(text, "\n"), maxLines)
}
// HTMLProperty returns the equivalent HTML vertical-align tag property.
func (va VAlign) HTMLProperty() string {
switch va {
case VAlignTop:
return "valign=\"top\""
case VAlignMiddle:
return "valign=\"middle\""
case VAlignBottom:
return "valign=\"bottom\""
default:
return ""
}
}

256
vendor/github.com/jedib0t/go-pretty/text/wrap.go generated vendored Normal file
View File

@@ -0,0 +1,256 @@
package text
import (
"strings"
"unicode/utf8"
)
// WrapHard wraps a string to the given length using a newline. Handles strings
// with ANSI escape sequences (such as text color) without breaking the text
// formatting. Breaks all words that go beyond the line boundary.
//
// For examples, refer to the unit-tests or GoDoc examples.
func WrapHard(str string, wrapLen int) string {
if wrapLen <= 0 {
return ""
}
str = strings.Replace(str, "\t", " ", -1)
sLen := utf8.RuneCountInString(str)
if sLen <= wrapLen {
return str
}
out := &strings.Builder{}
out.Grow(sLen + (sLen / wrapLen))
for idx, paragraph := range strings.Split(str, "\n\n") {
if idx > 0 {
out.WriteString("\n\n")
}
wrapHard(paragraph, wrapLen, out)
}
return out.String()
}
// WrapSoft wraps a string to the given length using a newline. Handles strings
// with ANSI escape sequences (such as text color) without breaking the text
// formatting. Tries to move words that go beyond the line boundary to the next
// line.
//
// For examples, refer to the unit-tests or GoDoc examples.
func WrapSoft(str string, wrapLen int) string {
if wrapLen <= 0 {
return ""
}
str = strings.Replace(str, "\t", " ", -1)
sLen := utf8.RuneCountInString(str)
if sLen <= wrapLen {
return str
}
out := &strings.Builder{}
out.Grow(sLen + (sLen / wrapLen))
for idx, paragraph := range strings.Split(str, "\n\n") {
if idx > 0 {
out.WriteString("\n\n")
}
wrapSoft(paragraph, wrapLen, out)
}
return out.String()
}
// WrapText is very similar to WrapHard except for one minor difference. Unlike
// WrapHard which discards line-breaks and respects only paragraph-breaks, this
// function respects line-breaks too.
//
// For examples, refer to the unit-tests or GoDoc examples.
func WrapText(str string, wrapLen int) string {
if wrapLen <= 0 {
return ""
}
var out strings.Builder
sLen := utf8.RuneCountInString(str)
out.Grow(sLen + (sLen / wrapLen))
lineIdx, isEscSeq, lastEscSeq := 0, false, ""
for _, char := range str {
if char == EscapeStartRune {
isEscSeq = true
lastEscSeq = ""
}
if isEscSeq {
lastEscSeq += string(char)
}
appendChar(char, wrapLen, &lineIdx, isEscSeq, lastEscSeq, &out)
if isEscSeq && char == EscapeStopRune {
isEscSeq = false
}
if lastEscSeq == EscapeReset {
lastEscSeq = ""
}
}
if lastEscSeq != "" && lastEscSeq != EscapeReset {
out.WriteString(EscapeReset)
}
return out.String()
}
func appendChar(char rune, wrapLen int, lineLen *int, inEscSeq bool, lastSeenEscSeq string, out *strings.Builder) {
// handle reaching the end of the line as dictated by wrapLen or by finding
// a newline character
if (*lineLen == wrapLen && !inEscSeq && char != '\n') || (char == '\n') {
if lastSeenEscSeq != "" {
// terminate escape sequence and the line; and restart the escape
// sequence in the next line
out.WriteString(EscapeReset)
out.WriteRune('\n')
out.WriteString(lastSeenEscSeq)
} else {
// just start a new line
out.WriteRune('\n')
}
// reset line index to 0th character
*lineLen = 0
}
// if the rune is not a new line, output it
if char != '\n' {
out.WriteRune(char)
// increment the line index if not in the middle of an escape sequence
if !inEscSeq {
*lineLen++
}
}
}
func appendWord(word string, lineIdx *int, lastSeenEscSeq string, wrapLen int, out *strings.Builder) {
inEscSeq := false
for _, char := range word {
if char == EscapeStartRune {
inEscSeq = true
lastSeenEscSeq = ""
}
if inEscSeq {
lastSeenEscSeq += string(char)
}
appendChar(char, wrapLen, lineIdx, inEscSeq, lastSeenEscSeq, out)
if inEscSeq && char == EscapeStopRune {
inEscSeq = false
}
if lastSeenEscSeq == EscapeReset {
lastSeenEscSeq = ""
}
}
}
func extractOpenEscapeSeq(str string) string {
escapeSeq, inEscSeq := "", false
for _, char := range str {
if char == EscapeStartRune {
inEscSeq = true
escapeSeq = ""
}
if inEscSeq {
escapeSeq += string(char)
}
if char == EscapeStopRune {
inEscSeq = false
}
}
if escapeSeq == EscapeReset {
escapeSeq = ""
}
return escapeSeq
}
func terminateLine(wrapLen int, lineLen *int, lastSeenEscSeq string, out *strings.Builder) {
if *lineLen < wrapLen {
out.WriteString(strings.Repeat(" ", wrapLen-*lineLen))
}
// something is already on the line; terminate it
if lastSeenEscSeq != "" {
out.WriteString(EscapeReset)
}
out.WriteRune('\n')
out.WriteString(lastSeenEscSeq)
*lineLen = 0
}
func terminateOutput(lastSeenEscSeq string, out *strings.Builder) {
if lastSeenEscSeq != "" && lastSeenEscSeq != EscapeReset && !strings.HasSuffix(out.String(), EscapeReset) {
out.WriteString(EscapeReset)
}
}
func wrapHard(paragraph string, wrapLen int, out *strings.Builder) {
lineLen, lastSeenEscSeq := 0, ""
words := strings.Fields(paragraph)
for wordIdx, word := range words {
escSeq := extractOpenEscapeSeq(word)
if escSeq != "" {
lastSeenEscSeq = escSeq
}
if lineLen > 0 {
out.WriteRune(' ')
lineLen++
}
wordLen := RuneCount(word)
if lineLen+wordLen <= wrapLen { // word fits within the line
out.WriteString(word)
lineLen += wordLen
} else { // word doesn't fit within the line; hard-wrap
appendWord(word, &lineLen, lastSeenEscSeq, wrapLen, out)
}
// end of line; but more words incoming
if lineLen == wrapLen && wordIdx < len(words)-1 {
terminateLine(wrapLen, &lineLen, lastSeenEscSeq, out)
}
}
terminateOutput(lastSeenEscSeq, out)
}
func wrapSoft(paragraph string, wrapLen int, out *strings.Builder) {
lineLen, lastSeenEscSeq := 0, ""
words := strings.Fields(paragraph)
for wordIdx, word := range words {
escSeq := extractOpenEscapeSeq(word)
if escSeq != "" {
lastSeenEscSeq = escSeq
}
spacing, spacingLen := "", 0
if lineLen > 0 {
spacing, spacingLen = " ", 1
}
wordLen := RuneCount(word)
if lineLen+spacingLen+wordLen <= wrapLen { // word fits within the line
out.WriteString(spacing)
out.WriteString(word)
lineLen += spacingLen + wordLen
} else { // word doesn't fit within the line
if lineLen > 0 { // something is already on the line; terminate it
terminateLine(wrapLen, &lineLen, lastSeenEscSeq, out)
}
if wordLen <= wrapLen { // word fits within a single line
out.WriteString(word)
lineLen = wordLen
} else { // word doesn't fit within a single line; hard-wrap
appendWord(word, &lineLen, lastSeenEscSeq, wrapLen, out)
}
}
// end of line; but more words incoming
if lineLen == wrapLen && wordIdx < len(words)-1 {
terminateLine(wrapLen, &lineLen, lastSeenEscSeq, out)
}
}
terminateOutput(lastSeenEscSeq, out)
}

21
vendor/github.com/jedib0t/go-pretty/v6/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 jedib0t
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.

30
vendor/github.com/jedib0t/go-pretty/v6/list/README.md generated vendored Normal file
View File

@@ -0,0 +1,30 @@
## List
[![GoDoc](https://godoc.org/github.com/jedib0t/go-pretty/list?status.svg)](https://godoc.org/github.com/jedib0t/go-pretty/list)
Pretty-print lists with multiple levels/indents into ASCII/Unicode strings.
- Append Items one-by-one or as a group
- Indent/UnIndent as you like
- Support Items with Multiple-lines
- Mirror output to an io.Writer object (like os.StdOut)
- Completely customizable styles
- Many ready-to-use styles: [style.go](style.go)
- Render as:
- (ASCII/Unicode) List
- HTML List (with custom CSS Class)
- Markdown List
```
■ Game Of Thrones
■ Winter
■ Is
■ Coming
■ This
■ Is
■ Known
■ The Dark Tower
■ The Gunslinger
```
A demonstration of all the capabilities can be found here:
[../cmd/demo-list](../cmd/demo-list)

174
vendor/github.com/jedib0t/go-pretty/v6/list/list.go generated vendored Normal file
View File

@@ -0,0 +1,174 @@
package list
import (
"fmt"
"io"
"strings"
"unicode/utf8"
)
const (
// DefaultHTMLCSSClass stores the css-class to use when none-provided via
// SetHTMLCSSClass(cssClass string).
DefaultHTMLCSSClass = "go-pretty-table"
)
// listItem represents one line in the List
type listItem struct {
Level int
Text string
}
// List helps print a 2-dimensional array in a human readable pretty-List.
type List struct {
// approxSize stores the approximate output length/size
approxSize int
// htmlCSSClass stores the HTML CSS Class to use on the <ul> node
htmlCSSClass string
// items contains the list of items to render
items []*listItem
// level stores the current indentation level
level int
// outputMirror stores an io.Writer where the "Render" functions would write
outputMirror io.Writer
// style contains all the strings used to draw the List, and more
style *Style
}
// AppendItem appends the item to the List of items to render.
func (l *List) AppendItem(item interface{}) {
l.items = append(l.items, l.analyzeAndStringify(item))
}
// AppendItems appends the items to the List of items to render.
func (l *List) AppendItems(items []interface{}) {
for _, item := range items {
l.AppendItem(item)
}
}
// Indent indents the following items to appear right-shifted.
func (l *List) Indent() {
if len(l.items) == 0 {
// should not indent when there is no item in the current level
} else if l.level > l.items[len(l.items)-1].Level {
// already indented compared to previous item; do not indent more
} else {
l.level++
}
}
// Length returns the number of items to be rendered.
func (l *List) Length() int {
return len(l.items)
}
// Reset sets the List to its initial state.
func (l *List) Reset() {
l.approxSize = 0
l.items = make([]*listItem, 0)
l.level = 0
l.style = nil
}
// SetHTMLCSSClass sets the the HTML CSS Class to use on the <ul> node
// when rendering the List in HTML format. Recursive lists would use a numbered
// index suffix. For ex., if the cssClass is set as "foo"; the <ul> for level 0
// would have the class set as "foo"; the <ul> for level 1 would have "foo-1".
func (l *List) SetHTMLCSSClass(cssClass string) {
l.htmlCSSClass = cssClass
}
// SetOutputMirror sets an io.Writer for all the Render functions to "Write" to
// in addition to returning a string.
func (l *List) SetOutputMirror(mirror io.Writer) {
l.outputMirror = mirror
}
// SetStyle overrides the DefaultStyle with the provided one.
func (l *List) SetStyle(style Style) {
l.style = &style
}
// Style returns the current style.
func (l *List) Style() *Style {
if l.style == nil {
tempStyle := StyleDefault
l.style = &tempStyle
}
return l.style
}
func (l *List) analyzeAndStringify(item interface{}) *listItem {
itemStr := fmt.Sprint(item)
if strings.Contains(itemStr, "\t") {
itemStr = strings.Replace(itemStr, "\t", " ", -1)
}
if strings.Contains(itemStr, "\r") {
itemStr = strings.Replace(itemStr, "\r", "", -1)
}
return &listItem{
Level: l.level,
Text: itemStr,
}
}
// UnIndent un-indents the following items to appear left-shifted.
func (l *List) UnIndent() {
if l.level > 0 {
l.level--
}
}
func (l *List) initForRender() {
// pick a default style
l.Style()
// calculate the approximate size needed by looking at all entries
l.approxSize = 0
for _, item := range l.items {
// account for the following when incrementing approxSize:
// 1. prefix, 2. padding, 3. bullet, 4. text, 5. newline
l.approxSize += utf8.RuneCountInString(l.style.LinePrefix)
if item.Level > 0 {
l.approxSize += utf8.RuneCountInString(l.style.CharItemVertical) * item.Level
}
l.approxSize += utf8.RuneCountInString(l.style.CharItemVertical)
l.approxSize += utf8.RuneCountInString(item.Text)
l.approxSize += utf8.RuneCountInString(l.style.CharNewline)
}
// default to a HTML CSS Class if none-defined
if l.htmlCSSClass == "" {
l.htmlCSSClass = DefaultHTMLCSSClass
}
}
func (l *List) hasMoreItemsInLevel(levelIdx int, fromItemIdx int) bool {
for idx := fromItemIdx + 1; idx >= 0 && idx < len(l.items); idx++ {
if l.items[idx].Level < levelIdx {
return false
} else if l.items[idx].Level == levelIdx {
return true
}
}
return false
}
func (l *List) render(out *strings.Builder) string {
outStr := out.String()
if l.outputMirror != nil && len(outStr) > 0 {
l.outputMirror.Write([]byte(outStr))
l.outputMirror.Write([]byte("\n"))
}
return outStr
}
// renderHint has hints for the Render*() logic
type renderHint struct {
isTopItem bool
isFirstItem bool
isOnlyItem bool
isLastItem bool
isBottomItem bool
}

110
vendor/github.com/jedib0t/go-pretty/v6/list/render.go generated vendored Normal file
View File

@@ -0,0 +1,110 @@
package list
import (
"strings"
"unicode/utf8"
)
// Render renders the List in a human-readable "pretty" format. Example:
// * Game Of Thrones
// * Winter
// * Is
// * Coming
// * This
// * Is
// * Known
// * The Dark Tower
// * The Gunslinger
func (l *List) Render() string {
l.initForRender()
var out strings.Builder
out.Grow(l.approxSize)
for idx, item := range l.items {
hint := renderHint{
isTopItem: bool(idx == 0),
isFirstItem: bool(idx == 0 || item.Level > l.items[idx-1].Level),
isLastItem: !l.hasMoreItemsInLevel(item.Level, idx),
isBottomItem: bool(idx == len(l.items)-1),
}
if hint.isFirstItem && hint.isLastItem {
hint.isOnlyItem = true
}
l.renderItem(&out, idx, item, hint)
}
return l.render(&out)
}
func (l *List) renderItem(out *strings.Builder, idx int, item *listItem, hint renderHint) {
// when working on item number 2 or more, render a newline first
if idx > 0 {
out.WriteRune('\n')
}
// format item.Text as directed in l.style
itemStr := l.style.Format.Apply(item.Text)
// convert newlines if newlines are not "\n" in l.style
if strings.Contains(itemStr, "\n") && l.style.CharNewline != "\n" {
itemStr = strings.Replace(itemStr, "\n", l.style.CharNewline, -1)
}
// render the item.Text line by line
for lineIdx, lineStr := range strings.Split(itemStr, "\n") {
if lineIdx > 0 {
out.WriteRune('\n')
}
// render the prefix or the leading text before the actual item
l.renderItemBulletPrefix(out, idx, item.Level, lineIdx, hint)
l.renderItemBullet(out, idx, item.Level, lineIdx, hint)
// render the actual item
out.WriteString(lineStr)
}
}
func (l *List) renderItemBullet(out *strings.Builder, itemIdx int, itemLevel int, lineIdx int, hint renderHint) {
if lineIdx > 0 {
// multi-line item.Text
if hint.isLastItem {
out.WriteString(strings.Repeat(" ", utf8.RuneCountInString(l.style.CharItemVertical)))
} else {
out.WriteString(l.style.CharItemVertical)
}
} else {
// single-line item.Text (or first line of a multi-line item.Text)
if hint.isOnlyItem {
if hint.isTopItem {
out.WriteString(l.style.CharItemSingle)
} else {
out.WriteString(l.style.CharItemBottom)
}
} else if hint.isTopItem {
out.WriteString(l.style.CharItemTop)
} else if hint.isFirstItem {
out.WriteString(l.style.CharItemFirst)
} else if hint.isBottomItem || hint.isLastItem {
out.WriteString(l.style.CharItemBottom)
} else {
out.WriteString(l.style.CharItemMiddle)
}
out.WriteRune(' ')
}
}
func (l *List) renderItemBulletPrefix(out *strings.Builder, itemIdx int, itemLevel int, lineIdx int, hint renderHint) {
// write a prefix if one has been set in l.style
if l.style.LinePrefix != "" {
out.WriteString(l.style.LinePrefix)
}
// render spaces and connectors until the item's position
for levelIdx := 0; levelIdx < itemLevel; levelIdx++ {
if l.hasMoreItemsInLevel(levelIdx, itemIdx) {
out.WriteString(l.style.CharItemVertical)
} else {
out.WriteString(strings.Repeat(" ", utf8.RuneCountInString(l.style.CharItemVertical)))
}
}
}

View File

@@ -0,0 +1,70 @@
package list
import (
"html"
"strconv"
"strings"
)
// RenderHTML renders the List in the HTML format. Example:
// <ul class="go-pretty-table">
// <li>Game Of Thrones</li>
// <ul class="go-pretty-table-1">
// <li>Winter</li>
// <li>Is</li>
// <li>Coming</li>
// <ul class="go-pretty-table-2">
// <li>This</li>
// <li>Is</li>
// <li>Known</li>
// </ul>
// </ul>
// <li>The Dark Tower</li>
// <ul class="go-pretty-table-1">
// <li>The Gunslinger</li>
// </ul>
// </ul>
func (l *List) RenderHTML() string {
l.initForRender()
var out strings.Builder
if len(l.items) > 0 {
l.htmlRenderRecursively(&out, 0, l.items[0])
}
return l.render(&out)
}
func (l *List) htmlRenderRecursively(out *strings.Builder, idx int, item *listItem) int {
linePrefix := strings.Repeat(" ", item.Level)
out.WriteString(linePrefix)
out.WriteString("<ul class=\"")
out.WriteString(l.htmlCSSClass)
if item.Level > 0 {
out.WriteRune('-')
out.WriteString(strconv.Itoa(item.Level))
}
out.WriteString("\">\n")
var numItemsRendered int
for itemIdx := idx; itemIdx < len(l.items); itemIdx++ {
if l.items[itemIdx].Level == item.Level {
out.WriteString(linePrefix)
out.WriteString(" <li>")
out.WriteString(strings.Replace(html.EscapeString(l.items[itemIdx].Text), "\n", "<br/>", -1))
out.WriteString("</li>\n")
numItemsRendered++
} else if l.items[itemIdx].Level > item.Level { // indent
numItemsRenderedRecursively := l.htmlRenderRecursively(out, itemIdx, l.items[itemIdx])
numItemsRendered += numItemsRenderedRecursively
itemIdx += numItemsRenderedRecursively - 1
if numItemsRendered > 0 {
out.WriteRune('\n')
}
} else { // un-indent
break
}
}
out.WriteString(linePrefix)
out.WriteString("</ul>")
return numItemsRendered
}

View File

@@ -0,0 +1,29 @@
package list
// RenderMarkdown renders the List in the Markdown format. Example:
// * Game Of Thrones
// * Winter
// * Is
// * Coming
// * This
// * Is
// * Known
// * The Dark Tower
// * The Gunslinger
func (l *List) RenderMarkdown() string {
// make a copy of the original style and ensure it is restored on exit
originalStyle := l.style
defer func() {
if originalStyle == nil {
l.style = nil
} else {
l.SetStyle(*originalStyle)
}
}()
// override whatever style was set with StyleMarkdown
l.SetStyle(StyleMarkdown)
// render like a regular list
return l.Render()
}

295
vendor/github.com/jedib0t/go-pretty/v6/list/style.go generated vendored Normal file
View File

@@ -0,0 +1,295 @@
package list
import "github.com/jedib0t/go-pretty/v6/text"
// Style declares how to render the List (items).
type Style struct {
Format text.Format // formatting for the Text
CharItemSingle string // the bullet for a single-item list
CharItemTop string // the bullet for the top-most item
CharItemFirst string // the bullet for the first item
CharItemMiddle string // the bullet for non-first/non-last item
CharItemVertical string // the vertical connector from one bullet to the next
CharItemBottom string // the bullet for the bottom-most item
CharNewline string // new-line character to use
LinePrefix string // prefix for every single line
Name string // name of the Style
}
var (
// StyleDefault renders a List like below:
// * Game Of Thrones
// * Winter
// * Is
// * Coming
// * This
// * Is
// * Known
// * The Dark Tower
// * The Gunslinger
StyleDefault = Style{
Format: text.FormatDefault,
CharItemSingle: "*",
CharItemTop: "*",
CharItemFirst: "*",
CharItemMiddle: "*",
CharItemVertical: " ",
CharItemBottom: "*",
CharNewline: "\n",
LinePrefix: "",
Name: "StyleDefault",
}
// StyleBulletCircle renders a List like below:
// ● Game Of Thrones
// ● Winter
// ● Is
// ● Coming
// ● This
// ● Is
// ● Known
// ● The Dark Tower
// ● The Gunslinger
StyleBulletCircle = Style{
Format: text.FormatDefault,
CharItemSingle: "●",
CharItemTop: "●",
CharItemFirst: "●",
CharItemMiddle: "●",
CharItemVertical: " ",
CharItemBottom: "●",
CharNewline: "\n",
LinePrefix: "",
Name: "StyleBulletCircle",
}
// StyleBulletFlower renders a List like below:
// ✽ Game Of Thrones
// ✽ Winter
// ✽ Is
// ✽ Coming
// ✽ This
// ✽ Is
// ✽ Known
// ✽ The Dark Tower
// ✽ The Gunslinger
StyleBulletFlower = Style{
Format: text.FormatDefault,
CharItemSingle: "✽",
CharItemTop: "✽",
CharItemFirst: "✽",
CharItemMiddle: "✽",
CharItemVertical: " ",
CharItemBottom: "✽",
CharNewline: "\n",
LinePrefix: "",
Name: "StyleBulletFlower",
}
// StyleBulletSquare renders a List like below:
// ■ Game Of Thrones
// ■ Winter
// ■ Is
// ■ Coming
// ■ This
// ■ Is
// ■ Known
// ■ The Dark Tower
// ■ The Gunslinger
StyleBulletSquare = Style{
Format: text.FormatDefault,
CharItemSingle: "■",
CharItemTop: "■",
CharItemFirst: "■",
CharItemMiddle: "■",
CharItemVertical: " ",
CharItemBottom: "■",
CharNewline: "\n",
LinePrefix: "",
Name: "StyleBulletSquare",
}
// StyleBulletStar renders a List like below:
// ★ Game Of Thrones
// ★ Winter
// ★ Is
// ★ Coming
// ★ This
// ★ Is
// ★ Known
// ★ The Dark Tower
// ★ The Gunslinger
StyleBulletStar = Style{
Format: text.FormatDefault,
CharItemSingle: "★",
CharItemTop: "★",
CharItemFirst: "★",
CharItemMiddle: "★",
CharItemVertical: " ",
CharItemBottom: "★",
CharNewline: "\n",
LinePrefix: "",
Name: "StyleBulletStar",
}
// StyleBulletTriangle renders a List like below:
// ▶ Game Of Thrones
// ▶ Winter
// ▶ Is
// ▶ Coming
// ▶ This
// ▶ Is
// ▶ Known
// ▶ The Dark Tower
// ▶ The Gunslinger
StyleBulletTriangle = Style{
Format: text.FormatDefault,
CharItemSingle: "▶",
CharItemTop: "▶",
CharItemFirst: "▶",
CharItemMiddle: "▶",
CharItemVertical: " ",
CharItemBottom: "▶",
CharNewline: "\n",
LinePrefix: "",
Name: "StyleBulletTriangle",
}
// StyleConnectedBold renders a List like below:
// ┏━ Game Of Thrones
// ┃ ┣━ Winter
// ┃ ┣━ Is
// ┃ ┗━ Coming
// ┃ ┣━ This
// ┃ ┣━ Is
// ┃ ┗━ Known
// ┗━ The Dark Tower
// ┗━ The Gunslinger
StyleConnectedBold = Style{
Format: text.FormatDefault,
CharItemSingle: "━━",
CharItemTop: "┏━",
CharItemFirst: "┣━",
CharItemMiddle: "┣━",
CharItemVertical: "┃ ",
CharItemBottom: "┗━",
CharNewline: "\n",
LinePrefix: "",
Name: "StyleConnectedBold",
}
// StyleConnectedDouble renders a List like below:
// ╔═ Game Of Thrones
// ║ ╠═ Winter
// ║ ╠═ Is
// ║ ╚═ Coming
// ║ ╠═ This
// ║ ╠═ Is
// ║ ╚═ Known
// ╚═ The Dark Tower
// ╚═ The Gunslinger
StyleConnectedDouble = Style{
Format: text.FormatDefault,
CharItemSingle: "══",
CharItemTop: "╔═",
CharItemFirst: "╠═",
CharItemMiddle: "╠═",
CharItemVertical: "║ ",
CharItemBottom: "╚═",
CharNewline: "\n",
LinePrefix: "",
Name: "StyleConnectedDouble",
}
// StyleConnectedLight renders a List like below:
// ┌─ Game Of Thrones
// │ ├─ Winter
// │ ├─ Is
// │ └─ Coming
// │ ├─ This
// │ ├─ Is
// │ └─ Known
// └─ The Dark Tower
// └─ The Gunslinger
StyleConnectedLight = Style{
Format: text.FormatDefault,
CharItemSingle: "──",
CharItemTop: "┌─",
CharItemFirst: "├─",
CharItemMiddle: "├─",
CharItemVertical: "│ ",
CharItemBottom: "└─",
CharNewline: "\n",
LinePrefix: "",
Name: "StyleConnectedLight",
}
// StyleConnectedRounded renders a List like below:
// ╭─ Game Of Thrones
// │ ├─ Winter
// │ ├─ Is
// │ ╰─ Coming
// │ ├─ This
// │ ├─ Is
// │ ╰─ Known
// ╰─ The Dark Tower
// ╰─ The Gunslinger
StyleConnectedRounded = Style{
Format: text.FormatDefault,
CharItemSingle: "──",
CharItemTop: "╭─",
CharItemFirst: "├─",
CharItemMiddle: "├─",
CharItemVertical: "│ ",
CharItemBottom: "╰─",
CharNewline: "\n",
LinePrefix: "",
Name: "StyleConnectedRounded",
}
// StyleMarkdown renders a List like below:
// * Game Of Thrones
// * Winter
// * Is
// * Coming
// * This
// * Is
// * Known
// * The Dark Tower
// * The Gunslinger
StyleMarkdown = Style{
Format: text.FormatDefault,
CharItemSingle: "*",
CharItemTop: "*",
CharItemFirst: "*",
CharItemMiddle: "*",
CharItemVertical: " ",
CharItemBottom: "*",
CharNewline: "<br/>",
LinePrefix: " ",
Name: "StyleMarkdown",
}
// styleTest renders a List like below:
// t Game Of Thrones
// |f Winter
// |m Is
// |b Coming
// | f This
// | m Is
// | b Known
// b The Dark Tower
// b The Gunslinger
styleTest = Style{
Format: text.FormatDefault,
CharItemSingle: "s",
CharItemTop: "t",
CharItemFirst: "f",
CharItemMiddle: "m",
CharItemVertical: "|",
CharItemBottom: "b",
CharNewline: "\n",
LinePrefix: "",
Name: "styleTest",
}
)

25
vendor/github.com/jedib0t/go-pretty/v6/list/writer.go generated vendored Normal file
View File

@@ -0,0 +1,25 @@
package list
import "io"
// Writer declares the interfaces that can be used to setup and render a list.
type Writer interface {
AppendItem(item interface{})
AppendItems(items []interface{})
Indent()
Length() int
Render() string
RenderHTML() string
RenderMarkdown() string
Reset()
SetHTMLCSSClass(cssClass string)
SetOutputMirror(mirror io.Writer)
SetStyle(style Style)
Style() *Style
UnIndent()
}
// NewWriter initializes and returns a Writer.
func NewWriter() Writer {
return &List{}
}

137
vendor/github.com/jedib0t/go-pretty/v6/text/align.go generated vendored Normal file
View File

@@ -0,0 +1,137 @@
package text
import (
"fmt"
"strconv"
"strings"
"unicode/utf8"
)
// Align denotes how text is to be aligned horizontally.
type Align int
// Align enumerations
const (
AlignDefault Align = iota // same as AlignLeft
AlignLeft // "left "
AlignCenter // " center "
AlignJustify // "justify it"
AlignRight // " right"
)
// Apply aligns the text as directed. For ex.:
// * AlignDefault.Apply("Jon Snow", 12) returns "Jon Snow "
// * AlignLeft.Apply("Jon Snow", 12) returns "Jon Snow "
// * AlignCenter.Apply("Jon Snow", 12) returns " Jon Snow "
// * AlignJustify.Apply("Jon Snow", 12) returns "Jon Snow"
// * AlignRight.Apply("Jon Snow", 12) returns " Jon Snow"
func (a Align) Apply(text string, maxLength int) string {
text = a.trimString(text)
sLen := utf8.RuneCountInString(text)
sLenWoE := RuneCount(text)
numEscChars := sLen - sLenWoE
// now, align the text
switch a {
case AlignDefault, AlignLeft:
return fmt.Sprintf("%-"+strconv.Itoa(maxLength+numEscChars)+"s", text)
case AlignCenter:
if sLenWoE < maxLength {
// left pad with half the number of spaces needed before using %text
return fmt.Sprintf("%"+strconv.Itoa(maxLength+numEscChars)+"s",
text+strings.Repeat(" ", int((maxLength-sLenWoE)/2)))
}
case AlignJustify:
return a.justifyText(text, sLenWoE, maxLength)
}
return fmt.Sprintf("%"+strconv.Itoa(maxLength+numEscChars)+"s", text)
}
// HTMLProperty returns the equivalent HTML horizontal-align tag property.
func (a Align) HTMLProperty() string {
switch a {
case AlignLeft:
return "align=\"left\""
case AlignCenter:
return "align=\"center\""
case AlignJustify:
return "align=\"justify\""
case AlignRight:
return "align=\"right\""
default:
return ""
}
}
// MarkdownProperty returns the equivalent Markdown horizontal-align separator.
func (a Align) MarkdownProperty() string {
switch a {
case AlignLeft:
return ":--- "
case AlignCenter:
return ":---:"
case AlignRight:
return " ---:"
default:
return " --- "
}
}
func (a Align) justifyText(text string, textLength int, maxLength int) string {
// split the text into individual words
wordsUnfiltered := strings.Split(text, " ")
words := Filter(wordsUnfiltered, func(item string) bool {
return item != ""
})
// empty string implies spaces for maxLength
if len(words) == 0 {
return strings.Repeat(" ", maxLength)
}
// get the number of spaces to insert into the text
numSpacesNeeded := maxLength - textLength + strings.Count(text, " ")
numSpacesNeededBetweenWords := 0
if len(words) > 1 {
numSpacesNeededBetweenWords = numSpacesNeeded / (len(words) - 1)
}
// create the output string word by word with spaces in between
var outText strings.Builder
outText.Grow(maxLength)
for idx, word := range words {
if idx > 0 {
// insert spaces only after the first word
if idx == len(words)-1 {
// insert all the remaining space before the last word
outText.WriteString(strings.Repeat(" ", numSpacesNeeded))
numSpacesNeeded = 0
} else {
// insert the determined number of spaces between each word
outText.WriteString(strings.Repeat(" ", numSpacesNeededBetweenWords))
// and reduce the number of spaces needed after this
numSpacesNeeded -= numSpacesNeededBetweenWords
}
}
outText.WriteString(word)
if idx == len(words)-1 && numSpacesNeeded > 0 {
outText.WriteString(strings.Repeat(" ", numSpacesNeeded))
}
}
return outText.String()
}
func (a Align) trimString(text string) string {
switch a {
case AlignDefault, AlignLeft:
if strings.HasSuffix(text, " ") {
return strings.TrimRight(text, " ")
}
case AlignRight:
if strings.HasPrefix(text, " ") {
return strings.TrimLeft(text, " ")
}
default:
if strings.HasPrefix(text, " ") || strings.HasSuffix(text, " ") {
return strings.Trim(text, " ")
}
}
return text
}

55
vendor/github.com/jedib0t/go-pretty/v6/text/ansi.go generated vendored Normal file
View File

@@ -0,0 +1,55 @@
package text
import "strings"
// ANSICodesSupported will be true on consoles where ANSI Escape Codes/Sequences
// are supported.
var ANSICodesSupported = areANSICodesSupported()
// Escape encodes the string with the ANSI Escape Sequence.
// For ex.:
// Escape("Ghost", "") == "Ghost"
// Escape("Ghost", "\x1b[91m") == "\x1b[91mGhost\x1b[0m"
// Escape("\x1b[94mGhost\x1b[0mLady", "\x1b[91m") == "\x1b[94mGhost\x1b[0m\x1b[91mLady\x1b[0m"
// Escape("Nymeria\x1b[94mGhost\x1b[0mLady", "\x1b[91m") == "\x1b[91mNymeria\x1b[94mGhost\x1b[0m\x1b[91mLady\x1b[0m"
// Escape("Nymeria \x1b[94mGhost\x1b[0m Lady", "\x1b[91m") == "\x1b[91mNymeria \x1b[94mGhost\x1b[0m\x1b[91m Lady\x1b[0m"
func Escape(str string, escapeSeq string) string {
out := ""
if !strings.HasPrefix(str, EscapeStart) {
out += escapeSeq
}
out += strings.Replace(str, EscapeReset, EscapeReset+escapeSeq, -1)
if !strings.HasSuffix(out, EscapeReset) {
out += EscapeReset
}
if strings.Contains(out, escapeSeq+EscapeReset) {
out = strings.Replace(out, escapeSeq+EscapeReset, "", -1)
}
return out
}
// StripEscape strips all ANSI Escape Sequence from the string.
// For ex.:
// StripEscape("Ghost") == "Ghost"
// StripEscape("\x1b[91mGhost\x1b[0m") == "Ghost"
// StripEscape("\x1b[94mGhost\x1b[0m\x1b[91mLady\x1b[0m") == "GhostLady"
// StripEscape("\x1b[91mNymeria\x1b[94mGhost\x1b[0m\x1b[91mLady\x1b[0m") == "NymeriaGhostLady"
// StripEscape("\x1b[91mNymeria \x1b[94mGhost\x1b[0m\x1b[91m Lady\x1b[0m") == "Nymeria Ghost Lady"
func StripEscape(str string) string {
var out strings.Builder
out.Grow(RuneCount(str))
isEscSeq := false
for _, sChr := range str {
if sChr == EscapeStartRune {
isEscSeq = true
}
if !isEscSeq {
out.WriteRune(sChr)
}
if isEscSeq && sChr == EscapeStopRune {
isEscSeq = false
}
}
return out.String()
}

View File

@@ -0,0 +1,7 @@
// +build !windows
package text
func areANSICodesSupported() bool {
return true
}

View File

@@ -0,0 +1,31 @@
// +build windows
package text
import (
"os"
"sync"
"golang.org/x/sys/windows"
)
var (
enableVTPMutex = sync.Mutex{}
)
func areANSICodesSupported() bool {
enableVTPMutex.Lock()
defer enableVTPMutex.Unlock()
outHandle := windows.Handle(os.Stdout.Fd())
var outMode uint32
if err := windows.GetConsoleMode(outHandle, &outMode); err == nil {
if outMode&windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING != 0 {
return true
}
if err := windows.SetConsoleMode(outHandle, outMode|windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING); err == nil {
return true
}
}
return false
}

183
vendor/github.com/jedib0t/go-pretty/v6/text/color.go generated vendored Normal file
View File

@@ -0,0 +1,183 @@
package text
import (
"fmt"
"sort"
"strconv"
"strings"
"sync"
)
var (
colorsEnabled = areANSICodesSupported()
)
// DisableColors (forcefully) disables color coding globally.
func DisableColors() {
colorsEnabled = false
}
// EnableColors (forcefully) enables color coding globally.
func EnableColors() {
colorsEnabled = true
}
// The logic here is inspired from github.com/fatih/color; the following is
// the the bare minimum logic required to print Colored to the console.
// The differences:
// * This one caches the escape sequences for cases with multiple colors
// * This one handles cases where the incoming already has colors in the
// form of escape sequences; in which case, text that does not have any
// escape sequences are colored/escaped
// Color represents a single color to render with.
type Color int
// Base colors -- attributes in reality
const (
Reset Color = iota
Bold
Faint
Italic
Underline
BlinkSlow
BlinkRapid
ReverseVideo
Concealed
CrossedOut
)
// Foreground colors
const (
FgBlack Color = iota + 30
FgRed
FgGreen
FgYellow
FgBlue
FgMagenta
FgCyan
FgWhite
)
// Foreground Hi-Intensity colors
const (
FgHiBlack Color = iota + 90
FgHiRed
FgHiGreen
FgHiYellow
FgHiBlue
FgHiMagenta
FgHiCyan
FgHiWhite
)
// Background colors
const (
BgBlack Color = iota + 40
BgRed
BgGreen
BgYellow
BgBlue
BgMagenta
BgCyan
BgWhite
)
// Background Hi-Intensity colors
const (
BgHiBlack Color = iota + 100
BgHiRed
BgHiGreen
BgHiYellow
BgHiBlue
BgHiMagenta
BgHiCyan
BgHiWhite
)
// EscapeSeq returns the ANSI escape sequence for the color.
func (c Color) EscapeSeq() string {
return EscapeStart + strconv.Itoa(int(c)) + EscapeStop
}
// HTMLProperty returns the "class" attribute for the color.
func (c Color) HTMLProperty() string {
out := ""
if class, ok := colorCSSClassMap[c]; ok {
out = fmt.Sprintf("class=\"%s\"", class)
}
return out
}
// Sprint colorizes and prints the given string(s).
func (c Color) Sprint(a ...interface{}) string {
return colorize(fmt.Sprint(a...), c.EscapeSeq())
}
// Sprintf formats and colorizes and prints the given string(s).
func (c Color) Sprintf(format string, a ...interface{}) string {
return colorize(fmt.Sprintf(format, a...), c.EscapeSeq())
}
// Colors represents an array of Color objects to render with.
// Example: Colors{FgCyan, BgBlack}
type Colors []Color
var (
// colorsSeqMap caches the escape sequence for a set of colors
colorsSeqMap = sync.Map{}
)
// EscapeSeq returns the ANSI escape sequence for the colors set.
func (c Colors) EscapeSeq() string {
if len(c) == 0 {
return ""
}
colorsKey := fmt.Sprintf("%#v", c)
escapeSeq, ok := colorsSeqMap.Load(colorsKey)
if !ok || escapeSeq == "" {
colorNums := make([]string, len(c))
for idx, color := range c {
colorNums[idx] = strconv.Itoa(int(color))
}
escapeSeq = EscapeStart + strings.Join(colorNums, ";") + EscapeStop
colorsSeqMap.Store(colorsKey, escapeSeq)
}
return escapeSeq.(string)
}
// HTMLProperty returns the "class" attribute for the colors.
func (c Colors) HTMLProperty() string {
if len(c) == 0 {
return ""
}
var classes []string
for _, color := range c {
if class, ok := colorCSSClassMap[color]; ok {
classes = append(classes, class)
}
}
if len(classes) > 1 {
sort.Strings(classes)
}
return fmt.Sprintf("class=\"%s\"", strings.Join(classes, " "))
}
// Sprint colorizes and prints the given string(s).
func (c Colors) Sprint(a ...interface{}) string {
return colorize(fmt.Sprint(a...), c.EscapeSeq())
}
// Sprintf formats and colorizes and prints the given string(s).
func (c Colors) Sprintf(format string, a ...interface{}) string {
return colorize(fmt.Sprintf(format, a...), c.EscapeSeq())
}
func colorize(s string, escapeSeq string) string {
if !colorsEnabled || escapeSeq == "" {
return s
}
return Escape(s, escapeSeq)
}

View File

@@ -0,0 +1,48 @@
package text
var (
// colorCSSClassMap contains the equivalent CSS-class for all colors
colorCSSClassMap = map[Color]string{
Bold: "bold",
Faint: "faint",
Italic: "italic",
Underline: "underline",
BlinkSlow: "blink-slow",
BlinkRapid: "blink-rapid",
ReverseVideo: "reverse-video",
Concealed: "concealed",
CrossedOut: "crossed-out",
FgBlack: "fg-black",
FgRed: "fg-red",
FgGreen: "fg-green",
FgYellow: "fg-yellow",
FgBlue: "fg-blue",
FgMagenta: "fg-magenta",
FgCyan: "fg-cyan",
FgWhite: "fg-white",
FgHiBlack: "fg-hi-black",
FgHiRed: "fg-hi-red",
FgHiGreen: "fg-hi-green",
FgHiYellow: "fg-hi-yellow",
FgHiBlue: "fg-hi-blue",
FgHiMagenta: "fg-hi-magenta",
FgHiCyan: "fg-hi-cyan",
FgHiWhite: "fg-hi-white",
BgBlack: "bg-black",
BgRed: "bg-red",
BgGreen: "bg-green",
BgYellow: "bg-yellow",
BgBlue: "bg-blue",
BgMagenta: "bg-magenta",
BgCyan: "bg-cyan",
BgWhite: "bg-white",
BgHiBlack: "bg-hi-black",
BgHiRed: "bg-hi-red",
BgHiGreen: "bg-hi-green",
BgHiYellow: "bg-hi-yellow",
BgHiBlue: "bg-hi-blue",
BgHiMagenta: "bg-hi-magenta",
BgHiCyan: "bg-hi-cyan",
BgHiWhite: "bg-hi-white",
}
)

39
vendor/github.com/jedib0t/go-pretty/v6/text/cursor.go generated vendored Normal file
View File

@@ -0,0 +1,39 @@
package text
import (
"fmt"
)
// Cursor helps move the cursor on the console in multiple directions.
type Cursor rune
const (
// CursorDown helps move the Cursor Down X lines
CursorDown Cursor = 'B'
// CursorLeft helps move the Cursor Left X characters
CursorLeft Cursor = 'D'
// CursorRight helps move the Cursor Right X characters
CursorRight Cursor = 'C'
// CursorUp helps move the Cursor Up X lines
CursorUp Cursor = 'A'
// EraseLine helps erase all characters to the Right of the Cursor in the
// current line
EraseLine Cursor = 'K'
)
// Sprint prints the Escape Sequence to move the Cursor once.
func (c Cursor) Sprint() string {
return fmt.Sprintf("%s%c", EscapeStart, c)
}
// Sprintn prints the Escape Sequence to move the Cursor "n" times.
func (c Cursor) Sprintn(n int) string {
if c == EraseLine {
return c.Sprint()
}
return fmt.Sprintf("%s%d%c", EscapeStart, n, c)
}

12
vendor/github.com/jedib0t/go-pretty/v6/text/filter.go generated vendored Normal file
View File

@@ -0,0 +1,12 @@
package text
// Filter filters the slice 's' to items which return truth when passed to 'f'.
func Filter(s []string, f func(string) bool) []string {
var out []string
for _, item := range s {
if f(item) {
out = append(out, item)
}
}
return out
}

100
vendor/github.com/jedib0t/go-pretty/v6/text/format.go generated vendored Normal file
View File

@@ -0,0 +1,100 @@
package text
import (
"strings"
"unicode"
)
// Format lets you transform the text in supported methods while keeping escape
// sequences in the string intact and untouched.
type Format int
// Format enumerations
const (
FormatDefault Format = iota // default_Case
FormatLower // lower
FormatTitle // Title
FormatUpper // UPPER
)
// Apply converts the text as directed.
func (tc Format) Apply(text string) string {
switch tc {
case FormatLower:
return strings.ToLower(text)
case FormatTitle:
return toTitle(text)
case FormatUpper:
return toUpper(text)
default:
return text
}
}
func toTitle(text string) string {
prev, inEscSeq := ' ', false
return strings.Map(
func(r rune) rune {
if r == EscapeStartRune {
inEscSeq = true
}
if !inEscSeq {
if isSeparator(prev) {
prev = r
r = unicode.ToUpper(r)
} else {
prev = r
}
}
if inEscSeq && r == EscapeStopRune {
inEscSeq = false
}
return r
},
text,
)
}
func toUpper(text string) string {
inEscSeq := false
return strings.Map(
func(r rune) rune {
if r == EscapeStartRune {
inEscSeq = true
}
if !inEscSeq {
r = unicode.ToUpper(r)
}
if inEscSeq && r == EscapeStopRune {
inEscSeq = false
}
return r
},
text,
)
}
// isSeparator returns true if the given rune is a separator. This function is
// lifted straight out of the standard library @ strings/strings.go.
func isSeparator(r rune) bool {
// ASCII alphanumerics and underscore are not separators
if r <= 0x7F {
switch {
case '0' <= r && r <= '9':
return false
case 'a' <= r && r <= 'z':
return false
case 'A' <= r && r <= 'Z':
return false
case r == '_':
return false
}
return true
}
// Letters and digits are not separators
if unicode.IsLetter(r) || unicode.IsDigit(r) {
return false
}
// Otherwise, all we can do for now is treat spaces as separators.
return unicode.IsSpace(r)
}

203
vendor/github.com/jedib0t/go-pretty/v6/text/string.go generated vendored Normal file
View File

@@ -0,0 +1,203 @@
package text
import (
"strings"
"unicode/utf8"
"github.com/mattn/go-runewidth"
)
// Constants
const (
EscapeReset = EscapeStart + "0" + EscapeStop
EscapeStart = "\x1b["
EscapeStartRune = rune(27) // \x1b
EscapeStop = "m"
EscapeStopRune = 'm'
)
// InsertEveryN inserts the rune every N characters in the string. For ex.:
// InsertEveryN("Ghost", '-', 1) == "G-h-o-s-t"
// InsertEveryN("Ghost", '-', 2) == "Gh-os-t"
// InsertEveryN("Ghost", '-', 3) == "Gho-st"
// InsertEveryN("Ghost", '-', 4) == "Ghos-t"
// InsertEveryN("Ghost", '-', 5) == "Ghost"
func InsertEveryN(str string, runeToInsert rune, n int) string {
if n <= 0 {
return str
}
sLen := RuneCount(str)
var out strings.Builder
out.Grow(sLen + (sLen / n))
outLen, isEscSeq := 0, false
for idx, c := range str {
if c == EscapeStartRune {
isEscSeq = true
}
if !isEscSeq && outLen > 0 && (outLen%n) == 0 && idx != sLen {
out.WriteRune(runeToInsert)
}
out.WriteRune(c)
if !isEscSeq {
outLen += RuneWidth(c)
}
if isEscSeq && c == EscapeStopRune {
isEscSeq = false
}
}
return out.String()
}
// LongestLineLen returns the length of the longest "line" within the
// argument string. For ex.:
// LongestLineLen("Ghost!\nCome back here!\nRight now!") == 15
func LongestLineLen(str string) int {
maxLength, currLength, isEscSeq := 0, 0, false
for _, c := range str {
if c == EscapeStartRune {
isEscSeq = true
} else if isEscSeq && c == EscapeStopRune {
isEscSeq = false
continue
}
if c == '\n' {
if currLength > maxLength {
maxLength = currLength
}
currLength = 0
} else if !isEscSeq {
currLength += RuneWidth(c)
}
}
if currLength > maxLength {
maxLength = currLength
}
return maxLength
}
// Pad pads the given string with as many characters as needed to make it as
// long as specified (maxLen). This function does not count escape sequences
// while calculating length of the string. Ex.:
// Pad("Ghost", 0, ' ') == "Ghost"
// Pad("Ghost", 3, ' ') == "Ghost"
// Pad("Ghost", 5, ' ') == "Ghost"
// Pad("Ghost", 7, ' ') == "Ghost "
// Pad("Ghost", 10, '.') == "Ghost....."
func Pad(str string, maxLen int, paddingChar rune) string {
strLen := RuneCount(str)
if strLen < maxLen {
str += strings.Repeat(string(paddingChar), maxLen-strLen)
}
return str
}
// RepeatAndTrim repeats the given string until it is as long as maxRunes.
// For ex.:
// RepeatAndTrim("Ghost", 0) == ""
// RepeatAndTrim("Ghost", 5) == "Ghost"
// RepeatAndTrim("Ghost", 7) == "GhostGh"
// RepeatAndTrim("Ghost", 10) == "GhostGhost"
func RepeatAndTrim(str string, maxRunes int) string {
if maxRunes == 0 {
return ""
} else if maxRunes == utf8.RuneCountInString(str) {
return str
}
repeatedS := strings.Repeat(str, int(maxRunes/utf8.RuneCountInString(str))+1)
return Trim(repeatedS, maxRunes)
}
// RuneCount is similar to utf8.RuneCountInString, except for the fact that it
// ignores escape sequences while counting. For ex.:
// RuneCount("") == 0
// RuneCount("Ghost") == 5
// RuneCount("\x1b[33mGhost\x1b[0m") == 5
// RuneCount("\x1b[33mGhost\x1b[0") == 5
func RuneCount(str string) int {
count, isEscSeq := 0, false
for _, c := range str {
if c == EscapeStartRune {
isEscSeq = true
} else if isEscSeq {
if c == EscapeStopRune {
isEscSeq = false
}
} else {
count += RuneWidth(c)
}
}
return count
}
// RuneWidth returns the mostly accurate character-width of the rune. This is
// not 100% accurate as the character width is usually dependant on the
// typeface (font) used in the console/terminal. For ex.:
// RuneWidth('A') == 1
// RuneWidth('ツ') == 2
// RuneWidth('⊙') == 1
// RuneWidth('︿') == 2
// RuneWidth(0x27) == 0
func RuneWidth(r rune) int {
return runewidth.RuneWidth(r)
}
// Snip returns the given string with a fixed length. For ex.:
// Snip("Ghost", 0, "~") == "Ghost"
// Snip("Ghost", 1, "~") == "~"
// Snip("Ghost", 3, "~") == "Gh~"
// Snip("Ghost", 5, "~") == "Ghost"
// Snip("Ghost", 7, "~") == "Ghost "
// Snip("\x1b[33mGhost\x1b[0m", 7, "~") == "\x1b[33mGhost\x1b[0m "
func Snip(str string, length int, snipIndicator string) string {
if length > 0 {
lenStr := RuneCount(str)
if lenStr > length {
lenStrFinal := length - RuneCount(snipIndicator)
return Trim(str, lenStrFinal) + snipIndicator
}
}
return str
}
// Trim trims a string to the given length while ignoring escape sequences. For
// ex.:
// Trim("Ghost", 3) == "Gho"
// Trim("Ghost", 6) == "Ghost"
// Trim("\x1b[33mGhost\x1b[0m", 3) == "\x1b[33mGho\x1b[0m"
// Trim("\x1b[33mGhost\x1b[0m", 6) == "\x1b[33mGhost\x1b[0m"
func Trim(str string, maxLen int) string {
if maxLen <= 0 {
return ""
}
var out strings.Builder
out.Grow(maxLen)
outLen, isEscSeq, lastEscSeq := 0, false, strings.Builder{}
for _, sChr := range str {
out.WriteRune(sChr)
if sChr == EscapeStartRune {
isEscSeq = true
lastEscSeq.Reset()
lastEscSeq.WriteRune(sChr)
} else if isEscSeq {
lastEscSeq.WriteRune(sChr)
if sChr == EscapeStopRune {
isEscSeq = false
}
} else {
outLen++
if outLen == maxLen {
break
}
}
}
if lastEscSeq.Len() > 0 && lastEscSeq.String() != EscapeReset {
out.WriteString(EscapeReset)
}
return out.String()
}

View File

@@ -0,0 +1,201 @@
package text
import (
"bytes"
"encoding/json"
"fmt"
"strconv"
"strings"
"time"
)
// Transformer related constants
const (
unixTimeMinMilliseconds = int64(10000000000)
unixTimeMinMicroseconds = unixTimeMinMilliseconds * 1000
unixTimeMinNanoSeconds = unixTimeMinMicroseconds * 1000
)
// Transformer related variables
var (
colorsNumberPositive = Colors{FgHiGreen}
colorsNumberNegative = Colors{FgHiRed}
colorsNumberZero = Colors{}
colorsURL = Colors{Underline, FgBlue}
rfc3339Milli = "2006-01-02T15:04:05.000Z07:00"
rfc3339Micro = "2006-01-02T15:04:05.000000Z07:00"
possibleTimeLayouts = []string{
time.RFC3339,
rfc3339Milli, // strfmt.DateTime.String()'s default layout
rfc3339Micro,
time.RFC3339Nano,
}
)
// Transformer helps format the contents of an object to the user's liking.
type Transformer func(val interface{}) string
// NewNumberTransformer returns a number Transformer that:
// * transforms the number as directed by 'format' (ex.: %.2f)
// * colors negative values Red
// * colors positive values Green
func NewNumberTransformer(format string) Transformer {
return func(val interface{}) string {
if number, ok := val.(int); ok {
return transformInt(format, int64(number))
}
if number, ok := val.(int8); ok {
return transformInt(format, int64(number))
}
if number, ok := val.(int16); ok {
return transformInt(format, int64(number))
}
if number, ok := val.(int32); ok {
return transformInt(format, int64(number))
}
if number, ok := val.(int64); ok {
return transformInt(format, int64(number))
}
if number, ok := val.(uint); ok {
return transformUint(format, uint64(number))
}
if number, ok := val.(uint8); ok {
return transformUint(format, uint64(number))
}
if number, ok := val.(uint16); ok {
return transformUint(format, uint64(number))
}
if number, ok := val.(uint32); ok {
return transformUint(format, uint64(number))
}
if number, ok := val.(uint64); ok {
return transformUint(format, uint64(number))
}
if number, ok := val.(float32); ok {
return transformFloat(format, float64(number))
}
if number, ok := val.(float64); ok {
return transformFloat(format, float64(number))
}
return fmt.Sprint(val)
}
}
func transformInt(format string, val int64) string {
if val < 0 {
return colorsNumberNegative.Sprintf("-"+format, -val)
}
if val > 0 {
return colorsNumberPositive.Sprintf(format, val)
}
return colorsNumberZero.Sprintf(format, val)
}
func transformUint(format string, val uint64) string {
if val > 0 {
return colorsNumberPositive.Sprintf(format, val)
}
return colorsNumberZero.Sprintf(format, val)
}
func transformFloat(format string, val float64) string {
if val < 0 {
return colorsNumberNegative.Sprintf("-"+format, -val)
}
if val > 0 {
return colorsNumberPositive.Sprintf(format, val)
}
return colorsNumberZero.Sprintf(format, val)
}
// NewJSONTransformer returns a Transformer that can format a JSON string or an
// object into pretty-indented JSON-strings.
func NewJSONTransformer(prefix string, indent string) Transformer {
return func(val interface{}) string {
if valStr, ok := val.(string); ok {
var b bytes.Buffer
if err := json.Indent(&b, []byte(strings.TrimSpace(valStr)), prefix, indent); err == nil {
return string(b.Bytes())
}
} else if b, err := json.MarshalIndent(val, prefix, indent); err == nil {
return string(b)
}
return fmt.Sprintf("%#v", val)
}
}
// NewTimeTransformer returns a Transformer that can format a timestamp (a
// time.Time) into a well-defined time format defined using the provided layout
// (ex.: time.RFC3339).
//
// If a non-nil location value is provided, the time will be localized to that
// location (use time.Local to get localized timestamps).
func NewTimeTransformer(layout string, location *time.Location) Transformer {
return func(val interface{}) string {
formatTime := func(t time.Time) string {
rsp := ""
if t.Unix() > 0 {
if location != nil {
t = t.In(location)
}
rsp = t.Format(layout)
}
return rsp
}
rsp := fmt.Sprint(val)
if valTime, ok := val.(time.Time); ok {
rsp = formatTime(valTime)
} else {
// cycle through some supported layouts to see if the string form
// of the object matches any of these layouts
for _, possibleTimeLayout := range possibleTimeLayouts {
if valTime, err := time.Parse(possibleTimeLayout, rsp); err == nil {
rsp = formatTime(valTime)
break
}
}
}
return rsp
}
}
// NewUnixTimeTransformer returns a Transformer that can format a unix-timestamp
// into a well-defined time format as defined by 'layout'. This can handle
// unix-time in Seconds, MilliSeconds, Microseconds and Nanoseconds.
//
// If a non-nil location value is provided, the time will be localized to that
// location (use time.Local to get localized timestamps).
func NewUnixTimeTransformer(layout string, location *time.Location) Transformer {
timeTransformer := NewTimeTransformer(layout, location)
formatUnixTime := func(unixTime int64) string {
if unixTime >= unixTimeMinNanoSeconds {
unixTime = unixTime / time.Second.Nanoseconds()
} else if unixTime >= unixTimeMinMicroseconds {
unixTime = unixTime / (time.Second.Nanoseconds() / 1000)
} else if unixTime >= unixTimeMinMilliseconds {
unixTime = unixTime / (time.Second.Nanoseconds() / 1000000)
}
return timeTransformer(time.Unix(unixTime, 0))
}
return func(val interface{}) string {
if unixTime, ok := val.(int64); ok {
return formatUnixTime(unixTime)
} else if unixTimeStr, ok := val.(string); ok {
if unixTime, err := strconv.ParseInt(unixTimeStr, 10, 64); err == nil {
return formatUnixTime(unixTime)
}
}
return fmt.Sprint(val)
}
}
// NewURLTransformer returns a Transformer that can format and pretty print a string
// that contains an URL (the text is underlined and colored Blue).
func NewURLTransformer() Transformer {
return func(val interface{}) string {
return colorsURL.Sprint(val)
}
}

67
vendor/github.com/jedib0t/go-pretty/v6/text/valign.go generated vendored Normal file
View File

@@ -0,0 +1,67 @@
package text
import "strings"
// VAlign denotes how text is to be aligned vertically.
type VAlign int
// VAlign enumerations
const (
VAlignDefault VAlign = iota // same as VAlignTop
VAlignTop // "top\n\n"
VAlignMiddle // "\nmiddle\n"
VAlignBottom // "\n\nbottom"
)
// Apply aligns the lines vertically. For ex.:
// * VAlignTop.Apply({"Game", "Of", "Thrones"}, 5)
// returns {"Game", "Of", "Thrones", "", ""}
// * VAlignMiddle.Apply({"Game", "Of", "Thrones"}, 5)
// returns {"", "Game", "Of", "Thrones", ""}
// * VAlignBottom.Apply({"Game", "Of", "Thrones"}, 5)
// returns {"", "", "Game", "Of", "Thrones"}
func (va VAlign) Apply(lines []string, maxLines int) []string {
if len(lines) == maxLines {
return lines
} else if len(lines) > maxLines {
maxLines = len(lines)
}
insertIdx := 0
if va == VAlignMiddle {
insertIdx = int(maxLines-len(lines)) / 2
} else if va == VAlignBottom {
insertIdx = maxLines - len(lines)
}
linesOut := strings.Split(strings.Repeat("\n", maxLines-1), "\n")
for idx, line := range lines {
linesOut[idx+insertIdx] = line
}
return linesOut
}
// ApplyStr aligns the string (of 1 or more lines) vertically. For ex.:
// * VAlignTop.ApplyStr("Game\nOf\nThrones", 5)
// returns {"Game", "Of", "Thrones", "", ""}
// * VAlignMiddle.ApplyStr("Game\nOf\nThrones", 5)
// returns {"", "Game", "Of", "Thrones", ""}
// * VAlignBottom.ApplyStr("Game\nOf\nThrones", 5)
// returns {"", "", "Game", "Of", "Thrones"}
func (va VAlign) ApplyStr(text string, maxLines int) []string {
return va.Apply(strings.Split(text, "\n"), maxLines)
}
// HTMLProperty returns the equivalent HTML vertical-align tag property.
func (va VAlign) HTMLProperty() string {
switch va {
case VAlignTop:
return "valign=\"top\""
case VAlignMiddle:
return "valign=\"middle\""
case VAlignBottom:
return "valign=\"bottom\""
default:
return ""
}
}

256
vendor/github.com/jedib0t/go-pretty/v6/text/wrap.go generated vendored Normal file
View File

@@ -0,0 +1,256 @@
package text
import (
"strings"
"unicode/utf8"
)
// WrapHard wraps a string to the given length using a newline. Handles strings
// with ANSI escape sequences (such as text color) without breaking the text
// formatting. Breaks all words that go beyond the line boundary.
//
// For examples, refer to the unit-tests or GoDoc examples.
func WrapHard(str string, wrapLen int) string {
if wrapLen <= 0 {
return ""
}
str = strings.Replace(str, "\t", " ", -1)
sLen := utf8.RuneCountInString(str)
if sLen <= wrapLen {
return str
}
out := &strings.Builder{}
out.Grow(sLen + (sLen / wrapLen))
for idx, paragraph := range strings.Split(str, "\n\n") {
if idx > 0 {
out.WriteString("\n\n")
}
wrapHard(paragraph, wrapLen, out)
}
return out.String()
}
// WrapSoft wraps a string to the given length using a newline. Handles strings
// with ANSI escape sequences (such as text color) without breaking the text
// formatting. Tries to move words that go beyond the line boundary to the next
// line.
//
// For examples, refer to the unit-tests or GoDoc examples.
func WrapSoft(str string, wrapLen int) string {
if wrapLen <= 0 {
return ""
}
str = strings.Replace(str, "\t", " ", -1)
sLen := utf8.RuneCountInString(str)
if sLen <= wrapLen {
return str
}
out := &strings.Builder{}
out.Grow(sLen + (sLen / wrapLen))
for idx, paragraph := range strings.Split(str, "\n\n") {
if idx > 0 {
out.WriteString("\n\n")
}
wrapSoft(paragraph, wrapLen, out)
}
return out.String()
}
// WrapText is very similar to WrapHard except for one minor difference. Unlike
// WrapHard which discards line-breaks and respects only paragraph-breaks, this
// function respects line-breaks too.
//
// For examples, refer to the unit-tests or GoDoc examples.
func WrapText(str string, wrapLen int) string {
if wrapLen <= 0 {
return ""
}
var out strings.Builder
sLen := utf8.RuneCountInString(str)
out.Grow(sLen + (sLen / wrapLen))
lineIdx, isEscSeq, lastEscSeq := 0, false, ""
for _, char := range str {
if char == EscapeStartRune {
isEscSeq = true
lastEscSeq = ""
}
if isEscSeq {
lastEscSeq += string(char)
}
appendChar(char, wrapLen, &lineIdx, isEscSeq, lastEscSeq, &out)
if isEscSeq && char == EscapeStopRune {
isEscSeq = false
}
if lastEscSeq == EscapeReset {
lastEscSeq = ""
}
}
if lastEscSeq != "" && lastEscSeq != EscapeReset {
out.WriteString(EscapeReset)
}
return out.String()
}
func appendChar(char rune, wrapLen int, lineLen *int, inEscSeq bool, lastSeenEscSeq string, out *strings.Builder) {
// handle reaching the end of the line as dictated by wrapLen or by finding
// a newline character
if (*lineLen == wrapLen && !inEscSeq && char != '\n') || (char == '\n') {
if lastSeenEscSeq != "" {
// terminate escape sequence and the line; and restart the escape
// sequence in the next line
out.WriteString(EscapeReset)
out.WriteRune('\n')
out.WriteString(lastSeenEscSeq)
} else {
// just start a new line
out.WriteRune('\n')
}
// reset line index to 0th character
*lineLen = 0
}
// if the rune is not a new line, output it
if char != '\n' {
out.WriteRune(char)
// increment the line index if not in the middle of an escape sequence
if !inEscSeq {
*lineLen++
}
}
}
func appendWord(word string, lineIdx *int, lastSeenEscSeq string, wrapLen int, out *strings.Builder) {
inEscSeq := false
for _, char := range word {
if char == EscapeStartRune {
inEscSeq = true
lastSeenEscSeq = ""
}
if inEscSeq {
lastSeenEscSeq += string(char)
}
appendChar(char, wrapLen, lineIdx, inEscSeq, lastSeenEscSeq, out)
if inEscSeq && char == EscapeStopRune {
inEscSeq = false
}
if lastSeenEscSeq == EscapeReset {
lastSeenEscSeq = ""
}
}
}
func extractOpenEscapeSeq(str string) string {
escapeSeq, inEscSeq := "", false
for _, char := range str {
if char == EscapeStartRune {
inEscSeq = true
escapeSeq = ""
}
if inEscSeq {
escapeSeq += string(char)
}
if char == EscapeStopRune {
inEscSeq = false
}
}
if escapeSeq == EscapeReset {
escapeSeq = ""
}
return escapeSeq
}
func terminateLine(wrapLen int, lineLen *int, lastSeenEscSeq string, out *strings.Builder) {
if *lineLen < wrapLen {
out.WriteString(strings.Repeat(" ", wrapLen-*lineLen))
}
// something is already on the line; terminate it
if lastSeenEscSeq != "" {
out.WriteString(EscapeReset)
}
out.WriteRune('\n')
out.WriteString(lastSeenEscSeq)
*lineLen = 0
}
func terminateOutput(lastSeenEscSeq string, out *strings.Builder) {
if lastSeenEscSeq != "" && lastSeenEscSeq != EscapeReset && !strings.HasSuffix(out.String(), EscapeReset) {
out.WriteString(EscapeReset)
}
}
func wrapHard(paragraph string, wrapLen int, out *strings.Builder) {
lineLen, lastSeenEscSeq := 0, ""
words := strings.Fields(paragraph)
for wordIdx, word := range words {
escSeq := extractOpenEscapeSeq(word)
if escSeq != "" {
lastSeenEscSeq = escSeq
}
if lineLen > 0 {
out.WriteRune(' ')
lineLen++
}
wordLen := RuneCount(word)
if lineLen+wordLen <= wrapLen { // word fits within the line
out.WriteString(word)
lineLen += wordLen
} else { // word doesn't fit within the line; hard-wrap
appendWord(word, &lineLen, lastSeenEscSeq, wrapLen, out)
}
// end of line; but more words incoming
if lineLen == wrapLen && wordIdx < len(words)-1 {
terminateLine(wrapLen, &lineLen, lastSeenEscSeq, out)
}
}
terminateOutput(lastSeenEscSeq, out)
}
func wrapSoft(paragraph string, wrapLen int, out *strings.Builder) {
lineLen, lastSeenEscSeq := 0, ""
words := strings.Fields(paragraph)
for wordIdx, word := range words {
escSeq := extractOpenEscapeSeq(word)
if escSeq != "" {
lastSeenEscSeq = escSeq
}
spacing, spacingLen := "", 0
if lineLen > 0 {
spacing, spacingLen = " ", 1
}
wordLen := RuneCount(word)
if lineLen+spacingLen+wordLen <= wrapLen { // word fits within the line
out.WriteString(spacing)
out.WriteString(word)
lineLen += spacingLen + wordLen
} else { // word doesn't fit within the line
if lineLen > 0 { // something is already on the line; terminate it
terminateLine(wrapLen, &lineLen, lastSeenEscSeq, out)
}
if wordLen <= wrapLen { // word fits within a single line
out.WriteString(word)
lineLen = wordLen
} else { // word doesn't fit within a single line; hard-wrap
appendWord(word, &lineLen, lastSeenEscSeq, wrapLen, out)
}
}
// end of line; but more words incoming
if lineLen == wrapLen && wordIdx < len(words)-1 {
terminateLine(wrapLen, &lineLen, lastSeenEscSeq, out)
}
}
terminateOutput(lastSeenEscSeq, out)
}