mirror of
https://github.com/mudler/luet.git
synced 2025-09-08 18:49:39 +00:00
Update vendor
This commit is contained in:
21
vendor/github.com/jedib0t/go-pretty/LICENSE
generated
vendored
Normal file
21
vendor/github.com/jedib0t/go-pretty/LICENSE
generated
vendored
Normal 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
354
vendor/github.com/jedib0t/go-pretty/table/README.md
generated
vendored
Normal file
@@ -0,0 +1,354 @@
|
||||
# Table
|
||||
[](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> </th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="right">1</td>
|
||||
<td>Arya</td>
|
||||
<td>Stark</td>
|
||||
<td align="right">3000</td>
|
||||
<td> </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> </td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td align="right"> </td>
|
||||
<td> </td>
|
||||
<td>Total</td>
|
||||
<td align="right">10000</td>
|
||||
<td> </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
52
vendor/github.com/jedib0t/go-pretty/table/config.go
generated
vendored
Normal 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
385
vendor/github.com/jedib0t/go-pretty/table/render.go
generated
vendored
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
69
vendor/github.com/jedib0t/go-pretty/table/render_csv.go
generated
vendored
Normal file
69
vendor/github.com/jedib0t/go-pretty/table/render_csv.go
generated
vendored
Normal 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)
|
||||
}
|
||||
}
|
156
vendor/github.com/jedib0t/go-pretty/table/render_html.go
generated
vendored
Normal file
156
vendor/github.com/jedib0t/go-pretty/table/render_html.go
generated
vendored
Normal 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> </th>
|
||||
// </tr>
|
||||
// </thead>
|
||||
// <tbody>
|
||||
// <tr>
|
||||
// <td align="right">1</td>
|
||||
// <td>Arya</td>
|
||||
// <td>Stark</td>
|
||||
// <td align="right">3000</td>
|
||||
// <td> </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> </td>
|
||||
// </tr>
|
||||
// </tbody>
|
||||
// <tfoot>
|
||||
// <tr>
|
||||
// <td align="right"> </td>
|
||||
// <td> </td>
|
||||
// <td>Total</td>
|
||||
// <td align="right">10000</td>
|
||||
// <td> </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(" ")
|
||||
}
|
||||
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")
|
||||
}
|
||||
}
|
||||
}
|
75
vendor/github.com/jedib0t/go-pretty/table/render_markdown.go
generated
vendored
Normal file
75
vendor/github.com/jedib0t/go-pretty/table/render_markdown.go
generated
vendored
Normal 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
119
vendor/github.com/jedib0t/go-pretty/table/sort.go
generated
vendored
Normal 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
828
vendor/github.com/jedib0t/go-pretty/table/style.go
generated
vendored
Normal 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
687
vendor/github.com/jedib0t/go-pretty/table/table.go
generated
vendored
Normal 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
27
vendor/github.com/jedib0t/go-pretty/table/util.go
generated
vendored
Normal 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
59
vendor/github.com/jedib0t/go-pretty/table/writer.go
generated
vendored
Normal 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
137
vendor/github.com/jedib0t/go-pretty/text/align.go
generated
vendored
Normal 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
55
vendor/github.com/jedib0t/go-pretty/text/ansi.go
generated
vendored
Normal 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()
|
||||
}
|
7
vendor/github.com/jedib0t/go-pretty/text/ansi_unix.go
generated
vendored
Normal file
7
vendor/github.com/jedib0t/go-pretty/text/ansi_unix.go
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
// +build !windows
|
||||
|
||||
package text
|
||||
|
||||
func areANSICodesSupported() bool {
|
||||
return true
|
||||
}
|
31
vendor/github.com/jedib0t/go-pretty/text/ansi_windows.go
generated
vendored
Normal file
31
vendor/github.com/jedib0t/go-pretty/text/ansi_windows.go
generated
vendored
Normal 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
183
vendor/github.com/jedib0t/go-pretty/text/color.go
generated
vendored
Normal 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
48
vendor/github.com/jedib0t/go-pretty/text/color_html.go
generated
vendored
Normal 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
39
vendor/github.com/jedib0t/go-pretty/text/cursor.go
generated
vendored
Normal 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
12
vendor/github.com/jedib0t/go-pretty/text/filter.go
generated
vendored
Normal 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
28
vendor/github.com/jedib0t/go-pretty/text/format.go
generated
vendored
Normal 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
203
vendor/github.com/jedib0t/go-pretty/text/string.go
generated
vendored
Normal 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
191
vendor/github.com/jedib0t/go-pretty/text/transformer.go
generated
vendored
Normal 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
67
vendor/github.com/jedib0t/go-pretty/text/valign.go
generated
vendored
Normal 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
256
vendor/github.com/jedib0t/go-pretty/text/wrap.go
generated
vendored
Normal 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
21
vendor/github.com/jedib0t/go-pretty/v6/LICENSE
generated
vendored
Normal 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
30
vendor/github.com/jedib0t/go-pretty/v6/list/README.md
generated
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
## List
|
||||
[](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
174
vendor/github.com/jedib0t/go-pretty/v6/list/list.go
generated
vendored
Normal 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
110
vendor/github.com/jedib0t/go-pretty/v6/list/render.go
generated
vendored
Normal 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)))
|
||||
}
|
||||
}
|
||||
}
|
70
vendor/github.com/jedib0t/go-pretty/v6/list/render_html.go
generated
vendored
Normal file
70
vendor/github.com/jedib0t/go-pretty/v6/list/render_html.go
generated
vendored
Normal 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
|
||||
}
|
29
vendor/github.com/jedib0t/go-pretty/v6/list/render_markdown.go
generated
vendored
Normal file
29
vendor/github.com/jedib0t/go-pretty/v6/list/render_markdown.go
generated
vendored
Normal 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
295
vendor/github.com/jedib0t/go-pretty/v6/list/style.go
generated
vendored
Normal 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
25
vendor/github.com/jedib0t/go-pretty/v6/list/writer.go
generated
vendored
Normal 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
137
vendor/github.com/jedib0t/go-pretty/v6/text/align.go
generated
vendored
Normal 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
55
vendor/github.com/jedib0t/go-pretty/v6/text/ansi.go
generated
vendored
Normal 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()
|
||||
}
|
7
vendor/github.com/jedib0t/go-pretty/v6/text/ansi_unix.go
generated
vendored
Normal file
7
vendor/github.com/jedib0t/go-pretty/v6/text/ansi_unix.go
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
// +build !windows
|
||||
|
||||
package text
|
||||
|
||||
func areANSICodesSupported() bool {
|
||||
return true
|
||||
}
|
31
vendor/github.com/jedib0t/go-pretty/v6/text/ansi_windows.go
generated
vendored
Normal file
31
vendor/github.com/jedib0t/go-pretty/v6/text/ansi_windows.go
generated
vendored
Normal 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
183
vendor/github.com/jedib0t/go-pretty/v6/text/color.go
generated
vendored
Normal 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/v6/text/color_html.go
generated
vendored
Normal file
48
vendor/github.com/jedib0t/go-pretty/v6/text/color_html.go
generated
vendored
Normal 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
39
vendor/github.com/jedib0t/go-pretty/v6/text/cursor.go
generated
vendored
Normal 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
12
vendor/github.com/jedib0t/go-pretty/v6/text/filter.go
generated
vendored
Normal 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
100
vendor/github.com/jedib0t/go-pretty/v6/text/format.go
generated
vendored
Normal 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
203
vendor/github.com/jedib0t/go-pretty/v6/text/string.go
generated
vendored
Normal 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()
|
||||
}
|
201
vendor/github.com/jedib0t/go-pretty/v6/text/transformer.go
generated
vendored
Normal file
201
vendor/github.com/jedib0t/go-pretty/v6/text/transformer.go
generated
vendored
Normal 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
67
vendor/github.com/jedib0t/go-pretty/v6/text/valign.go
generated
vendored
Normal 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
256
vendor/github.com/jedib0t/go-pretty/v6/text/wrap.go
generated
vendored
Normal 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)
|
||||
}
|
Reference in New Issue
Block a user