mirror of
https://github.com/rocky-linux/peridot.git
synced 2024-11-23 05:31:30 +00:00
feat(peridot-cli/task-info): fetch and display task details
given a task ID, fetch its details and display them to a table or to json with `-o json`. Table view also adds a calculated task duration and can optionally include the submitter information as well as a link to logs for the task. * --no-color - to skip colorizing output * --L|--logs - include column with link to logs * --submitter - show submitter * --no-wait - control whether to wait until a task completes to output (table mode)
This commit is contained in:
parent
0d3255ccca
commit
e7b15b3cde
2
go.mod
2
go.mod
@ -117,6 +117,7 @@ require (
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/olekukonko/tablewriter v0.0.5 // indirect
|
||||
github.com/pborman/uuid v1.2.1 // indirect
|
||||
github.com/pelletier/go-toml v1.8.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
@ -184,4 +185,5 @@ replace (
|
||||
peridot.resf.org/peridot/pb => ./bazel-bin/peridot/proto/v1/peridotpb_go_proto_/peridot.resf.org/peridot/pb
|
||||
peridot.resf.org/peridot/yumrepofs/pb => ./bazel-bin/peridot/proto/v1/yumrepofs/yumrepofspb_go_proto_/peridot.resf.org/peridot/yumrepofs/pb
|
||||
)
|
||||
|
||||
// sync-replace-end
|
||||
|
2
go.sum
2
go.sum
@ -452,6 +452,8 @@ github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRW
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
||||
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
|
||||
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
||||
github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4=
|
||||
github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o=
|
||||
github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg=
|
||||
|
@ -23,6 +23,7 @@ go_library(
|
||||
"project_list.go",
|
||||
"task.go",
|
||||
"task_logs.go",
|
||||
"task_info.go",
|
||||
"utils.go",
|
||||
],
|
||||
data = [
|
||||
@ -39,6 +40,7 @@ go_library(
|
||||
"//vendor/github.com/spf13/cobra",
|
||||
"//vendor/github.com/spf13/viper",
|
||||
"//vendor/openapi.peridot.resf.org/peridotopenapi",
|
||||
"//vendor/github.com/olekukonko/tablewriter",
|
||||
"@org_golang_x_oauth2//:oauth2",
|
||||
"@org_golang_x_oauth2//clientcredentials",
|
||||
],
|
||||
@ -67,7 +69,7 @@ pkg_rpm(
|
||||
architecture = "x86_64",
|
||||
description = "A command line interface to interact with the Peridot build system",
|
||||
license = "MIT",
|
||||
release = "1",
|
||||
release = "2",
|
||||
source_date_epoch = 0,
|
||||
summary = "Peridot Command Line Interface",
|
||||
version = "0.2.3",
|
||||
|
@ -54,7 +54,10 @@ func init() {
|
||||
root.PersistentFlags().String("client-secret", "", "Client secret for authentication")
|
||||
root.PersistentFlags().String("project-id", "", "Peridot project ID")
|
||||
root.PersistentFlags().Bool("debug", false, "Debug mode")
|
||||
|
||||
root.PersistentFlags().StringP("output", "o", "table", "Output format (table|json)")
|
||||
root.PersistentFlags().Bool("no-color", false, "don't colorize output")
|
||||
root.PersistentFlags().Bool("no-wait", false, "don't wait for completion")
|
||||
|
||||
root.AddCommand(lookaside)
|
||||
lookaside.AddCommand(lookasideUpload)
|
||||
@ -65,6 +68,7 @@ func init() {
|
||||
|
||||
root.AddCommand(task)
|
||||
task.AddCommand(taskLogs)
|
||||
task.AddCommand(taskInfo)
|
||||
|
||||
root.AddCommand(project)
|
||||
project.AddCommand(projectInfo)
|
||||
@ -125,3 +129,11 @@ func debug() bool {
|
||||
func output() string {
|
||||
return viper.GetString("output")
|
||||
}
|
||||
|
||||
func color() bool {
|
||||
return !viper.GetBool("no-color")
|
||||
}
|
||||
|
||||
func wait() bool {
|
||||
return !viper.GetBool("no-wait")
|
||||
}
|
||||
|
310
peridot/cmd/v1/peridot/task_info.go
Normal file
310
peridot/cmd/v1/peridot/task_info.go
Normal file
@ -0,0 +1,310 @@
|
||||
// Copyright (c) All respective contributors to the Peridot Project. All rights reserved.
|
||||
// Copyright (c) 2021-2022 Rocky Enterprise Software Foundation, Inc. All rights reserved.
|
||||
// Copyright (c) 2021-2022 Ctrl IQ, Inc. All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// 3. Neither the name of the copyright holder nor the names of its contributors
|
||||
// may be used to endorse or promote products derived from this software without
|
||||
// specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
// POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/olekukonko/tablewriter"
|
||||
"github.com/spf13/cobra"
|
||||
"openapi.peridot.resf.org/peridotopenapi"
|
||||
)
|
||||
|
||||
var taskInfo = &cobra.Command{
|
||||
Use: "info [name-or-buildId]",
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: taskInfoMn,
|
||||
}
|
||||
|
||||
var (
|
||||
showLogLink bool
|
||||
showSubmitterInfo bool
|
||||
showDuration bool
|
||||
)
|
||||
|
||||
func init() {
|
||||
taskInfo.Flags().BoolVar(&succeeded, "succeeded", true, "only query successful tasks")
|
||||
taskInfo.Flags().BoolVar(&cancelled, "cancelled", false, "only query cancelled tasks")
|
||||
taskInfo.Flags().BoolVar(&failed, "failed", false, "only query failed tasks")
|
||||
taskInfo.MarkFlagsMutuallyExclusive("cancelled", "failed", "succeeded")
|
||||
|
||||
taskInfo.Flags().BoolVarP(&showLogLink, "logs", "L", false, "include log link in output (table format only)")
|
||||
taskInfo.Flags().BoolVar(&showSubmitterInfo, "submitter", false, "include submitter details (table format only)")
|
||||
taskInfo.Flags().BoolVar(&showDuration, "duration", true, "include duration from start to stop (table format only)")
|
||||
}
|
||||
|
||||
func getNextColor(colors tablewriter.Colors) tablewriter.Colors {
|
||||
bgColor := getNextBackgroundColor(colors[0])
|
||||
fgColor := colors[1]
|
||||
if bgColor == -1 {
|
||||
fgColor = getNextForegroundColor(0)
|
||||
bgColor = getNextBackgroundColor(0)
|
||||
}
|
||||
return tablewriter.Colors{bgColor, fgColor}
|
||||
}
|
||||
|
||||
func getNextForegroundColor(color int) int {
|
||||
switch color {
|
||||
case 0:
|
||||
return tablewriter.FgGreenColor
|
||||
case tablewriter.FgCyanColor:
|
||||
return tablewriter.FgHiGreenColor
|
||||
case tablewriter.FgHiCyanColor:
|
||||
return tablewriter.FgGreenColor
|
||||
default:
|
||||
color++
|
||||
return color
|
||||
}
|
||||
}
|
||||
|
||||
func getNextBackgroundColor(color int) int {
|
||||
switch color {
|
||||
case 0:
|
||||
return tablewriter.BgRedColor
|
||||
case tablewriter.BgCyanColor:
|
||||
return tablewriter.BgHiRedColor
|
||||
case tablewriter.BgHiCyanColor:
|
||||
return -1
|
||||
default:
|
||||
color++
|
||||
return color
|
||||
}
|
||||
}
|
||||
|
||||
func buildHeaderAndAutoMergeCells() ([]string, []int) {
|
||||
header := []string{"ptid", "tid", "status", "type", "arch", "created", "finished"}
|
||||
mergableNames := []string{"ptid", "type", "arch"}
|
||||
var autoMergeCells []int
|
||||
|
||||
// Conditional appending to header
|
||||
if showDuration {
|
||||
header = append(header, "duration")
|
||||
mergableNames = append(mergableNames, "duration")
|
||||
}
|
||||
if showSubmitterInfo {
|
||||
header = append(header, "submitter")
|
||||
mergableNames = append(mergableNames, "submitter")
|
||||
}
|
||||
if showLogLink {
|
||||
header = append(header, "logs")
|
||||
}
|
||||
|
||||
// Determine dynamic indices for auto-merge cells
|
||||
for _, itemName := range mergableNames {
|
||||
index := slices.Index(header, itemName)
|
||||
if index != -1 {
|
||||
autoMergeCells = append(autoMergeCells, index)
|
||||
}
|
||||
}
|
||||
|
||||
return header, autoMergeCells
|
||||
}
|
||||
|
||||
func convertSubTaskSliceToCSV(task peridotopenapi.V1AsyncTask) {
|
||||
subtasks, ok := task.GetSubtasksOk()
|
||||
if !ok {
|
||||
errFatal(fmt.Errorf("error getting subtasks: %v", ok))
|
||||
}
|
||||
|
||||
var parentTask = (*subtasks)[0]
|
||||
|
||||
var table = tablewriter.NewWriter(os.Stdout)
|
||||
// var data [][]string
|
||||
|
||||
var header, autoMergeCells = buildHeaderAndAutoMergeCells()
|
||||
|
||||
var lastColor = tablewriter.Colors{0, tablewriter.FgWhiteColor}
|
||||
var seenTasksColors = make(map[string]tablewriter.Colors)
|
||||
|
||||
var parentTaskIds []string // cache parentTaskIds for colorizing
|
||||
|
||||
// precache all the subtask's parent tasks so we know if we should color them
|
||||
for _, subtask := range *subtasks {
|
||||
parentTaskIds = append(parentTaskIds, subtask.GetParentTaskId())
|
||||
}
|
||||
|
||||
for _, subtask := range *subtasks {
|
||||
json, err := subtask.MarshalJSON()
|
||||
if err != nil {
|
||||
errFatal(err)
|
||||
}
|
||||
|
||||
if debug() {
|
||||
err = PrettyPrintJSON(json)
|
||||
if err != nil {
|
||||
errFatal(err)
|
||||
}
|
||||
// taskResponse, _ := subtask.GetResponse().MarshalJSON()
|
||||
// taskMetadata, _ := subtask.GetMetadata().MarshalJSON()
|
||||
}
|
||||
|
||||
subtaskId := subtask.GetId()
|
||||
subtaskParentTaskId := subtask.GetParentTaskId()
|
||||
createdAt := subtask.GetCreatedAt()
|
||||
finishedAt := subtask.GetFinishedAt()
|
||||
|
||||
row := []string{
|
||||
subtaskParentTaskId,
|
||||
subtaskId,
|
||||
string(subtask.GetStatus()),
|
||||
string(subtask.GetType()),
|
||||
subtask.GetArch(),
|
||||
formatTime(createdAt),
|
||||
formatTime(finishedAt),
|
||||
}
|
||||
|
||||
if showDuration {
|
||||
row = append(row, formatDuration(createdAt, finishedAt))
|
||||
}
|
||||
|
||||
if showSubmitterInfo {
|
||||
effectiveSubmitter := fmt.Sprintf("%s <%s>", parentTask.GetSubmitterId(), parentTask.GetSubmitterEmail())
|
||||
row = append(row, effectiveSubmitter)
|
||||
}
|
||||
|
||||
if showLogLink {
|
||||
row = append(row, getLogLink(subtaskId))
|
||||
}
|
||||
|
||||
if !color() {
|
||||
table.Append(row)
|
||||
continue
|
||||
}
|
||||
|
||||
nextColor := tablewriter.Colors{tablewriter.BgBlackColor, tablewriter.FgWhiteColor}
|
||||
needsColor := hasAny(parentTaskIds, subtaskId)
|
||||
if _, seen := seenTasksColors[subtaskId]; !seen && needsColor {
|
||||
debugP("before: lastcolor: %v next: %v", lastColor, nextColor)
|
||||
nextColor = getNextColor(lastColor)
|
||||
debugP("after: lastcolor: %v next: %v", lastColor, nextColor)
|
||||
lastColor = nextColor
|
||||
seenTasksColors[subtaskId] = nextColor
|
||||
}
|
||||
|
||||
tidColors := nextColor
|
||||
|
||||
ptidColors := tablewriter.Colors{tablewriter.FgWhiteColor, tablewriter.BgBlackColor}
|
||||
if seenColor, seen := seenTasksColors[subtaskParentTaskId]; seen {
|
||||
ptidColors = seenColor
|
||||
}
|
||||
|
||||
var colors = make([]tablewriter.Colors, len(row))
|
||||
|
||||
debugP("tidcolor: %v ptidcolor %d", tidColors, ptidColors)
|
||||
for i, v := range header {
|
||||
switch v {
|
||||
case "ptid":
|
||||
colors[i] = ptidColors
|
||||
case "tid":
|
||||
colors[i] = tidColors
|
||||
default:
|
||||
colors[i] = tablewriter.Colors{}
|
||||
}
|
||||
}
|
||||
|
||||
table.Rich(row, colors)
|
||||
}
|
||||
|
||||
table.SetHeader(header)
|
||||
table.SetAutoMergeCellsByColumnIndex(autoMergeCells)
|
||||
table.SetRowLine(true)
|
||||
table.Render()
|
||||
|
||||
}
|
||||
|
||||
func debugP(s string, args ...any) {
|
||||
if debug() {
|
||||
log.Printf(s, args...)
|
||||
}
|
||||
}
|
||||
|
||||
func hasAny(slice []string, target string) bool {
|
||||
if idx := slices.Index(slice, target); idx >= 0 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func taskInfoMn(_ *cobra.Command, args []string) {
|
||||
// Ensure project id exists
|
||||
projectId := mustGetProjectID()
|
||||
|
||||
taskId := args[0]
|
||||
|
||||
err := uuid.Validate(taskId)
|
||||
if err != nil {
|
||||
errFatal(errors.New("invalid task id"))
|
||||
}
|
||||
|
||||
taskCl := getClient(serviceTask).(peridotopenapi.TaskServiceApi)
|
||||
log.Printf("Searching for task %s in project %s\n", taskId, projectId)
|
||||
|
||||
var waiting = false
|
||||
for {
|
||||
res, _, err := taskCl.GetTask(getContext(), projectId, taskId).Execute()
|
||||
if err != nil {
|
||||
errFatal(fmt.Errorf("error getting task: %s", err.Error()))
|
||||
}
|
||||
|
||||
task := res.GetTask()
|
||||
|
||||
switch output() {
|
||||
case "table":
|
||||
if !waiting || task.GetDone() {
|
||||
convertSubTaskSliceToCSV(task)
|
||||
}
|
||||
if wait() && !task.GetDone() {
|
||||
waiting = true
|
||||
log.Printf("Waiting for task %s to complete", task.GetTaskId())
|
||||
time.Sleep(5 * time.Second)
|
||||
continue
|
||||
}
|
||||
|
||||
case "json":
|
||||
taskJSON, err := res.MarshalJSON()
|
||||
if err != nil {
|
||||
errFatal(err)
|
||||
}
|
||||
|
||||
err = PrettyPrintJSON(taskJSON)
|
||||
if err != nil {
|
||||
errFatal(err)
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
@ -39,6 +39,8 @@ import (
|
||||
"log"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
"golang.org/x/oauth2/clientcredentials"
|
||||
@ -208,3 +210,18 @@ func PrettyPrintJSON(data []byte) error {
|
||||
fmt.Println(string(formattedJSON))
|
||||
return nil
|
||||
}
|
||||
|
||||
func formatTime(t time.Time) string {
|
||||
return t.Format("2006-01-02 15:04:05")
|
||||
}
|
||||
|
||||
func formatDuration(start, end time.Time) string {
|
||||
duration := end.Sub(start)
|
||||
return time.Time{}.Add(duration).Format("15:04:05")
|
||||
}
|
||||
|
||||
func getLogLink(subtaskId string) string {
|
||||
return fmt.Sprintf("https://%s/api/v1/projects/%s/tasks/%s/logs",
|
||||
strings.Replace(endpoint(), "-api", "", 1),
|
||||
mustGetProjectID(), subtaskId)
|
||||
}
|
||||
|
@ -1068,6 +1068,12 @@ def go_repositories():
|
||||
sum = "h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=",
|
||||
version = "v1.3.1",
|
||||
)
|
||||
go_repository(
|
||||
name = "com_github_olekukonko_tablewriter",
|
||||
importpath = "github.com/olekukonko/tablewriter",
|
||||
sum = "h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=",
|
||||
version = "v0.0.5",
|
||||
)
|
||||
go_repository(
|
||||
name = "com_github_oneofone_xxhash",
|
||||
importpath = "github.com/OneOfOne/xxhash",
|
||||
|
27
vendor/github.com/olekukonko/tablewriter/BUILD.bazel
generated
vendored
Normal file
27
vendor/github.com/olekukonko/tablewriter/BUILD.bazel
generated
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
|
||||
|
||||
go_library(
|
||||
name = "tablewriter",
|
||||
srcs = [
|
||||
"csv.go",
|
||||
"table.go",
|
||||
"table_with_color.go",
|
||||
"util.go",
|
||||
"wrap.go",
|
||||
],
|
||||
importmap = "peridot.resf.org/vendor/github.com/olekukonko/tablewriter",
|
||||
importpath = "github.com/olekukonko/tablewriter",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = ["//vendor/github.com/mattn/go-runewidth"],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "tablewriter_test",
|
||||
srcs = [
|
||||
"table_test.go",
|
||||
"wrap_test.go",
|
||||
],
|
||||
data = glob(["testdata/**"]),
|
||||
embed = [":tablewriter"],
|
||||
deps = ["//vendor/github.com/mattn/go-runewidth"],
|
||||
)
|
19
vendor/github.com/olekukonko/tablewriter/LICENSE.md
generated
vendored
Normal file
19
vendor/github.com/olekukonko/tablewriter/LICENSE.md
generated
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
Copyright (C) 2014 by Oleku Konko
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
431
vendor/github.com/olekukonko/tablewriter/README.md
generated
vendored
Normal file
431
vendor/github.com/olekukonko/tablewriter/README.md
generated
vendored
Normal file
@ -0,0 +1,431 @@
|
||||
ASCII Table Writer
|
||||
=========
|
||||
|
||||
[![Build Status](https://travis-ci.org/olekukonko/tablewriter.png?branch=master)](https://travis-ci.org/olekukonko/tablewriter)
|
||||
[![Total views](https://img.shields.io/sourcegraph/rrc/github.com/olekukonko/tablewriter.svg)](https://sourcegraph.com/github.com/olekukonko/tablewriter)
|
||||
[![Godoc](https://godoc.org/github.com/olekukonko/tablewriter?status.svg)](https://godoc.org/github.com/olekukonko/tablewriter)
|
||||
|
||||
Generate ASCII table on the fly ... Installation is simple as
|
||||
|
||||
go get github.com/olekukonko/tablewriter
|
||||
|
||||
|
||||
#### Features
|
||||
- Automatic Padding
|
||||
- Support Multiple Lines
|
||||
- Supports Alignment
|
||||
- Support Custom Separators
|
||||
- Automatic Alignment of numbers & percentage
|
||||
- Write directly to http , file etc via `io.Writer`
|
||||
- Read directly from CSV file
|
||||
- Optional row line via `SetRowLine`
|
||||
- Normalise table header
|
||||
- Make CSV Headers optional
|
||||
- Enable or disable table border
|
||||
- Set custom footer support
|
||||
- Optional identical cells merging
|
||||
- Set custom caption
|
||||
- Optional reflowing of paragraphs in multi-line cells.
|
||||
|
||||
#### Example 1 - Basic
|
||||
```go
|
||||
data := [][]string{
|
||||
[]string{"A", "The Good", "500"},
|
||||
[]string{"B", "The Very very Bad Man", "288"},
|
||||
[]string{"C", "The Ugly", "120"},
|
||||
[]string{"D", "The Gopher", "800"},
|
||||
}
|
||||
|
||||
table := tablewriter.NewWriter(os.Stdout)
|
||||
table.SetHeader([]string{"Name", "Sign", "Rating"})
|
||||
|
||||
for _, v := range data {
|
||||
table.Append(v)
|
||||
}
|
||||
table.Render() // Send output
|
||||
```
|
||||
|
||||
##### Output 1
|
||||
```
|
||||
+------+-----------------------+--------+
|
||||
| NAME | SIGN | RATING |
|
||||
+------+-----------------------+--------+
|
||||
| A | The Good | 500 |
|
||||
| B | The Very very Bad Man | 288 |
|
||||
| C | The Ugly | 120 |
|
||||
| D | The Gopher | 800 |
|
||||
+------+-----------------------+--------+
|
||||
```
|
||||
|
||||
#### Example 2 - Without Border / Footer / Bulk Append
|
||||
```go
|
||||
data := [][]string{
|
||||
[]string{"1/1/2014", "Domain name", "2233", "$10.98"},
|
||||
[]string{"1/1/2014", "January Hosting", "2233", "$54.95"},
|
||||
[]string{"1/4/2014", "February Hosting", "2233", "$51.00"},
|
||||
[]string{"1/4/2014", "February Extra Bandwidth", "2233", "$30.00"},
|
||||
}
|
||||
|
||||
table := tablewriter.NewWriter(os.Stdout)
|
||||
table.SetHeader([]string{"Date", "Description", "CV2", "Amount"})
|
||||
table.SetFooter([]string{"", "", "Total", "$146.93"}) // Add Footer
|
||||
table.SetBorder(false) // Set Border to false
|
||||
table.AppendBulk(data) // Add Bulk Data
|
||||
table.Render()
|
||||
```
|
||||
|
||||
##### Output 2
|
||||
```
|
||||
|
||||
DATE | DESCRIPTION | CV2 | AMOUNT
|
||||
-----------+--------------------------+-------+----------
|
||||
1/1/2014 | Domain name | 2233 | $10.98
|
||||
1/1/2014 | January Hosting | 2233 | $54.95
|
||||
1/4/2014 | February Hosting | 2233 | $51.00
|
||||
1/4/2014 | February Extra Bandwidth | 2233 | $30.00
|
||||
-----------+--------------------------+-------+----------
|
||||
TOTAL | $146 93
|
||||
--------+----------
|
||||
|
||||
```
|
||||
|
||||
|
||||
#### Example 3 - CSV
|
||||
```go
|
||||
table, _ := tablewriter.NewCSV(os.Stdout, "testdata/test_info.csv", true)
|
||||
table.SetAlignment(tablewriter.ALIGN_LEFT) // Set Alignment
|
||||
table.Render()
|
||||
```
|
||||
|
||||
##### Output 3
|
||||
```
|
||||
+----------+--------------+------+-----+---------+----------------+
|
||||
| FIELD | TYPE | NULL | KEY | DEFAULT | EXTRA |
|
||||
+----------+--------------+------+-----+---------+----------------+
|
||||
| user_id | smallint(5) | NO | PRI | NULL | auto_increment |
|
||||
| username | varchar(10) | NO | | NULL | |
|
||||
| password | varchar(100) | NO | | NULL | |
|
||||
+----------+--------------+------+-----+---------+----------------+
|
||||
```
|
||||
|
||||
#### Example 4 - Custom Separator
|
||||
```go
|
||||
table, _ := tablewriter.NewCSV(os.Stdout, "testdata/test.csv", true)
|
||||
table.SetRowLine(true) // Enable row line
|
||||
|
||||
// Change table lines
|
||||
table.SetCenterSeparator("*")
|
||||
table.SetColumnSeparator("╪")
|
||||
table.SetRowSeparator("-")
|
||||
|
||||
table.SetAlignment(tablewriter.ALIGN_LEFT)
|
||||
table.Render()
|
||||
```
|
||||
|
||||
##### Output 4
|
||||
```
|
||||
*------------*-----------*---------*
|
||||
╪ FIRST NAME ╪ LAST NAME ╪ SSN ╪
|
||||
*------------*-----------*---------*
|
||||
╪ John ╪ Barry ╪ 123456 ╪
|
||||
*------------*-----------*---------*
|
||||
╪ Kathy ╪ Smith ╪ 687987 ╪
|
||||
*------------*-----------*---------*
|
||||
╪ Bob ╪ McCornick ╪ 3979870 ╪
|
||||
*------------*-----------*---------*
|
||||
```
|
||||
|
||||
#### Example 5 - Markdown Format
|
||||
```go
|
||||
data := [][]string{
|
||||
[]string{"1/1/2014", "Domain name", "2233", "$10.98"},
|
||||
[]string{"1/1/2014", "January Hosting", "2233", "$54.95"},
|
||||
[]string{"1/4/2014", "February Hosting", "2233", "$51.00"},
|
||||
[]string{"1/4/2014", "February Extra Bandwidth", "2233", "$30.00"},
|
||||
}
|
||||
|
||||
table := tablewriter.NewWriter(os.Stdout)
|
||||
table.SetHeader([]string{"Date", "Description", "CV2", "Amount"})
|
||||
table.SetBorders(tablewriter.Border{Left: true, Top: false, Right: true, Bottom: false})
|
||||
table.SetCenterSeparator("|")
|
||||
table.AppendBulk(data) // Add Bulk Data
|
||||
table.Render()
|
||||
```
|
||||
|
||||
##### Output 5
|
||||
```
|
||||
| DATE | DESCRIPTION | CV2 | AMOUNT |
|
||||
|----------|--------------------------|------|--------|
|
||||
| 1/1/2014 | Domain name | 2233 | $10.98 |
|
||||
| 1/1/2014 | January Hosting | 2233 | $54.95 |
|
||||
| 1/4/2014 | February Hosting | 2233 | $51.00 |
|
||||
| 1/4/2014 | February Extra Bandwidth | 2233 | $30.00 |
|
||||
```
|
||||
|
||||
#### Example 6 - Identical cells merging
|
||||
```go
|
||||
data := [][]string{
|
||||
[]string{"1/1/2014", "Domain name", "1234", "$10.98"},
|
||||
[]string{"1/1/2014", "January Hosting", "2345", "$54.95"},
|
||||
[]string{"1/4/2014", "February Hosting", "3456", "$51.00"},
|
||||
[]string{"1/4/2014", "February Extra Bandwidth", "4567", "$30.00"},
|
||||
}
|
||||
|
||||
table := tablewriter.NewWriter(os.Stdout)
|
||||
table.SetHeader([]string{"Date", "Description", "CV2", "Amount"})
|
||||
table.SetFooter([]string{"", "", "Total", "$146.93"})
|
||||
table.SetAutoMergeCells(true)
|
||||
table.SetRowLine(true)
|
||||
table.AppendBulk(data)
|
||||
table.Render()
|
||||
```
|
||||
|
||||
##### Output 6
|
||||
```
|
||||
+----------+--------------------------+-------+---------+
|
||||
| DATE | DESCRIPTION | CV2 | AMOUNT |
|
||||
+----------+--------------------------+-------+---------+
|
||||
| 1/1/2014 | Domain name | 1234 | $10.98 |
|
||||
+ +--------------------------+-------+---------+
|
||||
| | January Hosting | 2345 | $54.95 |
|
||||
+----------+--------------------------+-------+---------+
|
||||
| 1/4/2014 | February Hosting | 3456 | $51.00 |
|
||||
+ +--------------------------+-------+---------+
|
||||
| | February Extra Bandwidth | 4567 | $30.00 |
|
||||
+----------+--------------------------+-------+---------+
|
||||
| TOTAL | $146 93 |
|
||||
+----------+--------------------------+-------+---------+
|
||||
```
|
||||
|
||||
#### Example 7 - Identical cells merging (specify the column index to merge)
|
||||
```go
|
||||
data := [][]string{
|
||||
[]string{"1/1/2014", "Domain name", "1234", "$10.98"},
|
||||
[]string{"1/1/2014", "January Hosting", "1234", "$10.98"},
|
||||
[]string{"1/4/2014", "February Hosting", "3456", "$51.00"},
|
||||
[]string{"1/4/2014", "February Extra Bandwidth", "4567", "$30.00"},
|
||||
}
|
||||
|
||||
table := tablewriter.NewWriter(os.Stdout)
|
||||
table.SetHeader([]string{"Date", "Description", "CV2", "Amount"})
|
||||
table.SetFooter([]string{"", "", "Total", "$146.93"})
|
||||
table.SetAutoMergeCellsByColumnIndex([]int{2, 3})
|
||||
table.SetRowLine(true)
|
||||
table.AppendBulk(data)
|
||||
table.Render()
|
||||
```
|
||||
|
||||
##### Output 7
|
||||
```
|
||||
+----------+--------------------------+-------+---------+
|
||||
| DATE | DESCRIPTION | CV2 | AMOUNT |
|
||||
+----------+--------------------------+-------+---------+
|
||||
| 1/1/2014 | Domain name | 1234 | $10.98 |
|
||||
+----------+--------------------------+ + +
|
||||
| 1/1/2014 | January Hosting | | |
|
||||
+----------+--------------------------+-------+---------+
|
||||
| 1/4/2014 | February Hosting | 3456 | $51.00 |
|
||||
+----------+--------------------------+-------+---------+
|
||||
| 1/4/2014 | February Extra Bandwidth | 4567 | $30.00 |
|
||||
+----------+--------------------------+-------+---------+
|
||||
| TOTAL | $146.93 |
|
||||
+----------+--------------------------+-------+---------+
|
||||
```
|
||||
|
||||
|
||||
#### Table with color
|
||||
```go
|
||||
data := [][]string{
|
||||
[]string{"1/1/2014", "Domain name", "2233", "$10.98"},
|
||||
[]string{"1/1/2014", "January Hosting", "2233", "$54.95"},
|
||||
[]string{"1/4/2014", "February Hosting", "2233", "$51.00"},
|
||||
[]string{"1/4/2014", "February Extra Bandwidth", "2233", "$30.00"},
|
||||
}
|
||||
|
||||
table := tablewriter.NewWriter(os.Stdout)
|
||||
table.SetHeader([]string{"Date", "Description", "CV2", "Amount"})
|
||||
table.SetFooter([]string{"", "", "Total", "$146.93"}) // Add Footer
|
||||
table.SetBorder(false) // Set Border to false
|
||||
|
||||
table.SetHeaderColor(tablewriter.Colors{tablewriter.Bold, tablewriter.BgGreenColor},
|
||||
tablewriter.Colors{tablewriter.FgHiRedColor, tablewriter.Bold, tablewriter.BgBlackColor},
|
||||
tablewriter.Colors{tablewriter.BgRedColor, tablewriter.FgWhiteColor},
|
||||
tablewriter.Colors{tablewriter.BgCyanColor, tablewriter.FgWhiteColor})
|
||||
|
||||
table.SetColumnColor(tablewriter.Colors{tablewriter.Bold, tablewriter.FgHiBlackColor},
|
||||
tablewriter.Colors{tablewriter.Bold, tablewriter.FgHiRedColor},
|
||||
tablewriter.Colors{tablewriter.Bold, tablewriter.FgHiBlackColor},
|
||||
tablewriter.Colors{tablewriter.Bold, tablewriter.FgBlackColor})
|
||||
|
||||
table.SetFooterColor(tablewriter.Colors{}, tablewriter.Colors{},
|
||||
tablewriter.Colors{tablewriter.Bold},
|
||||
tablewriter.Colors{tablewriter.FgHiRedColor})
|
||||
|
||||
table.AppendBulk(data)
|
||||
table.Render()
|
||||
```
|
||||
|
||||
#### Table with color Output
|
||||
![Table with Color](https://cloud.githubusercontent.com/assets/6460392/21101956/bbc7b356-c0a1-11e6-9f36-dba694746efc.png)
|
||||
|
||||
#### Example - 8 Table Cells with Color
|
||||
|
||||
Individual Cell Colors from `func Rich` take precedence over Column Colors
|
||||
|
||||
```go
|
||||
data := [][]string{
|
||||
[]string{"Test1Merge", "HelloCol2 - 1", "HelloCol3 - 1", "HelloCol4 - 1"},
|
||||
[]string{"Test1Merge", "HelloCol2 - 2", "HelloCol3 - 2", "HelloCol4 - 2"},
|
||||
[]string{"Test1Merge", "HelloCol2 - 3", "HelloCol3 - 3", "HelloCol4 - 3"},
|
||||
[]string{"Test2Merge", "HelloCol2 - 4", "HelloCol3 - 4", "HelloCol4 - 4"},
|
||||
[]string{"Test2Merge", "HelloCol2 - 5", "HelloCol3 - 5", "HelloCol4 - 5"},
|
||||
[]string{"Test2Merge", "HelloCol2 - 6", "HelloCol3 - 6", "HelloCol4 - 6"},
|
||||
[]string{"Test2Merge", "HelloCol2 - 7", "HelloCol3 - 7", "HelloCol4 - 7"},
|
||||
[]string{"Test3Merge", "HelloCol2 - 8", "HelloCol3 - 8", "HelloCol4 - 8"},
|
||||
[]string{"Test3Merge", "HelloCol2 - 9", "HelloCol3 - 9", "HelloCol4 - 9"},
|
||||
[]string{"Test3Merge", "HelloCol2 - 10", "HelloCol3 -10", "HelloCol4 - 10"},
|
||||
}
|
||||
|
||||
table := tablewriter.NewWriter(os.Stdout)
|
||||
table.SetHeader([]string{"Col1", "Col2", "Col3", "Col4"})
|
||||
table.SetFooter([]string{"", "", "Footer3", "Footer4"})
|
||||
table.SetBorder(false)
|
||||
|
||||
table.SetHeaderColor(tablewriter.Colors{tablewriter.Bold, tablewriter.BgGreenColor},
|
||||
tablewriter.Colors{tablewriter.FgHiRedColor, tablewriter.Bold, tablewriter.BgBlackColor},
|
||||
tablewriter.Colors{tablewriter.BgRedColor, tablewriter.FgWhiteColor},
|
||||
tablewriter.Colors{tablewriter.BgCyanColor, tablewriter.FgWhiteColor})
|
||||
|
||||
table.SetColumnColor(tablewriter.Colors{tablewriter.Bold, tablewriter.FgHiBlackColor},
|
||||
tablewriter.Colors{tablewriter.Bold, tablewriter.FgHiRedColor},
|
||||
tablewriter.Colors{tablewriter.Bold, tablewriter.FgHiBlackColor},
|
||||
tablewriter.Colors{tablewriter.Bold, tablewriter.FgBlackColor})
|
||||
|
||||
table.SetFooterColor(tablewriter.Colors{}, tablewriter.Colors{},
|
||||
tablewriter.Colors{tablewriter.Bold},
|
||||
tablewriter.Colors{tablewriter.FgHiRedColor})
|
||||
|
||||
colorData1 := []string{"TestCOLOR1Merge", "HelloCol2 - COLOR1", "HelloCol3 - COLOR1", "HelloCol4 - COLOR1"}
|
||||
colorData2 := []string{"TestCOLOR2Merge", "HelloCol2 - COLOR2", "HelloCol3 - COLOR2", "HelloCol4 - COLOR2"}
|
||||
|
||||
for i, row := range data {
|
||||
if i == 4 {
|
||||
table.Rich(colorData1, []tablewriter.Colors{tablewriter.Colors{}, tablewriter.Colors{tablewriter.Normal, tablewriter.FgCyanColor}, tablewriter.Colors{tablewriter.Bold, tablewriter.FgWhiteColor}, tablewriter.Colors{}})
|
||||
table.Rich(colorData2, []tablewriter.Colors{tablewriter.Colors{tablewriter.Normal, tablewriter.FgMagentaColor}, tablewriter.Colors{}, tablewriter.Colors{tablewriter.Bold, tablewriter.BgRedColor}, tablewriter.Colors{tablewriter.FgHiGreenColor, tablewriter.Italic, tablewriter.BgHiCyanColor}})
|
||||
}
|
||||
table.Append(row)
|
||||
}
|
||||
|
||||
table.SetAutoMergeCells(true)
|
||||
table.Render()
|
||||
|
||||
```
|
||||
|
||||
##### Table cells with color Output
|
||||
![Table cells with Color](https://user-images.githubusercontent.com/9064687/63969376-bcd88d80-ca6f-11e9-9466-c3d954700b25.png)
|
||||
|
||||
#### Example 9 - Set table caption
|
||||
```go
|
||||
data := [][]string{
|
||||
[]string{"A", "The Good", "500"},
|
||||
[]string{"B", "The Very very Bad Man", "288"},
|
||||
[]string{"C", "The Ugly", "120"},
|
||||
[]string{"D", "The Gopher", "800"},
|
||||
}
|
||||
|
||||
table := tablewriter.NewWriter(os.Stdout)
|
||||
table.SetHeader([]string{"Name", "Sign", "Rating"})
|
||||
table.SetCaption(true, "Movie ratings.")
|
||||
|
||||
for _, v := range data {
|
||||
table.Append(v)
|
||||
}
|
||||
table.Render() // Send output
|
||||
```
|
||||
|
||||
Note: Caption text will wrap with total width of rendered table.
|
||||
|
||||
##### Output 9
|
||||
```
|
||||
+------+-----------------------+--------+
|
||||
| NAME | SIGN | RATING |
|
||||
+------+-----------------------+--------+
|
||||
| A | The Good | 500 |
|
||||
| B | The Very very Bad Man | 288 |
|
||||
| C | The Ugly | 120 |
|
||||
| D | The Gopher | 800 |
|
||||
+------+-----------------------+--------+
|
||||
Movie ratings.
|
||||
```
|
||||
|
||||
#### Example 10 - Set NoWhiteSpace and TablePadding option
|
||||
```go
|
||||
data := [][]string{
|
||||
{"node1.example.com", "Ready", "compute", "1.11"},
|
||||
{"node2.example.com", "Ready", "compute", "1.11"},
|
||||
{"node3.example.com", "Ready", "compute", "1.11"},
|
||||
{"node4.example.com", "NotReady", "compute", "1.11"},
|
||||
}
|
||||
|
||||
table := tablewriter.NewWriter(os.Stdout)
|
||||
table.SetHeader([]string{"Name", "Status", "Role", "Version"})
|
||||
table.SetAutoWrapText(false)
|
||||
table.SetAutoFormatHeaders(true)
|
||||
table.SetHeaderAlignment(ALIGN_LEFT)
|
||||
table.SetAlignment(ALIGN_LEFT)
|
||||
table.SetCenterSeparator("")
|
||||
table.SetColumnSeparator("")
|
||||
table.SetRowSeparator("")
|
||||
table.SetHeaderLine(false)
|
||||
table.SetBorder(false)
|
||||
table.SetTablePadding("\t") // pad with tabs
|
||||
table.SetNoWhiteSpace(true)
|
||||
table.AppendBulk(data) // Add Bulk Data
|
||||
table.Render()
|
||||
```
|
||||
|
||||
##### Output 10
|
||||
```
|
||||
NAME STATUS ROLE VERSION
|
||||
node1.example.com Ready compute 1.11
|
||||
node2.example.com Ready compute 1.11
|
||||
node3.example.com Ready compute 1.11
|
||||
node4.example.com NotReady compute 1.11
|
||||
```
|
||||
|
||||
#### Render table into a string
|
||||
|
||||
Instead of rendering the table to `io.Stdout` you can also render it into a string. Go 1.10 introduced the `strings.Builder` type which implements the `io.Writer` interface and can therefore be used for this task. Example:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"fmt"
|
||||
|
||||
"github.com/olekukonko/tablewriter"
|
||||
)
|
||||
|
||||
func main() {
|
||||
tableString := &strings.Builder{}
|
||||
table := tablewriter.NewWriter(tableString)
|
||||
|
||||
/*
|
||||
* Code to fill the table
|
||||
*/
|
||||
|
||||
table.Render()
|
||||
|
||||
fmt.Println(tableString.String())
|
||||
}
|
||||
```
|
||||
|
||||
#### TODO
|
||||
- ~~Import Directly from CSV~~ - `done`
|
||||
- ~~Support for `SetFooter`~~ - `done`
|
||||
- ~~Support for `SetBorder`~~ - `done`
|
||||
- ~~Support table with uneven rows~~ - `done`
|
||||
- ~~Support custom alignment~~
|
||||
- General Improvement & Optimisation
|
||||
- `NewHTML` Parse table from HTML
|
52
vendor/github.com/olekukonko/tablewriter/csv.go
generated
vendored
Normal file
52
vendor/github.com/olekukonko/tablewriter/csv.go
generated
vendored
Normal file
@ -0,0 +1,52 @@
|
||||
// Copyright 2014 Oleku Konko All rights reserved.
|
||||
// Use of this source code is governed by a MIT
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// This module is a Table Writer API for the Go Programming Language.
|
||||
// The protocols were written in pure Go and works on windows and unix systems
|
||||
|
||||
package tablewriter
|
||||
|
||||
import (
|
||||
"encoding/csv"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
// Start A new table by importing from a CSV file
|
||||
// Takes io.Writer and csv File name
|
||||
func NewCSV(writer io.Writer, fileName string, hasHeader bool) (*Table, error) {
|
||||
file, err := os.Open(fileName)
|
||||
if err != nil {
|
||||
return &Table{}, err
|
||||
}
|
||||
defer file.Close()
|
||||
csvReader := csv.NewReader(file)
|
||||
t, err := NewCSVReader(writer, csvReader, hasHeader)
|
||||
return t, err
|
||||
}
|
||||
|
||||
// Start a New Table Writer with csv.Reader
|
||||
// This enables customisation such as reader.Comma = ';'
|
||||
// See http://golang.org/src/pkg/encoding/csv/reader.go?s=3213:3671#L94
|
||||
func NewCSVReader(writer io.Writer, csvReader *csv.Reader, hasHeader bool) (*Table, error) {
|
||||
t := NewWriter(writer)
|
||||
if hasHeader {
|
||||
// Read the first row
|
||||
headers, err := csvReader.Read()
|
||||
if err != nil {
|
||||
return &Table{}, err
|
||||
}
|
||||
t.SetHeader(headers)
|
||||
}
|
||||
for {
|
||||
record, err := csvReader.Read()
|
||||
if err == io.EOF {
|
||||
break
|
||||
} else if err != nil {
|
||||
return &Table{}, err
|
||||
}
|
||||
t.Append(record)
|
||||
}
|
||||
return t, nil
|
||||
}
|
16
vendor/github.com/olekukonko/tablewriter/csv2table/BUILD.bazel
generated
vendored
Normal file
16
vendor/github.com/olekukonko/tablewriter/csv2table/BUILD.bazel
generated
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
|
||||
|
||||
go_library(
|
||||
name = "csv2table_lib",
|
||||
srcs = ["csv2table.go"],
|
||||
importmap = "peridot.resf.org/vendor/github.com/olekukonko/tablewriter/csv2table",
|
||||
importpath = "github.com/olekukonko/tablewriter/csv2table",
|
||||
visibility = ["//visibility:private"],
|
||||
deps = ["//vendor/github.com/olekukonko/tablewriter"],
|
||||
)
|
||||
|
||||
go_binary(
|
||||
name = "csv2table",
|
||||
embed = [":csv2table_lib"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
43
vendor/github.com/olekukonko/tablewriter/csv2table/README.md
generated
vendored
Normal file
43
vendor/github.com/olekukonko/tablewriter/csv2table/README.md
generated
vendored
Normal file
@ -0,0 +1,43 @@
|
||||
ASCII Table Writer Tool
|
||||
=========
|
||||
|
||||
Generate ASCII table on the fly via command line ... Installation is simple as
|
||||
|
||||
#### Get Tool
|
||||
|
||||
go get github.com/olekukonko/tablewriter/csv2table
|
||||
|
||||
#### Install Tool
|
||||
|
||||
go install github.com/olekukonko/tablewriter/csv2table
|
||||
|
||||
|
||||
#### Usage
|
||||
|
||||
csv2table -f test.csv
|
||||
|
||||
#### Support for Piping
|
||||
|
||||
cat test.csv | csv2table -p=true
|
||||
|
||||
#### Output
|
||||
|
||||
```
|
||||
+------------+-----------+---------+
|
||||
| FIRST NAME | LAST NAME | SSN |
|
||||
+------------+-----------+---------+
|
||||
| John | Barry | 123456 |
|
||||
| Kathy | Smith | 687987 |
|
||||
| Bob | McCornick | 3979870 |
|
||||
+------------+-----------+---------+
|
||||
```
|
||||
|
||||
#### Another Piping with Header set to `false`
|
||||
|
||||
echo dance,with,me | csv2table -p=true -h=false
|
||||
|
||||
#### Output
|
||||
|
||||
+-------+------+-----+
|
||||
| dance | with | me |
|
||||
+-------+------+-----+
|
85
vendor/github.com/olekukonko/tablewriter/csv2table/csv2table.go
generated
vendored
Normal file
85
vendor/github.com/olekukonko/tablewriter/csv2table/csv2table.go
generated
vendored
Normal file
@ -0,0 +1,85 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/csv"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/olekukonko/tablewriter"
|
||||
)
|
||||
|
||||
var (
|
||||
fileName = flag.String("f", "", "Set file with eg. sample.csv")
|
||||
delimiter = flag.String("d", ",", "Set CSV File delimiter eg. ,|;|\t ")
|
||||
header = flag.Bool("h", true, "Set header options eg. true|false ")
|
||||
align = flag.String("a", "none", "Set alignment with eg. none|left|right|center")
|
||||
pipe = flag.Bool("p", false, "Support for Piping from STDIN")
|
||||
border = flag.Bool("b", true, "Enable / disable table border")
|
||||
)
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
fmt.Println()
|
||||
if *pipe || hasArg("-p") {
|
||||
process(os.Stdin)
|
||||
} else {
|
||||
if *fileName == "" {
|
||||
fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
|
||||
flag.PrintDefaults()
|
||||
fmt.Println()
|
||||
os.Exit(1)
|
||||
}
|
||||
processFile()
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
func hasArg(name string) bool {
|
||||
for _, v := range os.Args {
|
||||
if name == v {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
func processFile() {
|
||||
r, err := os.Open(*fileName)
|
||||
if err != nil {
|
||||
exit(err)
|
||||
}
|
||||
defer r.Close()
|
||||
process(r)
|
||||
}
|
||||
func process(r io.Reader) {
|
||||
csvReader := csv.NewReader(r)
|
||||
rune, size := utf8.DecodeRuneInString(*delimiter)
|
||||
if size == 0 {
|
||||
rune = ','
|
||||
}
|
||||
csvReader.Comma = rune
|
||||
|
||||
table, err := tablewriter.NewCSVReader(os.Stdout, csvReader, *header)
|
||||
|
||||
if err != nil {
|
||||
exit(err)
|
||||
}
|
||||
|
||||
switch *align {
|
||||
case "left":
|
||||
table.SetAlignment(tablewriter.ALIGN_LEFT)
|
||||
case "right":
|
||||
table.SetAlignment(tablewriter.ALIGN_RIGHT)
|
||||
case "center":
|
||||
table.SetAlignment(tablewriter.ALIGN_CENTER)
|
||||
}
|
||||
table.SetBorder(*border)
|
||||
table.Render()
|
||||
}
|
||||
|
||||
func exit(err error) {
|
||||
fmt.Fprintf(os.Stderr, "#Error : %s", err)
|
||||
os.Exit(1)
|
||||
}
|
5
vendor/github.com/olekukonko/tablewriter/go.mod
generated
vendored
Normal file
5
vendor/github.com/olekukonko/tablewriter/go.mod
generated
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
module github.com/olekukonko/tablewriter
|
||||
|
||||
go 1.12
|
||||
|
||||
require github.com/mattn/go-runewidth v0.0.9
|
2
vendor/github.com/olekukonko/tablewriter/go.sum
generated
vendored
Normal file
2
vendor/github.com/olekukonko/tablewriter/go.sum
generated
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
967
vendor/github.com/olekukonko/tablewriter/table.go
generated
vendored
Normal file
967
vendor/github.com/olekukonko/tablewriter/table.go
generated
vendored
Normal file
@ -0,0 +1,967 @@
|
||||
// Copyright 2014 Oleku Konko All rights reserved.
|
||||
// Use of this source code is governed by a MIT
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// This module is a Table Writer API for the Go Programming Language.
|
||||
// The protocols were written in pure Go and works on windows and unix systems
|
||||
|
||||
// Create & Generate text based table
|
||||
package tablewriter
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
MAX_ROW_WIDTH = 30
|
||||
)
|
||||
|
||||
const (
|
||||
CENTER = "+"
|
||||
ROW = "-"
|
||||
COLUMN = "|"
|
||||
SPACE = " "
|
||||
NEWLINE = "\n"
|
||||
)
|
||||
|
||||
const (
|
||||
ALIGN_DEFAULT = iota
|
||||
ALIGN_CENTER
|
||||
ALIGN_RIGHT
|
||||
ALIGN_LEFT
|
||||
)
|
||||
|
||||
var (
|
||||
decimal = regexp.MustCompile(`^-?(?:\d{1,3}(?:,\d{3})*|\d+)(?:\.\d+)?$`)
|
||||
percent = regexp.MustCompile(`^-?\d+\.?\d*$%$`)
|
||||
)
|
||||
|
||||
type Border struct {
|
||||
Left bool
|
||||
Right bool
|
||||
Top bool
|
||||
Bottom bool
|
||||
}
|
||||
|
||||
type Table struct {
|
||||
out io.Writer
|
||||
rows [][]string
|
||||
lines [][][]string
|
||||
cs map[int]int
|
||||
rs map[int]int
|
||||
headers [][]string
|
||||
footers [][]string
|
||||
caption bool
|
||||
captionText string
|
||||
autoFmt bool
|
||||
autoWrap bool
|
||||
reflowText bool
|
||||
mW int
|
||||
pCenter string
|
||||
pRow string
|
||||
pColumn string
|
||||
tColumn int
|
||||
tRow int
|
||||
hAlign int
|
||||
fAlign int
|
||||
align int
|
||||
newLine string
|
||||
rowLine bool
|
||||
autoMergeCells bool
|
||||
columnsToAutoMergeCells map[int]bool
|
||||
noWhiteSpace bool
|
||||
tablePadding string
|
||||
hdrLine bool
|
||||
borders Border
|
||||
colSize int
|
||||
headerParams []string
|
||||
columnsParams []string
|
||||
footerParams []string
|
||||
columnsAlign []int
|
||||
}
|
||||
|
||||
// Start New Table
|
||||
// Take io.Writer Directly
|
||||
func NewWriter(writer io.Writer) *Table {
|
||||
t := &Table{
|
||||
out: writer,
|
||||
rows: [][]string{},
|
||||
lines: [][][]string{},
|
||||
cs: make(map[int]int),
|
||||
rs: make(map[int]int),
|
||||
headers: [][]string{},
|
||||
footers: [][]string{},
|
||||
caption: false,
|
||||
captionText: "Table caption.",
|
||||
autoFmt: true,
|
||||
autoWrap: true,
|
||||
reflowText: true,
|
||||
mW: MAX_ROW_WIDTH,
|
||||
pCenter: CENTER,
|
||||
pRow: ROW,
|
||||
pColumn: COLUMN,
|
||||
tColumn: -1,
|
||||
tRow: -1,
|
||||
hAlign: ALIGN_DEFAULT,
|
||||
fAlign: ALIGN_DEFAULT,
|
||||
align: ALIGN_DEFAULT,
|
||||
newLine: NEWLINE,
|
||||
rowLine: false,
|
||||
hdrLine: true,
|
||||
borders: Border{Left: true, Right: true, Bottom: true, Top: true},
|
||||
colSize: -1,
|
||||
headerParams: []string{},
|
||||
columnsParams: []string{},
|
||||
footerParams: []string{},
|
||||
columnsAlign: []int{}}
|
||||
return t
|
||||
}
|
||||
|
||||
// Render table output
|
||||
func (t *Table) Render() {
|
||||
if t.borders.Top {
|
||||
t.printLine(true)
|
||||
}
|
||||
t.printHeading()
|
||||
if t.autoMergeCells {
|
||||
t.printRowsMergeCells()
|
||||
} else {
|
||||
t.printRows()
|
||||
}
|
||||
if !t.rowLine && t.borders.Bottom {
|
||||
t.printLine(true)
|
||||
}
|
||||
t.printFooter()
|
||||
|
||||
if t.caption {
|
||||
t.printCaption()
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
headerRowIdx = -1
|
||||
footerRowIdx = -2
|
||||
)
|
||||
|
||||
// Set table header
|
||||
func (t *Table) SetHeader(keys []string) {
|
||||
t.colSize = len(keys)
|
||||
for i, v := range keys {
|
||||
lines := t.parseDimension(v, i, headerRowIdx)
|
||||
t.headers = append(t.headers, lines)
|
||||
}
|
||||
}
|
||||
|
||||
// Set table Footer
|
||||
func (t *Table) SetFooter(keys []string) {
|
||||
//t.colSize = len(keys)
|
||||
for i, v := range keys {
|
||||
lines := t.parseDimension(v, i, footerRowIdx)
|
||||
t.footers = append(t.footers, lines)
|
||||
}
|
||||
}
|
||||
|
||||
// Set table Caption
|
||||
func (t *Table) SetCaption(caption bool, captionText ...string) {
|
||||
t.caption = caption
|
||||
if len(captionText) == 1 {
|
||||
t.captionText = captionText[0]
|
||||
}
|
||||
}
|
||||
|
||||
// Turn header autoformatting on/off. Default is on (true).
|
||||
func (t *Table) SetAutoFormatHeaders(auto bool) {
|
||||
t.autoFmt = auto
|
||||
}
|
||||
|
||||
// Turn automatic multiline text adjustment on/off. Default is on (true).
|
||||
func (t *Table) SetAutoWrapText(auto bool) {
|
||||
t.autoWrap = auto
|
||||
}
|
||||
|
||||
// Turn automatic reflowing of multiline text when rewrapping. Default is on (true).
|
||||
func (t *Table) SetReflowDuringAutoWrap(auto bool) {
|
||||
t.reflowText = auto
|
||||
}
|
||||
|
||||
// Set the Default column width
|
||||
func (t *Table) SetColWidth(width int) {
|
||||
t.mW = width
|
||||
}
|
||||
|
||||
// Set the minimal width for a column
|
||||
func (t *Table) SetColMinWidth(column int, width int) {
|
||||
t.cs[column] = width
|
||||
}
|
||||
|
||||
// Set the Column Separator
|
||||
func (t *Table) SetColumnSeparator(sep string) {
|
||||
t.pColumn = sep
|
||||
}
|
||||
|
||||
// Set the Row Separator
|
||||
func (t *Table) SetRowSeparator(sep string) {
|
||||
t.pRow = sep
|
||||
}
|
||||
|
||||
// Set the center Separator
|
||||
func (t *Table) SetCenterSeparator(sep string) {
|
||||
t.pCenter = sep
|
||||
}
|
||||
|
||||
// Set Header Alignment
|
||||
func (t *Table) SetHeaderAlignment(hAlign int) {
|
||||
t.hAlign = hAlign
|
||||
}
|
||||
|
||||
// Set Footer Alignment
|
||||
func (t *Table) SetFooterAlignment(fAlign int) {
|
||||
t.fAlign = fAlign
|
||||
}
|
||||
|
||||
// Set Table Alignment
|
||||
func (t *Table) SetAlignment(align int) {
|
||||
t.align = align
|
||||
}
|
||||
|
||||
// Set No White Space
|
||||
func (t *Table) SetNoWhiteSpace(allow bool) {
|
||||
t.noWhiteSpace = allow
|
||||
}
|
||||
|
||||
// Set Table Padding
|
||||
func (t *Table) SetTablePadding(padding string) {
|
||||
t.tablePadding = padding
|
||||
}
|
||||
|
||||
func (t *Table) SetColumnAlignment(keys []int) {
|
||||
for _, v := range keys {
|
||||
switch v {
|
||||
case ALIGN_CENTER:
|
||||
break
|
||||
case ALIGN_LEFT:
|
||||
break
|
||||
case ALIGN_RIGHT:
|
||||
break
|
||||
default:
|
||||
v = ALIGN_DEFAULT
|
||||
}
|
||||
t.columnsAlign = append(t.columnsAlign, v)
|
||||
}
|
||||
}
|
||||
|
||||
// Set New Line
|
||||
func (t *Table) SetNewLine(nl string) {
|
||||
t.newLine = nl
|
||||
}
|
||||
|
||||
// Set Header Line
|
||||
// This would enable / disable a line after the header
|
||||
func (t *Table) SetHeaderLine(line bool) {
|
||||
t.hdrLine = line
|
||||
}
|
||||
|
||||
// Set Row Line
|
||||
// This would enable / disable a line on each row of the table
|
||||
func (t *Table) SetRowLine(line bool) {
|
||||
t.rowLine = line
|
||||
}
|
||||
|
||||
// Set Auto Merge Cells
|
||||
// This would enable / disable the merge of cells with identical values
|
||||
func (t *Table) SetAutoMergeCells(auto bool) {
|
||||
t.autoMergeCells = auto
|
||||
}
|
||||
|
||||
// Set Auto Merge Cells By Column Index
|
||||
// This would enable / disable the merge of cells with identical values for specific columns
|
||||
// If cols is empty, it is the same as `SetAutoMergeCells(true)`.
|
||||
func (t *Table) SetAutoMergeCellsByColumnIndex(cols []int) {
|
||||
t.autoMergeCells = true
|
||||
|
||||
if len(cols) > 0 {
|
||||
m := make(map[int]bool)
|
||||
for _, col := range cols {
|
||||
m[col] = true
|
||||
}
|
||||
t.columnsToAutoMergeCells = m
|
||||
}
|
||||
}
|
||||
|
||||
// Set Table Border
|
||||
// This would enable / disable line around the table
|
||||
func (t *Table) SetBorder(border bool) {
|
||||
t.SetBorders(Border{border, border, border, border})
|
||||
}
|
||||
|
||||
func (t *Table) SetBorders(border Border) {
|
||||
t.borders = border
|
||||
}
|
||||
|
||||
// Append row to table
|
||||
func (t *Table) Append(row []string) {
|
||||
rowSize := len(t.headers)
|
||||
if rowSize > t.colSize {
|
||||
t.colSize = rowSize
|
||||
}
|
||||
|
||||
n := len(t.lines)
|
||||
line := [][]string{}
|
||||
for i, v := range row {
|
||||
|
||||
// Detect string width
|
||||
// Detect String height
|
||||
// Break strings into words
|
||||
out := t.parseDimension(v, i, n)
|
||||
|
||||
// Append broken words
|
||||
line = append(line, out)
|
||||
}
|
||||
t.lines = append(t.lines, line)
|
||||
}
|
||||
|
||||
// Append row to table with color attributes
|
||||
func (t *Table) Rich(row []string, colors []Colors) {
|
||||
rowSize := len(t.headers)
|
||||
if rowSize > t.colSize {
|
||||
t.colSize = rowSize
|
||||
}
|
||||
|
||||
n := len(t.lines)
|
||||
line := [][]string{}
|
||||
for i, v := range row {
|
||||
|
||||
// Detect string width
|
||||
// Detect String height
|
||||
// Break strings into words
|
||||
out := t.parseDimension(v, i, n)
|
||||
|
||||
if len(colors) > i {
|
||||
color := colors[i]
|
||||
out[0] = format(out[0], color)
|
||||
}
|
||||
|
||||
// Append broken words
|
||||
line = append(line, out)
|
||||
}
|
||||
t.lines = append(t.lines, line)
|
||||
}
|
||||
|
||||
// Allow Support for Bulk Append
|
||||
// Eliminates repeated for loops
|
||||
func (t *Table) AppendBulk(rows [][]string) {
|
||||
for _, row := range rows {
|
||||
t.Append(row)
|
||||
}
|
||||
}
|
||||
|
||||
// NumLines to get the number of lines
|
||||
func (t *Table) NumLines() int {
|
||||
return len(t.lines)
|
||||
}
|
||||
|
||||
// Clear rows
|
||||
func (t *Table) ClearRows() {
|
||||
t.lines = [][][]string{}
|
||||
}
|
||||
|
||||
// Clear footer
|
||||
func (t *Table) ClearFooter() {
|
||||
t.footers = [][]string{}
|
||||
}
|
||||
|
||||
// Center based on position and border.
|
||||
func (t *Table) center(i int) string {
|
||||
if i == -1 && !t.borders.Left {
|
||||
return t.pRow
|
||||
}
|
||||
|
||||
if i == len(t.cs)-1 && !t.borders.Right {
|
||||
return t.pRow
|
||||
}
|
||||
|
||||
return t.pCenter
|
||||
}
|
||||
|
||||
// Print line based on row width
|
||||
func (t *Table) printLine(nl bool) {
|
||||
fmt.Fprint(t.out, t.center(-1))
|
||||
for i := 0; i < len(t.cs); i++ {
|
||||
v := t.cs[i]
|
||||
fmt.Fprintf(t.out, "%s%s%s%s",
|
||||
t.pRow,
|
||||
strings.Repeat(string(t.pRow), v),
|
||||
t.pRow,
|
||||
t.center(i))
|
||||
}
|
||||
if nl {
|
||||
fmt.Fprint(t.out, t.newLine)
|
||||
}
|
||||
}
|
||||
|
||||
// Print line based on row width with our without cell separator
|
||||
func (t *Table) printLineOptionalCellSeparators(nl bool, displayCellSeparator []bool) {
|
||||
fmt.Fprint(t.out, t.pCenter)
|
||||
for i := 0; i < len(t.cs); i++ {
|
||||
v := t.cs[i]
|
||||
if i > len(displayCellSeparator) || displayCellSeparator[i] {
|
||||
// Display the cell separator
|
||||
fmt.Fprintf(t.out, "%s%s%s%s",
|
||||
t.pRow,
|
||||
strings.Repeat(string(t.pRow), v),
|
||||
t.pRow,
|
||||
t.pCenter)
|
||||
} else {
|
||||
// Don't display the cell separator for this cell
|
||||
fmt.Fprintf(t.out, "%s%s",
|
||||
strings.Repeat(" ", v+2),
|
||||
t.pCenter)
|
||||
}
|
||||
}
|
||||
if nl {
|
||||
fmt.Fprint(t.out, t.newLine)
|
||||
}
|
||||
}
|
||||
|
||||
// Return the PadRight function if align is left, PadLeft if align is right,
|
||||
// and Pad by default
|
||||
func pad(align int) func(string, string, int) string {
|
||||
padFunc := Pad
|
||||
switch align {
|
||||
case ALIGN_LEFT:
|
||||
padFunc = PadRight
|
||||
case ALIGN_RIGHT:
|
||||
padFunc = PadLeft
|
||||
}
|
||||
return padFunc
|
||||
}
|
||||
|
||||
// Print heading information
|
||||
func (t *Table) printHeading() {
|
||||
// Check if headers is available
|
||||
if len(t.headers) < 1 {
|
||||
return
|
||||
}
|
||||
|
||||
// Identify last column
|
||||
end := len(t.cs) - 1
|
||||
|
||||
// Get pad function
|
||||
padFunc := pad(t.hAlign)
|
||||
|
||||
// Checking for ANSI escape sequences for header
|
||||
is_esc_seq := false
|
||||
if len(t.headerParams) > 0 {
|
||||
is_esc_seq = true
|
||||
}
|
||||
|
||||
// Maximum height.
|
||||
max := t.rs[headerRowIdx]
|
||||
|
||||
// Print Heading
|
||||
for x := 0; x < max; x++ {
|
||||
// Check if border is set
|
||||
// Replace with space if not set
|
||||
if !t.noWhiteSpace {
|
||||
fmt.Fprint(t.out, ConditionString(t.borders.Left, t.pColumn, SPACE))
|
||||
}
|
||||
|
||||
for y := 0; y <= end; y++ {
|
||||
v := t.cs[y]
|
||||
h := ""
|
||||
|
||||
if y < len(t.headers) && x < len(t.headers[y]) {
|
||||
h = t.headers[y][x]
|
||||
}
|
||||
if t.autoFmt {
|
||||
h = Title(h)
|
||||
}
|
||||
pad := ConditionString((y == end && !t.borders.Left), SPACE, t.pColumn)
|
||||
if t.noWhiteSpace {
|
||||
pad = ConditionString((y == end && !t.borders.Left), SPACE, t.tablePadding)
|
||||
}
|
||||
if is_esc_seq {
|
||||
if !t.noWhiteSpace {
|
||||
fmt.Fprintf(t.out, " %s %s",
|
||||
format(padFunc(h, SPACE, v),
|
||||
t.headerParams[y]), pad)
|
||||
} else {
|
||||
fmt.Fprintf(t.out, "%s %s",
|
||||
format(padFunc(h, SPACE, v),
|
||||
t.headerParams[y]), pad)
|
||||
}
|
||||
} else {
|
||||
if !t.noWhiteSpace {
|
||||
fmt.Fprintf(t.out, " %s %s",
|
||||
padFunc(h, SPACE, v),
|
||||
pad)
|
||||
} else {
|
||||
// the spaces between breaks the kube formatting
|
||||
fmt.Fprintf(t.out, "%s%s",
|
||||
padFunc(h, SPACE, v),
|
||||
pad)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Next line
|
||||
fmt.Fprint(t.out, t.newLine)
|
||||
}
|
||||
if t.hdrLine {
|
||||
t.printLine(true)
|
||||
}
|
||||
}
|
||||
|
||||
// Print heading information
|
||||
func (t *Table) printFooter() {
|
||||
// Check if headers is available
|
||||
if len(t.footers) < 1 {
|
||||
return
|
||||
}
|
||||
|
||||
// Only print line if border is not set
|
||||
if !t.borders.Bottom {
|
||||
t.printLine(true)
|
||||
}
|
||||
|
||||
// Identify last column
|
||||
end := len(t.cs) - 1
|
||||
|
||||
// Get pad function
|
||||
padFunc := pad(t.fAlign)
|
||||
|
||||
// Checking for ANSI escape sequences for header
|
||||
is_esc_seq := false
|
||||
if len(t.footerParams) > 0 {
|
||||
is_esc_seq = true
|
||||
}
|
||||
|
||||
// Maximum height.
|
||||
max := t.rs[footerRowIdx]
|
||||
|
||||
// Print Footer
|
||||
erasePad := make([]bool, len(t.footers))
|
||||
for x := 0; x < max; x++ {
|
||||
// Check if border is set
|
||||
// Replace with space if not set
|
||||
fmt.Fprint(t.out, ConditionString(t.borders.Bottom, t.pColumn, SPACE))
|
||||
|
||||
for y := 0; y <= end; y++ {
|
||||
v := t.cs[y]
|
||||
f := ""
|
||||
if y < len(t.footers) && x < len(t.footers[y]) {
|
||||
f = t.footers[y][x]
|
||||
}
|
||||
if t.autoFmt {
|
||||
f = Title(f)
|
||||
}
|
||||
pad := ConditionString((y == end && !t.borders.Top), SPACE, t.pColumn)
|
||||
|
||||
if erasePad[y] || (x == 0 && len(f) == 0) {
|
||||
pad = SPACE
|
||||
erasePad[y] = true
|
||||
}
|
||||
|
||||
if is_esc_seq {
|
||||
fmt.Fprintf(t.out, " %s %s",
|
||||
format(padFunc(f, SPACE, v),
|
||||
t.footerParams[y]), pad)
|
||||
} else {
|
||||
fmt.Fprintf(t.out, " %s %s",
|
||||
padFunc(f, SPACE, v),
|
||||
pad)
|
||||
}
|
||||
|
||||
//fmt.Fprintf(t.out, " %s %s",
|
||||
// padFunc(f, SPACE, v),
|
||||
// pad)
|
||||
}
|
||||
// Next line
|
||||
fmt.Fprint(t.out, t.newLine)
|
||||
//t.printLine(true)
|
||||
}
|
||||
|
||||
hasPrinted := false
|
||||
|
||||
for i := 0; i <= end; i++ {
|
||||
v := t.cs[i]
|
||||
pad := t.pRow
|
||||
center := t.pCenter
|
||||
length := len(t.footers[i][0])
|
||||
|
||||
if length > 0 {
|
||||
hasPrinted = true
|
||||
}
|
||||
|
||||
// Set center to be space if length is 0
|
||||
if length == 0 && !t.borders.Right {
|
||||
center = SPACE
|
||||
}
|
||||
|
||||
// Print first junction
|
||||
if i == 0 {
|
||||
if length > 0 && !t.borders.Left {
|
||||
center = t.pRow
|
||||
}
|
||||
fmt.Fprint(t.out, center)
|
||||
}
|
||||
|
||||
// Pad With space of length is 0
|
||||
if length == 0 {
|
||||
pad = SPACE
|
||||
}
|
||||
// Ignore left space as it has printed before
|
||||
if hasPrinted || t.borders.Left {
|
||||
pad = t.pRow
|
||||
center = t.pCenter
|
||||
}
|
||||
|
||||
// Change Center end position
|
||||
if center != SPACE {
|
||||
if i == end && !t.borders.Right {
|
||||
center = t.pRow
|
||||
}
|
||||
}
|
||||
|
||||
// Change Center start position
|
||||
if center == SPACE {
|
||||
if i < end && len(t.footers[i+1][0]) != 0 {
|
||||
if !t.borders.Left {
|
||||
center = t.pRow
|
||||
} else {
|
||||
center = t.pCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Print the footer
|
||||
fmt.Fprintf(t.out, "%s%s%s%s",
|
||||
pad,
|
||||
strings.Repeat(string(pad), v),
|
||||
pad,
|
||||
center)
|
||||
|
||||
}
|
||||
|
||||
fmt.Fprint(t.out, t.newLine)
|
||||
}
|
||||
|
||||
// Print caption text
|
||||
func (t Table) printCaption() {
|
||||
width := t.getTableWidth()
|
||||
paragraph, _ := WrapString(t.captionText, width)
|
||||
for linecount := 0; linecount < len(paragraph); linecount++ {
|
||||
fmt.Fprintln(t.out, paragraph[linecount])
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate the total number of characters in a row
|
||||
func (t Table) getTableWidth() int {
|
||||
var chars int
|
||||
for _, v := range t.cs {
|
||||
chars += v
|
||||
}
|
||||
|
||||
// Add chars, spaces, seperators to calculate the total width of the table.
|
||||
// ncols := t.colSize
|
||||
// spaces := ncols * 2
|
||||
// seps := ncols + 1
|
||||
|
||||
return (chars + (3 * t.colSize) + 2)
|
||||
}
|
||||
|
||||
func (t Table) printRows() {
|
||||
for i, lines := range t.lines {
|
||||
t.printRow(lines, i)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Table) fillAlignment(num int) {
|
||||
if len(t.columnsAlign) < num {
|
||||
t.columnsAlign = make([]int, num)
|
||||
for i := range t.columnsAlign {
|
||||
t.columnsAlign[i] = t.align
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Print Row Information
|
||||
// Adjust column alignment based on type
|
||||
|
||||
func (t *Table) printRow(columns [][]string, rowIdx int) {
|
||||
// Get Maximum Height
|
||||
max := t.rs[rowIdx]
|
||||
total := len(columns)
|
||||
|
||||
// TODO Fix uneven col size
|
||||
// if total < t.colSize {
|
||||
// for n := t.colSize - total; n < t.colSize ; n++ {
|
||||
// columns = append(columns, []string{SPACE})
|
||||
// t.cs[n] = t.mW
|
||||
// }
|
||||
//}
|
||||
|
||||
// Pad Each Height
|
||||
pads := []int{}
|
||||
|
||||
// Checking for ANSI escape sequences for columns
|
||||
is_esc_seq := false
|
||||
if len(t.columnsParams) > 0 {
|
||||
is_esc_seq = true
|
||||
}
|
||||
t.fillAlignment(total)
|
||||
|
||||
for i, line := range columns {
|
||||
length := len(line)
|
||||
pad := max - length
|
||||
pads = append(pads, pad)
|
||||
for n := 0; n < pad; n++ {
|
||||
columns[i] = append(columns[i], " ")
|
||||
}
|
||||
}
|
||||
//fmt.Println(max, "\n")
|
||||
for x := 0; x < max; x++ {
|
||||
for y := 0; y < total; y++ {
|
||||
|
||||
// Check if border is set
|
||||
if !t.noWhiteSpace {
|
||||
fmt.Fprint(t.out, ConditionString((!t.borders.Left && y == 0), SPACE, t.pColumn))
|
||||
fmt.Fprintf(t.out, SPACE)
|
||||
}
|
||||
|
||||
str := columns[y][x]
|
||||
|
||||
// Embedding escape sequence with column value
|
||||
if is_esc_seq {
|
||||
str = format(str, t.columnsParams[y])
|
||||
}
|
||||
|
||||
// This would print alignment
|
||||
// Default alignment would use multiple configuration
|
||||
switch t.columnsAlign[y] {
|
||||
case ALIGN_CENTER: //
|
||||
fmt.Fprintf(t.out, "%s", Pad(str, SPACE, t.cs[y]))
|
||||
case ALIGN_RIGHT:
|
||||
fmt.Fprintf(t.out, "%s", PadLeft(str, SPACE, t.cs[y]))
|
||||
case ALIGN_LEFT:
|
||||
fmt.Fprintf(t.out, "%s", PadRight(str, SPACE, t.cs[y]))
|
||||
default:
|
||||
if decimal.MatchString(strings.TrimSpace(str)) || percent.MatchString(strings.TrimSpace(str)) {
|
||||
fmt.Fprintf(t.out, "%s", PadLeft(str, SPACE, t.cs[y]))
|
||||
} else {
|
||||
fmt.Fprintf(t.out, "%s", PadRight(str, SPACE, t.cs[y]))
|
||||
|
||||
// TODO Custom alignment per column
|
||||
//if max == 1 || pads[y] > 0 {
|
||||
// fmt.Fprintf(t.out, "%s", Pad(str, SPACE, t.cs[y]))
|
||||
//} else {
|
||||
// fmt.Fprintf(t.out, "%s", PadRight(str, SPACE, t.cs[y]))
|
||||
//}
|
||||
|
||||
}
|
||||
}
|
||||
if !t.noWhiteSpace {
|
||||
fmt.Fprintf(t.out, SPACE)
|
||||
} else {
|
||||
fmt.Fprintf(t.out, t.tablePadding)
|
||||
}
|
||||
}
|
||||
// Check if border is set
|
||||
// Replace with space if not set
|
||||
if !t.noWhiteSpace {
|
||||
fmt.Fprint(t.out, ConditionString(t.borders.Left, t.pColumn, SPACE))
|
||||
}
|
||||
fmt.Fprint(t.out, t.newLine)
|
||||
}
|
||||
|
||||
if t.rowLine {
|
||||
t.printLine(true)
|
||||
}
|
||||
}
|
||||
|
||||
// Print the rows of the table and merge the cells that are identical
|
||||
func (t *Table) printRowsMergeCells() {
|
||||
var previousLine []string
|
||||
var displayCellBorder []bool
|
||||
var tmpWriter bytes.Buffer
|
||||
for i, lines := range t.lines {
|
||||
// We store the display of the current line in a tmp writer, as we need to know which border needs to be print above
|
||||
previousLine, displayCellBorder = t.printRowMergeCells(&tmpWriter, lines, i, previousLine)
|
||||
if i > 0 { //We don't need to print borders above first line
|
||||
if t.rowLine {
|
||||
t.printLineOptionalCellSeparators(true, displayCellBorder)
|
||||
}
|
||||
}
|
||||
tmpWriter.WriteTo(t.out)
|
||||
}
|
||||
//Print the end of the table
|
||||
if t.rowLine {
|
||||
t.printLine(true)
|
||||
}
|
||||
}
|
||||
|
||||
// Print Row Information to a writer and merge identical cells.
|
||||
// Adjust column alignment based on type
|
||||
|
||||
func (t *Table) printRowMergeCells(writer io.Writer, columns [][]string, rowIdx int, previousLine []string) ([]string, []bool) {
|
||||
// Get Maximum Height
|
||||
max := t.rs[rowIdx]
|
||||
total := len(columns)
|
||||
|
||||
// Pad Each Height
|
||||
pads := []int{}
|
||||
|
||||
// Checking for ANSI escape sequences for columns
|
||||
is_esc_seq := false
|
||||
if len(t.columnsParams) > 0 {
|
||||
is_esc_seq = true
|
||||
}
|
||||
for i, line := range columns {
|
||||
length := len(line)
|
||||
pad := max - length
|
||||
pads = append(pads, pad)
|
||||
for n := 0; n < pad; n++ {
|
||||
columns[i] = append(columns[i], " ")
|
||||
}
|
||||
}
|
||||
|
||||
var displayCellBorder []bool
|
||||
t.fillAlignment(total)
|
||||
for x := 0; x < max; x++ {
|
||||
for y := 0; y < total; y++ {
|
||||
|
||||
// Check if border is set
|
||||
fmt.Fprint(writer, ConditionString((!t.borders.Left && y == 0), SPACE, t.pColumn))
|
||||
|
||||
fmt.Fprintf(writer, SPACE)
|
||||
|
||||
str := columns[y][x]
|
||||
|
||||
// Embedding escape sequence with column value
|
||||
if is_esc_seq {
|
||||
str = format(str, t.columnsParams[y])
|
||||
}
|
||||
|
||||
if t.autoMergeCells {
|
||||
var mergeCell bool
|
||||
if t.columnsToAutoMergeCells != nil {
|
||||
// Check to see if the column index is in columnsToAutoMergeCells.
|
||||
if t.columnsToAutoMergeCells[y] {
|
||||
mergeCell = true
|
||||
}
|
||||
} else {
|
||||
// columnsToAutoMergeCells was not set.
|
||||
mergeCell = true
|
||||
}
|
||||
//Store the full line to merge mutli-lines cells
|
||||
fullLine := strings.TrimRight(strings.Join(columns[y], " "), " ")
|
||||
if len(previousLine) > y && fullLine == previousLine[y] && fullLine != "" && mergeCell {
|
||||
// If this cell is identical to the one above but not empty, we don't display the border and keep the cell empty.
|
||||
displayCellBorder = append(displayCellBorder, false)
|
||||
str = ""
|
||||
} else {
|
||||
// First line or different content, keep the content and print the cell border
|
||||
displayCellBorder = append(displayCellBorder, true)
|
||||
}
|
||||
}
|
||||
|
||||
// This would print alignment
|
||||
// Default alignment would use multiple configuration
|
||||
switch t.columnsAlign[y] {
|
||||
case ALIGN_CENTER: //
|
||||
fmt.Fprintf(writer, "%s", Pad(str, SPACE, t.cs[y]))
|
||||
case ALIGN_RIGHT:
|
||||
fmt.Fprintf(writer, "%s", PadLeft(str, SPACE, t.cs[y]))
|
||||
case ALIGN_LEFT:
|
||||
fmt.Fprintf(writer, "%s", PadRight(str, SPACE, t.cs[y]))
|
||||
default:
|
||||
if decimal.MatchString(strings.TrimSpace(str)) || percent.MatchString(strings.TrimSpace(str)) {
|
||||
fmt.Fprintf(writer, "%s", PadLeft(str, SPACE, t.cs[y]))
|
||||
} else {
|
||||
fmt.Fprintf(writer, "%s", PadRight(str, SPACE, t.cs[y]))
|
||||
}
|
||||
}
|
||||
fmt.Fprintf(writer, SPACE)
|
||||
}
|
||||
// Check if border is set
|
||||
// Replace with space if not set
|
||||
fmt.Fprint(writer, ConditionString(t.borders.Left, t.pColumn, SPACE))
|
||||
fmt.Fprint(writer, t.newLine)
|
||||
}
|
||||
|
||||
//The new previous line is the current one
|
||||
previousLine = make([]string, total)
|
||||
for y := 0; y < total; y++ {
|
||||
previousLine[y] = strings.TrimRight(strings.Join(columns[y], " "), " ") //Store the full line for multi-lines cells
|
||||
}
|
||||
//Returns the newly added line and wether or not a border should be displayed above.
|
||||
return previousLine, displayCellBorder
|
||||
}
|
||||
|
||||
func (t *Table) parseDimension(str string, colKey, rowKey int) []string {
|
||||
var (
|
||||
raw []string
|
||||
maxWidth int
|
||||
)
|
||||
|
||||
raw = getLines(str)
|
||||
maxWidth = 0
|
||||
for _, line := range raw {
|
||||
if w := DisplayWidth(line); w > maxWidth {
|
||||
maxWidth = w
|
||||
}
|
||||
}
|
||||
|
||||
// If wrapping, ensure that all paragraphs in the cell fit in the
|
||||
// specified width.
|
||||
if t.autoWrap {
|
||||
// If there's a maximum allowed width for wrapping, use that.
|
||||
if maxWidth > t.mW {
|
||||
maxWidth = t.mW
|
||||
}
|
||||
|
||||
// In the process of doing so, we need to recompute maxWidth. This
|
||||
// is because perhaps a word in the cell is longer than the
|
||||
// allowed maximum width in t.mW.
|
||||
newMaxWidth := maxWidth
|
||||
newRaw := make([]string, 0, len(raw))
|
||||
|
||||
if t.reflowText {
|
||||
// Make a single paragraph of everything.
|
||||
raw = []string{strings.Join(raw, " ")}
|
||||
}
|
||||
for i, para := range raw {
|
||||
paraLines, _ := WrapString(para, maxWidth)
|
||||
for _, line := range paraLines {
|
||||
if w := DisplayWidth(line); w > newMaxWidth {
|
||||
newMaxWidth = w
|
||||
}
|
||||
}
|
||||
if i > 0 {
|
||||
newRaw = append(newRaw, " ")
|
||||
}
|
||||
newRaw = append(newRaw, paraLines...)
|
||||
}
|
||||
raw = newRaw
|
||||
maxWidth = newMaxWidth
|
||||
}
|
||||
|
||||
// Store the new known maximum width.
|
||||
v, ok := t.cs[colKey]
|
||||
if !ok || v < maxWidth || v == 0 {
|
||||
t.cs[colKey] = maxWidth
|
||||
}
|
||||
|
||||
// Remember the number of lines for the row printer.
|
||||
h := len(raw)
|
||||
v, ok = t.rs[rowKey]
|
||||
|
||||
if !ok || v < h || v == 0 {
|
||||
t.rs[rowKey] = h
|
||||
}
|
||||
//fmt.Printf("Raw %+v %d\n", raw, len(raw))
|
||||
return raw
|
||||
}
|
1182
vendor/github.com/olekukonko/tablewriter/table_test.go
generated
vendored
Normal file
1182
vendor/github.com/olekukonko/tablewriter/table_test.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
136
vendor/github.com/olekukonko/tablewriter/table_with_color.go
generated
vendored
Normal file
136
vendor/github.com/olekukonko/tablewriter/table_with_color.go
generated
vendored
Normal file
@ -0,0 +1,136 @@
|
||||
package tablewriter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const ESC = "\033"
|
||||
const SEP = ";"
|
||||
|
||||
const (
|
||||
BgBlackColor int = iota + 40
|
||||
BgRedColor
|
||||
BgGreenColor
|
||||
BgYellowColor
|
||||
BgBlueColor
|
||||
BgMagentaColor
|
||||
BgCyanColor
|
||||
BgWhiteColor
|
||||
)
|
||||
|
||||
const (
|
||||
FgBlackColor int = iota + 30
|
||||
FgRedColor
|
||||
FgGreenColor
|
||||
FgYellowColor
|
||||
FgBlueColor
|
||||
FgMagentaColor
|
||||
FgCyanColor
|
||||
FgWhiteColor
|
||||
)
|
||||
|
||||
const (
|
||||
BgHiBlackColor int = iota + 100
|
||||
BgHiRedColor
|
||||
BgHiGreenColor
|
||||
BgHiYellowColor
|
||||
BgHiBlueColor
|
||||
BgHiMagentaColor
|
||||
BgHiCyanColor
|
||||
BgHiWhiteColor
|
||||
)
|
||||
|
||||
const (
|
||||
FgHiBlackColor int = iota + 90
|
||||
FgHiRedColor
|
||||
FgHiGreenColor
|
||||
FgHiYellowColor
|
||||
FgHiBlueColor
|
||||
FgHiMagentaColor
|
||||
FgHiCyanColor
|
||||
FgHiWhiteColor
|
||||
)
|
||||
|
||||
const (
|
||||
Normal = 0
|
||||
Bold = 1
|
||||
UnderlineSingle = 4
|
||||
Italic
|
||||
)
|
||||
|
||||
type Colors []int
|
||||
|
||||
func startFormat(seq string) string {
|
||||
return fmt.Sprintf("%s[%sm", ESC, seq)
|
||||
}
|
||||
|
||||
func stopFormat() string {
|
||||
return fmt.Sprintf("%s[%dm", ESC, Normal)
|
||||
}
|
||||
|
||||
// Making the SGR (Select Graphic Rendition) sequence.
|
||||
func makeSequence(codes []int) string {
|
||||
codesInString := []string{}
|
||||
for _, code := range codes {
|
||||
codesInString = append(codesInString, strconv.Itoa(code))
|
||||
}
|
||||
return strings.Join(codesInString, SEP)
|
||||
}
|
||||
|
||||
// Adding ANSI escape sequences before and after string
|
||||
func format(s string, codes interface{}) string {
|
||||
var seq string
|
||||
|
||||
switch v := codes.(type) {
|
||||
|
||||
case string:
|
||||
seq = v
|
||||
case []int:
|
||||
seq = makeSequence(v)
|
||||
case Colors:
|
||||
seq = makeSequence(v)
|
||||
default:
|
||||
return s
|
||||
}
|
||||
|
||||
if len(seq) == 0 {
|
||||
return s
|
||||
}
|
||||
return startFormat(seq) + s + stopFormat()
|
||||
}
|
||||
|
||||
// Adding header colors (ANSI codes)
|
||||
func (t *Table) SetHeaderColor(colors ...Colors) {
|
||||
if t.colSize != len(colors) {
|
||||
panic("Number of header colors must be equal to number of headers.")
|
||||
}
|
||||
for i := 0; i < len(colors); i++ {
|
||||
t.headerParams = append(t.headerParams, makeSequence(colors[i]))
|
||||
}
|
||||
}
|
||||
|
||||
// Adding column colors (ANSI codes)
|
||||
func (t *Table) SetColumnColor(colors ...Colors) {
|
||||
if t.colSize != len(colors) {
|
||||
panic("Number of column colors must be equal to number of headers.")
|
||||
}
|
||||
for i := 0; i < len(colors); i++ {
|
||||
t.columnsParams = append(t.columnsParams, makeSequence(colors[i]))
|
||||
}
|
||||
}
|
||||
|
||||
// Adding column colors (ANSI codes)
|
||||
func (t *Table) SetFooterColor(colors ...Colors) {
|
||||
if len(t.footers) != len(colors) {
|
||||
panic("Number of footer colors must be equal to number of footer.")
|
||||
}
|
||||
for i := 0; i < len(colors); i++ {
|
||||
t.footerParams = append(t.footerParams, makeSequence(colors[i]))
|
||||
}
|
||||
}
|
||||
|
||||
func Color(colors ...int) []int {
|
||||
return colors
|
||||
}
|
4
vendor/github.com/olekukonko/tablewriter/testdata/test.csv
generated
vendored
Normal file
4
vendor/github.com/olekukonko/tablewriter/testdata/test.csv
generated
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
first_name,last_name,ssn
|
||||
John,Barry,123456
|
||||
Kathy,Smith,687987
|
||||
Bob,McCornick,3979870
|
|
4
vendor/github.com/olekukonko/tablewriter/testdata/test_info.csv
generated
vendored
Normal file
4
vendor/github.com/olekukonko/tablewriter/testdata/test_info.csv
generated
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
Field,Type,Null,Key,Default,Extra
|
||||
user_id,smallint(5),NO,PRI,NULL,auto_increment
|
||||
username,varchar(10),NO,,NULL,
|
||||
password,varchar(100),NO,,NULL,
|
|
93
vendor/github.com/olekukonko/tablewriter/util.go
generated
vendored
Normal file
93
vendor/github.com/olekukonko/tablewriter/util.go
generated
vendored
Normal file
@ -0,0 +1,93 @@
|
||||
// Copyright 2014 Oleku Konko All rights reserved.
|
||||
// Use of this source code is governed by a MIT
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// This module is a Table Writer API for the Go Programming Language.
|
||||
// The protocols were written in pure Go and works on windows and unix systems
|
||||
|
||||
package tablewriter
|
||||
|
||||
import (
|
||||
"math"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/mattn/go-runewidth"
|
||||
)
|
||||
|
||||
var ansi = regexp.MustCompile("\033\\[(?:[0-9]{1,3}(?:;[0-9]{1,3})*)?[m|K]")
|
||||
|
||||
func DisplayWidth(str string) int {
|
||||
return runewidth.StringWidth(ansi.ReplaceAllLiteralString(str, ""))
|
||||
}
|
||||
|
||||
// Simple Condition for string
|
||||
// Returns value based on condition
|
||||
func ConditionString(cond bool, valid, inValid string) string {
|
||||
if cond {
|
||||
return valid
|
||||
}
|
||||
return inValid
|
||||
}
|
||||
|
||||
func isNumOrSpace(r rune) bool {
|
||||
return ('0' <= r && r <= '9') || r == ' '
|
||||
}
|
||||
|
||||
// Format Table Header
|
||||
// Replace _ , . and spaces
|
||||
func Title(name string) string {
|
||||
origLen := len(name)
|
||||
rs := []rune(name)
|
||||
for i, r := range rs {
|
||||
switch r {
|
||||
case '_':
|
||||
rs[i] = ' '
|
||||
case '.':
|
||||
// ignore floating number 0.0
|
||||
if (i != 0 && !isNumOrSpace(rs[i-1])) || (i != len(rs)-1 && !isNumOrSpace(rs[i+1])) {
|
||||
rs[i] = ' '
|
||||
}
|
||||
}
|
||||
}
|
||||
name = string(rs)
|
||||
name = strings.TrimSpace(name)
|
||||
if len(name) == 0 && origLen > 0 {
|
||||
// Keep at least one character. This is important to preserve
|
||||
// empty lines in multi-line headers/footers.
|
||||
name = " "
|
||||
}
|
||||
return strings.ToUpper(name)
|
||||
}
|
||||
|
||||
// Pad String
|
||||
// Attempts to place string in the center
|
||||
func Pad(s, pad string, width int) string {
|
||||
gap := width - DisplayWidth(s)
|
||||
if gap > 0 {
|
||||
gapLeft := int(math.Ceil(float64(gap / 2)))
|
||||
gapRight := gap - gapLeft
|
||||
return strings.Repeat(string(pad), gapLeft) + s + strings.Repeat(string(pad), gapRight)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Pad String Right position
|
||||
// This would place string at the left side of the screen
|
||||
func PadRight(s, pad string, width int) string {
|
||||
gap := width - DisplayWidth(s)
|
||||
if gap > 0 {
|
||||
return s + strings.Repeat(string(pad), gap)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Pad String Left position
|
||||
// This would place string at the right side of the screen
|
||||
func PadLeft(s, pad string, width int) string {
|
||||
gap := width - DisplayWidth(s)
|
||||
if gap > 0 {
|
||||
return strings.Repeat(string(pad), gap) + s
|
||||
}
|
||||
return s
|
||||
}
|
99
vendor/github.com/olekukonko/tablewriter/wrap.go
generated
vendored
Normal file
99
vendor/github.com/olekukonko/tablewriter/wrap.go
generated
vendored
Normal file
@ -0,0 +1,99 @@
|
||||
// Copyright 2014 Oleku Konko All rights reserved.
|
||||
// Use of this source code is governed by a MIT
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// This module is a Table Writer API for the Go Programming Language.
|
||||
// The protocols were written in pure Go and works on windows and unix systems
|
||||
|
||||
package tablewriter
|
||||
|
||||
import (
|
||||
"math"
|
||||
"strings"
|
||||
|
||||
"github.com/mattn/go-runewidth"
|
||||
)
|
||||
|
||||
var (
|
||||
nl = "\n"
|
||||
sp = " "
|
||||
)
|
||||
|
||||
const defaultPenalty = 1e5
|
||||
|
||||
// Wrap wraps s into a paragraph of lines of length lim, with minimal
|
||||
// raggedness.
|
||||
func WrapString(s string, lim int) ([]string, int) {
|
||||
words := strings.Split(strings.Replace(s, nl, sp, -1), sp)
|
||||
var lines []string
|
||||
max := 0
|
||||
for _, v := range words {
|
||||
max = runewidth.StringWidth(v)
|
||||
if max > lim {
|
||||
lim = max
|
||||
}
|
||||
}
|
||||
for _, line := range WrapWords(words, 1, lim, defaultPenalty) {
|
||||
lines = append(lines, strings.Join(line, sp))
|
||||
}
|
||||
return lines, lim
|
||||
}
|
||||
|
||||
// WrapWords is the low-level line-breaking algorithm, useful if you need more
|
||||
// control over the details of the text wrapping process. For most uses,
|
||||
// WrapString will be sufficient and more convenient.
|
||||
//
|
||||
// WrapWords splits a list of words into lines with minimal "raggedness",
|
||||
// treating each rune as one unit, accounting for spc units between adjacent
|
||||
// words on each line, and attempting to limit lines to lim units. Raggedness
|
||||
// is the total error over all lines, where error is the square of the
|
||||
// difference of the length of the line and lim. Too-long lines (which only
|
||||
// happen when a single word is longer than lim units) have pen penalty units
|
||||
// added to the error.
|
||||
func WrapWords(words []string, spc, lim, pen int) [][]string {
|
||||
n := len(words)
|
||||
|
||||
length := make([][]int, n)
|
||||
for i := 0; i < n; i++ {
|
||||
length[i] = make([]int, n)
|
||||
length[i][i] = runewidth.StringWidth(words[i])
|
||||
for j := i + 1; j < n; j++ {
|
||||
length[i][j] = length[i][j-1] + spc + runewidth.StringWidth(words[j])
|
||||
}
|
||||
}
|
||||
nbrk := make([]int, n)
|
||||
cost := make([]int, n)
|
||||
for i := range cost {
|
||||
cost[i] = math.MaxInt32
|
||||
}
|
||||
for i := n - 1; i >= 0; i-- {
|
||||
if length[i][n-1] <= lim {
|
||||
cost[i] = 0
|
||||
nbrk[i] = n
|
||||
} else {
|
||||
for j := i + 1; j < n; j++ {
|
||||
d := lim - length[i][j-1]
|
||||
c := d*d + cost[j]
|
||||
if length[i][j-1] > lim {
|
||||
c += pen // too-long lines get a worse penalty
|
||||
}
|
||||
if c < cost[i] {
|
||||
cost[i] = c
|
||||
nbrk[i] = j
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var lines [][]string
|
||||
i := 0
|
||||
for i < n {
|
||||
lines = append(lines, words[i:nbrk[i]])
|
||||
i = nbrk[i]
|
||||
}
|
||||
return lines
|
||||
}
|
||||
|
||||
// getLines decomposes a multiline string into a slice of strings.
|
||||
func getLines(s string) []string {
|
||||
return strings.Split(s, nl)
|
||||
}
|
58
vendor/github.com/olekukonko/tablewriter/wrap_test.go
generated
vendored
Normal file
58
vendor/github.com/olekukonko/tablewriter/wrap_test.go
generated
vendored
Normal file
@ -0,0 +1,58 @@
|
||||
// Copyright 2014 Oleku Konko All rights reserved.
|
||||
// Use of this source code is governed by a MIT
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// This module is a Table Writer API for the Go Programming Language.
|
||||
// The protocols were written in pure Go and works on windows and unix systems
|
||||
|
||||
package tablewriter
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/mattn/go-runewidth"
|
||||
)
|
||||
|
||||
var text = "The quick brown fox jumps over the lazy dog."
|
||||
|
||||
func TestWrap(t *testing.T) {
|
||||
exp := []string{
|
||||
"The", "quick", "brown", "fox",
|
||||
"jumps", "over", "the", "lazy", "dog."}
|
||||
|
||||
got, _ := WrapString(text, 6)
|
||||
checkEqual(t, len(got), len(exp))
|
||||
}
|
||||
|
||||
func TestWrapOneLine(t *testing.T) {
|
||||
exp := "The quick brown fox jumps over the lazy dog."
|
||||
words, _ := WrapString(text, 500)
|
||||
checkEqual(t, strings.Join(words, string(sp)), exp)
|
||||
|
||||
}
|
||||
|
||||
func TestUnicode(t *testing.T) {
|
||||
input := "Česká řeřicha"
|
||||
var wordsUnicode []string
|
||||
if runewidth.IsEastAsian() {
|
||||
wordsUnicode, _ = WrapString(input, 14)
|
||||
} else {
|
||||
wordsUnicode, _ = WrapString(input, 13)
|
||||
}
|
||||
// input contains 13 (or 14 for CJK) runes, so it fits on one line.
|
||||
checkEqual(t, len(wordsUnicode), 1)
|
||||
}
|
||||
|
||||
func TestDisplayWidth(t *testing.T) {
|
||||
input := "Česká řeřicha"
|
||||
want := 13
|
||||
if runewidth.IsEastAsian() {
|
||||
want = 14
|
||||
}
|
||||
if n := DisplayWidth(input); n != want {
|
||||
t.Errorf("Wants: %d Got: %d", want, n)
|
||||
}
|
||||
input = "\033[43;30m" + input + "\033[00m"
|
||||
checkEqual(t, DisplayWidth(input), want)
|
||||
}
|
3
vendor/modules.txt
vendored
3
vendor/modules.txt
vendored
@ -1200,3 +1200,6 @@ sigs.k8s.io/yaml
|
||||
# peridot.resf.org/peridot/yumrepofs/pb => ./bazel-bin/peridot/proto/v1/yumrepofs/yumrepofspb_go_proto_/peridot.resf.org/peridot/yumrepofs/pb
|
||||
# peridot.resf.org/common => ./bazel-bin/proto/commonpb_go_proto_/peridot.resf.org/common
|
||||
# github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2/options => ./bazel-bin/protoc-gen-openapiv2/options/options_go_proto_/github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2/options
|
||||
# github.com/olekukonko/tablewriter v0.0.5
|
||||
## explicit; go 1.13
|
||||
github.com/olekukonko/tablewriter
|
||||
|
Loading…
Reference in New Issue
Block a user