1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168
|
// Copyright 2012 Apcera Inc. All rights reserved.
package termtables
import (
"fmt"
"math"
"regexp"
"strconv"
"strings"
"unicode/utf8"
runewidth "github.com/mattn/go-runewidth"
)
var (
// Must match SGR escape sequence, which is "CSI Pm m", where the Control
// Sequence Introducer (CSI) is "ESC ["; where Pm is "A multiple numeric
// parameter composed of any number of single numeric parameters, separated
// by ; character(s). Individual values for the parameters are listed with
// Ps" and where Ps is A single (usually optional) numeric parameter,
// composed of one of [sic] more digits."
//
// In practice, the end sequence is usually given as \e[0m but reading that
// definition, it's clear that the 0 is optional and some testing confirms
// that it is certainly optional with MacOS Terminal 2.3, so we need to
// support the string \e[m as a terminator too.
colorFilter = regexp.MustCompile(`\033\[(?:\d+(?:;\d+)*)?m`)
)
// A Cell denotes one cell of a table; it spans one row and a variable number
// of columns. A given Cell can only be used at one place in a table; the act
// of adding the Cell to the table mutates it with position information, so
// do not create one "const" Cell to add it multiple times.
type Cell struct {
column int
formattedValue string
alignment *TableAlignment
colSpan int
}
// CreateCell returns a Cell where the content is the supplied value, with the
// optional supplied style (which may be given as nil). The style can include
// a non-zero ColSpan to cause the cell to become column-spanning. Changing
// the style afterwards will not adjust the column-spanning state of the cell
// itself.
func CreateCell(v interface{}, style *CellStyle) *Cell {
return createCell(0, v, style)
}
func createCell(column int, v interface{}, style *CellStyle) *Cell {
cell := &Cell{column: column, formattedValue: renderValue(v), colSpan: 1}
if style != nil {
cell.alignment = &style.Alignment
if style.ColSpan != 0 {
cell.colSpan = style.ColSpan
}
}
return cell
}
// Width returns the width of the content of the cell, measured in runes as best
// as possible considering sophisticated Unicode.
func (c *Cell) Width() int {
return runewidth.StringWidth(filterColorCodes(c.formattedValue))
}
// Filter out terminal bold/color sequences in a string.
// This supports only basic bold/color escape sequences.
func filterColorCodes(s string) string {
return colorFilter.ReplaceAllString(s, "")
}
// Render returns a string representing the content of the cell, together with
// padding (to the widths specified) and handling any alignment.
func (c *Cell) Render(style *renderStyle) (buffer string) {
// if no alignment is set, import the table's default
if c.alignment == nil {
c.alignment = &style.Alignment
}
// left padding
buffer += strings.Repeat(" ", style.PaddingLeft)
// append the main value and handle alignment
buffer += c.alignCell(style)
// right padding
buffer += strings.Repeat(" ", style.PaddingRight)
// this handles escaping for, eg, Markdown, where we don't care about the
// alignment quite as much
if style.replaceContent != nil {
buffer = style.replaceContent(buffer)
}
return buffer
}
func (c *Cell) alignCell(style *renderStyle) string {
buffer := ""
width := style.CellWidth(c.column)
if c.colSpan > 1 {
for i := 1; i < c.colSpan; i++ {
w := style.CellWidth(c.column + i)
if w == 0 {
break
}
width += style.PaddingLeft + w + style.PaddingRight + utf8.RuneCountInString(style.BorderY)
}
}
switch *c.alignment {
default:
buffer += c.formattedValue
if l := width - c.Width(); l > 0 {
buffer += strings.Repeat(" ", l)
}
case AlignLeft:
buffer += c.formattedValue
if l := width - c.Width(); l > 0 {
buffer += strings.Repeat(" ", l)
}
case AlignRight:
if l := width - c.Width(); l > 0 {
buffer += strings.Repeat(" ", l)
}
buffer += c.formattedValue
case AlignCenter:
left, right := 0, 0
if l := width - c.Width(); l > 0 {
lf := float64(l)
left = int(math.Floor(lf / 2))
right = int(math.Ceil(lf / 2))
}
buffer += strings.Repeat(" ", left)
buffer += c.formattedValue
buffer += strings.Repeat(" ", right)
}
return buffer
}
// Format the raw value as a string depending on the type
func renderValue(v interface{}) string {
switch vv := v.(type) {
case string:
return vv
case bool:
return strconv.FormatBool(vv)
case int:
return strconv.Itoa(vv)
case int64:
return strconv.FormatInt(vv, 10)
case uint64:
return strconv.FormatUint(vv, 10)
case float64:
return strconv.FormatFloat(vv, 'f', 2, 64)
case fmt.Stringer:
return vv.String()
}
return fmt.Sprintf("%v", v)
}
|