2020-08-19 23:46:04 +03:00
|
|
|
// Package tablew implements text table output writers.
|
|
|
|
//
|
|
|
|
// The actual rendering of the text table is handled by a modified
|
|
|
|
// version ofolekukonko/tablewriter which can be found in the internal
|
|
|
|
// sub-package. At the time, tablewriter didn't provide all the
|
|
|
|
// functionality that sq required. However, that package has been
|
|
|
|
// significantly developed since then fork, and it may be possible
|
|
|
|
// that we could dispense with the forked version entirely and directly
|
|
|
|
// use a newer version of tablewriter.
|
|
|
|
//
|
|
|
|
// This entire package could use a rewrite, a lot has changed with sq
|
|
|
|
// since this package was first created. So, if you see code in here
|
|
|
|
// that doesn't make sense to you, you're probably judging it correctly.
|
2020-08-06 20:58:47 +03:00
|
|
|
package tablew
|
|
|
|
|
|
|
|
import (
|
|
|
|
"database/sql"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"strconv"
|
|
|
|
"time"
|
|
|
|
|
2023-05-01 06:59:34 +03:00
|
|
|
"github.com/fatih/color"
|
|
|
|
|
2020-08-06 20:58:47 +03:00
|
|
|
"github.com/neilotoole/sq/cli/output"
|
|
|
|
"github.com/neilotoole/sq/cli/output/tablew/internal"
|
2020-08-23 13:42:15 +03:00
|
|
|
"github.com/neilotoole/sq/libsq/core/kind"
|
|
|
|
"github.com/neilotoole/sq/libsq/core/stringz"
|
2020-08-06 20:58:47 +03:00
|
|
|
)
|
|
|
|
|
2020-10-20 18:05:43 +03:00
|
|
|
// table encapsulates our table implementation.
|
2020-08-06 20:58:47 +03:00
|
|
|
type table struct {
|
2023-04-22 06:36:32 +03:00
|
|
|
pr *output.Printing
|
2020-08-06 20:58:47 +03:00
|
|
|
out io.Writer
|
|
|
|
header bool
|
|
|
|
|
|
|
|
tblImpl *internal.Table
|
|
|
|
}
|
|
|
|
|
2022-12-18 09:42:11 +03:00
|
|
|
func (t *table) renderResultCell(knd kind.Kind, val any) string { //nolint:funlen,cyclop,gocognit,gocyclo
|
2020-08-06 20:58:47 +03:00
|
|
|
switch val := val.(type) {
|
2023-05-27 16:57:07 +03:00
|
|
|
case nil:
|
|
|
|
return t.sprintNull()
|
2020-08-06 20:58:47 +03:00
|
|
|
case *sql.NullString:
|
|
|
|
if !val.Valid {
|
|
|
|
return t.sprintNull()
|
|
|
|
}
|
2023-04-22 06:36:32 +03:00
|
|
|
return t.pr.String.Sprint(val.String)
|
2023-05-27 16:57:07 +03:00
|
|
|
case string:
|
2020-08-09 16:46:46 +03:00
|
|
|
|
|
|
|
// Although val is string, we allow for the case where
|
2020-08-23 13:42:15 +03:00
|
|
|
// the kind is not kind.Text: for example, sqlite returns
|
|
|
|
// values of kind.Time as a string.
|
2020-08-09 16:46:46 +03:00
|
|
|
|
2022-12-18 03:51:33 +03:00
|
|
|
switch knd { //nolint:exhaustive // ignore kind.Unknown and kind.Null
|
2020-08-23 13:42:15 +03:00
|
|
|
case kind.Datetime, kind.Date, kind.Time:
|
2023-05-27 16:57:07 +03:00
|
|
|
return t.pr.Datetime.Sprint(val)
|
2020-08-23 13:42:15 +03:00
|
|
|
case kind.Decimal, kind.Float, kind.Int:
|
2023-05-27 16:57:07 +03:00
|
|
|
return t.pr.Number.Sprint(val)
|
2020-08-23 13:42:15 +03:00
|
|
|
case kind.Bool:
|
2023-05-27 16:57:07 +03:00
|
|
|
return t.pr.Bool.Sprint(val)
|
2020-08-23 13:42:15 +03:00
|
|
|
case kind.Bytes:
|
2023-05-27 16:57:07 +03:00
|
|
|
return t.sprintBytes([]byte(val))
|
2020-08-23 13:42:15 +03:00
|
|
|
case kind.Text:
|
2023-05-27 16:57:07 +03:00
|
|
|
return t.pr.String.Sprint(val)
|
2020-08-09 16:46:46 +03:00
|
|
|
default:
|
|
|
|
// Shouldn't happen
|
2023-05-27 16:57:07 +03:00
|
|
|
return val
|
2020-08-09 16:46:46 +03:00
|
|
|
}
|
|
|
|
|
2020-08-06 20:58:47 +03:00
|
|
|
case float64:
|
|
|
|
return t.sprintFloat64(val)
|
|
|
|
case *float64:
|
|
|
|
if val == nil {
|
|
|
|
return t.sprintNull()
|
|
|
|
}
|
|
|
|
return t.sprintFloat64(*val)
|
|
|
|
case *sql.NullFloat64:
|
|
|
|
if !val.Valid {
|
|
|
|
return t.sprintNull()
|
|
|
|
}
|
|
|
|
return t.sprintFloat64(val.Float64)
|
|
|
|
case float32:
|
|
|
|
return t.sprintFloat64(float64(val))
|
|
|
|
case *float32:
|
|
|
|
if val == nil {
|
|
|
|
return t.sprintNull()
|
|
|
|
}
|
|
|
|
return t.sprintFloat64(float64(*val))
|
|
|
|
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
|
|
|
|
s := fmt.Sprintf("%d", val)
|
2023-04-22 06:36:32 +03:00
|
|
|
return t.pr.Number.Sprint(s)
|
2020-08-06 20:58:47 +03:00
|
|
|
case *int:
|
|
|
|
if val == nil {
|
|
|
|
return t.sprintNull()
|
|
|
|
}
|
|
|
|
return t.sprintInt64(int64(*val))
|
|
|
|
case *int8:
|
|
|
|
if val == nil {
|
|
|
|
return t.sprintNull()
|
|
|
|
}
|
|
|
|
return t.sprintInt64(int64(*val))
|
|
|
|
case *int16:
|
|
|
|
if val == nil {
|
|
|
|
return t.sprintNull()
|
|
|
|
}
|
|
|
|
return t.sprintInt64(int64(*val))
|
|
|
|
case *int32:
|
|
|
|
if val == nil {
|
|
|
|
return t.sprintNull()
|
|
|
|
}
|
|
|
|
return t.sprintInt64(int64(*val))
|
|
|
|
case *int64:
|
|
|
|
if val == nil {
|
|
|
|
return t.sprintNull()
|
|
|
|
}
|
|
|
|
return t.sprintInt64(*val)
|
|
|
|
case *uint:
|
|
|
|
if val == nil {
|
|
|
|
return t.sprintNull()
|
|
|
|
}
|
|
|
|
return t.sprintInt64(int64(*val))
|
|
|
|
case *uint8:
|
|
|
|
if val == nil {
|
|
|
|
return t.sprintNull()
|
|
|
|
}
|
|
|
|
return t.sprintInt64(int64(*val))
|
|
|
|
case *uint16:
|
|
|
|
if val == nil {
|
|
|
|
return t.sprintNull()
|
|
|
|
}
|
|
|
|
return t.sprintInt64(int64(*val))
|
|
|
|
case *uint32:
|
|
|
|
if val == nil {
|
|
|
|
return t.sprintNull()
|
|
|
|
}
|
|
|
|
return t.sprintInt64(int64(*val))
|
|
|
|
case *uint64:
|
|
|
|
if val == nil {
|
|
|
|
return t.sprintNull()
|
|
|
|
}
|
|
|
|
return t.sprintInt64(int64(*val))
|
|
|
|
case *sql.NullInt64:
|
|
|
|
if !val.Valid {
|
|
|
|
return t.sprintNull()
|
|
|
|
}
|
|
|
|
return t.sprintInt64(val.Int64)
|
|
|
|
case bool:
|
2023-04-22 06:36:32 +03:00
|
|
|
return t.pr.Bool.Sprint(strconv.FormatBool(val))
|
2020-08-06 20:58:47 +03:00
|
|
|
case *bool:
|
|
|
|
if val == nil {
|
|
|
|
return t.sprintNull()
|
|
|
|
}
|
2023-04-22 06:36:32 +03:00
|
|
|
return t.pr.Bool.Sprint(strconv.FormatBool(*val))
|
2023-05-27 16:57:07 +03:00
|
|
|
case time.Time:
|
|
|
|
if val.IsZero() {
|
2020-08-06 20:58:47 +03:00
|
|
|
return t.sprintNull()
|
|
|
|
}
|
2020-08-09 16:46:46 +03:00
|
|
|
|
|
|
|
var s string
|
2022-12-18 08:16:10 +03:00
|
|
|
switch knd { //nolint:exhaustive
|
2020-08-09 16:46:46 +03:00
|
|
|
default:
|
2023-05-27 16:57:07 +03:00
|
|
|
s = t.pr.FormatDatetime(val)
|
2023-05-07 05:36:34 +03:00
|
|
|
|
|
|
|
// s = val.Format(timez.DefaultTimestampFormat)
|
2020-08-23 13:42:15 +03:00
|
|
|
case kind.Time:
|
2023-05-27 16:57:07 +03:00
|
|
|
s = t.pr.FormatTime(val)
|
2023-05-07 05:36:34 +03:00
|
|
|
// s = val.Format(timez.DefaultTimeFormat)
|
2020-08-23 13:42:15 +03:00
|
|
|
case kind.Date:
|
2023-05-27 16:57:07 +03:00
|
|
|
s = t.pr.FormatDate(val)
|
2023-05-07 05:36:34 +03:00
|
|
|
// s = val.Format(timez.DefaultDateFormat)
|
2020-08-09 16:46:46 +03:00
|
|
|
}
|
|
|
|
|
2023-04-22 06:36:32 +03:00
|
|
|
return t.pr.Datetime.Sprint(s)
|
2020-08-09 16:46:46 +03:00
|
|
|
|
2020-08-06 20:58:47 +03:00
|
|
|
case *sql.NullBool:
|
|
|
|
if !val.Valid {
|
|
|
|
return t.sprintNull()
|
|
|
|
}
|
2023-04-22 06:36:32 +03:00
|
|
|
return t.pr.Bool.Sprint(strconv.FormatBool(val.Bool))
|
2023-05-27 16:57:07 +03:00
|
|
|
|
2020-08-06 20:58:47 +03:00
|
|
|
case []byte:
|
|
|
|
return t.sprintBytes(val)
|
|
|
|
case *[]byte:
|
|
|
|
if val == nil || *val == nil {
|
|
|
|
return t.sprintNull()
|
|
|
|
}
|
|
|
|
return t.sprintBytes(*val)
|
|
|
|
case *sql.RawBytes:
|
|
|
|
if val == nil || *val == nil {
|
|
|
|
return t.sprintNull()
|
|
|
|
}
|
2020-08-23 13:42:15 +03:00
|
|
|
if knd == kind.Text {
|
2022-12-18 03:51:33 +03:00
|
|
|
return string(*val)
|
2020-08-06 20:58:47 +03:00
|
|
|
}
|
|
|
|
return t.sprintBytes(*val)
|
|
|
|
}
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *table) sprintBytes(b []byte) string {
|
2020-08-09 16:46:46 +03:00
|
|
|
s := fmt.Sprintf("[%d bytes]", len(b))
|
2023-04-22 06:36:32 +03:00
|
|
|
return t.pr.Bytes.Sprint(s)
|
2020-08-06 20:58:47 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
func (t *table) sprintNull() string {
|
2023-04-22 06:36:32 +03:00
|
|
|
return t.pr.Null.Sprint("NULL")
|
2020-08-06 20:58:47 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
func (t *table) sprintInt64(num int64) string {
|
2023-04-22 06:36:32 +03:00
|
|
|
return t.pr.Number.Sprint(strconv.FormatInt(num, 10))
|
2020-08-06 20:58:47 +03:00
|
|
|
}
|
2022-12-18 11:35:59 +03:00
|
|
|
|
2020-08-06 20:58:47 +03:00
|
|
|
func (t *table) sprintFloat64(num float64) string {
|
2023-04-22 06:36:32 +03:00
|
|
|
return t.pr.Number.Sprint(stringz.FormatFloat(num))
|
2020-08-06 20:58:47 +03:00
|
|
|
}
|
|
|
|
|
2023-04-19 08:28:09 +03:00
|
|
|
// reset resets the table internals.
|
2020-08-06 20:58:47 +03:00
|
|
|
func (t *table) reset() {
|
|
|
|
t.tblImpl = internal.NewTable(t.out)
|
|
|
|
t.setTableWriterOptions()
|
|
|
|
t.tblImpl.SetAutoFormatHeaders(false)
|
|
|
|
t.tblImpl.SetAutoWrapText(false)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *table) setTableWriterOptions() {
|
|
|
|
t.tblImpl.SetAlignment(internal.AlignLeft)
|
2020-10-20 18:05:43 +03:00
|
|
|
t.tblImpl.SetAutoWrapText(false)
|
2020-08-06 20:58:47 +03:00
|
|
|
t.tblImpl.SetBorder(false)
|
|
|
|
t.tblImpl.SetHeaderAlignment(internal.AlignLeft)
|
|
|
|
t.tblImpl.SetCenterSeparator("")
|
|
|
|
t.tblImpl.SetColumnSeparator("")
|
|
|
|
t.tblImpl.SetRowSeparator("")
|
|
|
|
t.tblImpl.SetBorders(internal.Border{Left: false, Top: false, Right: false, Bottom: false})
|
|
|
|
t.tblImpl.SetAutoFormatHeaders(false)
|
|
|
|
t.tblImpl.SetHeaderDisable(!t.header)
|
2023-04-22 06:36:32 +03:00
|
|
|
t.tblImpl.SetHeaderTrans(t.pr.Header.SprintFunc())
|
2020-08-06 20:58:47 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
func (t *table) appendRowsAndRenderAll(rows [][]string) {
|
|
|
|
for _, v := range rows {
|
|
|
|
t.tblImpl.Append(v)
|
|
|
|
}
|
|
|
|
t.tblImpl.RenderAll()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *table) appendRows(rows [][]string) {
|
|
|
|
for _, v := range rows {
|
|
|
|
t.tblImpl.Append(v)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *table) renderAll() {
|
|
|
|
t.tblImpl.RenderAll()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *table) renderRow(row []string) {
|
|
|
|
t.tblImpl.Append(row)
|
|
|
|
t.tblImpl.RenderAll() // Send output
|
|
|
|
}
|
2023-05-01 06:59:34 +03:00
|
|
|
|
|
|
|
func getColorForVal(pr *output.Printing, v any) *color.Color {
|
|
|
|
switch v.(type) {
|
|
|
|
case nil:
|
|
|
|
return pr.Null
|
|
|
|
case int, int64, float32, float64, uint, uint64:
|
|
|
|
return pr.Number
|
|
|
|
case bool:
|
|
|
|
return pr.Bool
|
|
|
|
case string:
|
|
|
|
return pr.String
|
|
|
|
case time.Time:
|
|
|
|
return pr.Datetime
|
|
|
|
case time.Duration:
|
|
|
|
return pr.Duration
|
|
|
|
default:
|
|
|
|
return pr.Normal
|
|
|
|
}
|
|
|
|
}
|