| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360 |
- // Copyright 2019 Yunion
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- package prettytable
- import (
- "bytes"
- "strings"
- "unicode"
- )
- type AlignmentType uint8
- const (
- AlignLeft AlignmentType = iota
- AlignCenter
- AlignRight
- )
- const TabWidth = 8
- type ptColumn struct {
- Title string
- Align AlignmentType
- }
- type PrettyTable struct {
- columns []ptColumn
- maxLineWidth int
- tryTermWidth bool
- }
- func NewPrettyTable(fields []string) *PrettyTable {
- var pt = PrettyTable{
- tryTermWidth: true,
- }
- for _, field := range fields {
- col := ptColumn{Title: field, Align: AlignLeft}
- pt.columns = append(pt.columns, col)
- }
- return &pt
- }
- func NewPrettyTableWithTryTermWidth(fields []string, tryTermWidth bool) *PrettyTable {
- pt := NewPrettyTable(fields)
- pt.tryTermWidth = tryTermWidth
- return pt
- }
- func rowLine(buf *bytes.Buffer, widths []int) {
- buf.WriteByte('+')
- for _, w := range widths {
- buf.WriteByte('-')
- for i := 0; i < w; i++ {
- buf.WriteByte('-')
- }
- buf.WriteByte('-')
- buf.WriteByte('+')
- }
- buf.WriteByte('\n')
- }
- func textCell(buf *bytes.Buffer, col ptColumn, width int, nthCol int, prevWidth int) {
- var padLeft, padRight int
- celWidth := cellDisplayWidth(col.Title, nthCol, prevWidth)
- switch col.Align {
- case AlignLeft:
- padLeft = 0
- padRight = width - celWidth
- case AlignRight:
- padLeft = width - celWidth
- padRight = 0
- default: //case AlignCenter:
- padLeft = (width - celWidth) / 2
- padRight = width - padLeft - celWidth
- }
- for i := 0; i < padLeft; i++ {
- buf.WriteByte(' ')
- }
- buf.WriteString(col.Title)
- for i := 0; i < padRight; i++ {
- buf.WriteByte(' ')
- }
- }
- func textLine(buf *bytes.Buffer, columns []ptColumn, widths []int) {
- nlines := 0
- splitted := make([][]string, len(columns))
- for i, column := range columns {
- title := strings.TrimRight(column.Title, "\n")
- s := strings.Split(title, "\n")
- splitted[i] = s
- if nlines < len(s) {
- nlines = len(s)
- }
- }
- for j := 0; j < nlines; j++ {
- buf.WriteByte('|')
- widthAcc := 0
- for i, w := range widths {
- buf.WriteByte(' ')
- var text string
- if j < len(splitted[i]) {
- text = splitted[i][j]
- }
- c := ptColumn{
- Title: text,
- Align: columns[i].Align,
- }
- textCell(buf, c, w, i, widthAcc)
- widthAcc += w
- buf.WriteByte(' ')
- buf.WriteByte('|')
- }
- buf.WriteByte('\n')
- }
- }
- func runeDisplayWidth(r rune) int {
- const puncts = "。,;:()、?《》"
- if unicode.Is(unicode.Han, r) {
- return 2
- }
- if strings.ContainsRune(puncts, r) {
- return 2
- }
- return 1
- }
- // cellDisplayWidth returns display width of the cell content (excluding bars
- // and paddings) when printed as the nthCol.
- //
- // nthCol is 0-based
- //
- // prevWidth is the total display width (as return by this same func) of
- // previous cells in the same line. It will be used to calculate width of tab
- // characters
- func cellDisplayWidth(cell string, nthCol int, prevWidth int) int {
- // `| ` at the front and ` | ` in the middle
- sX := prevWidth + nthCol*3 + 2
- width := 0
- lines := strings.Split(cell, "\n")
- for _, line := range lines {
- displayWidth := 0
- x := sX
- for _, c := range line {
- incr := 0
- if c != '\t' {
- incr = runeDisplayWidth(c)
- } else {
- // terminal with have the char TabWidth aligned
- incr = TabWidth - (x & (TabWidth - 1))
- }
- displayWidth += incr
- x += incr
- }
- if width < displayWidth {
- width = displayWidth
- }
- }
- return width
- }
- func wrapCell(cell string, nthCol int, prevWidth int, newWidth int) string {
- sX := prevWidth + nthCol*3 + 2
- var (
- wrapped = false
- newCell = ""
- cline = make([]rune, 0, newWidth+1)
- cw = 0
- cx = sX
- )
- for _, c := range cell {
- var incr int
- if c == '\n' {
- cline = append(cline, c)
- newCell += string(cline)
- cline = cline[:0]
- cw = 0
- cx = sX
- continue
- } else if c != '\t' {
- incr = runeDisplayWidth(c)
- } else {
- // terminal with have the char TabWidth aligned
- incr = TabWidth - (cx & (TabWidth - 1))
- }
- t := cw + incr
- if t < newWidth {
- cline = append(cline, c)
- cw += incr
- cx += incr
- } else if t == newWidth {
- cline = append(cline, c, '\n')
- newCell += string(cline)
- cline = cline[:0]
- cw = 0
- cx = sX
- if !wrapped {
- wrapped = true
- }
- } else {
- if len(cline) == 0 {
- newCell += string(c) + "\n"
- } else {
- newCell += string(cline) + "\n"
- cline = cline[:1]
- cline[0] = c
- }
- cw = 0
- cx = sX
- if !wrapped {
- wrapped = true
- }
- }
- }
- if len(cline) > 0 {
- newCell += string(cline)
- } else if !wrapped && newCell != "" {
- l := len(newCell) - 1
- c := newCell[l]
- if c == '\n' {
- newCell = newCell[:l]
- }
- }
- return newCell
- }
- func (this *PrettyTable) MaxLineWidth(w int) {
- this.maxLineWidth = w
- }
- func (this *PrettyTable) hasMaxLineWidth() (int, bool) {
- if this.maxLineWidth > 0 {
- return this.maxLineWidth, true
- }
- if this.tryTermWidth {
- w, err := termWidth()
- if err != nil {
- return 0, false
- }
- return w, true
- }
- return 0, false
- }
- func (this *PrettyTable) GetString(fields [][]string) string {
- if len(fields) == 0 {
- return ""
- }
- var (
- widths = make([]int, len(this.columns))
- widthAcc = 0
- )
- {
- // width of title columns
- widthAcc = 0
- for i, c := range this.columns {
- widths[i] = cellDisplayWidth(c.Title, i, widthAcc)
- widthAcc += widths[i]
- }
- }
- {
- // width of content columns
- widthAcc = 0
- for col := 0; ; col++ {
- cont := false
- colWidth := 0
- for _, line := range fields {
- if col < len(line) {
- cont = true
- cw := cellDisplayWidth(line[col], col, widthAcc)
- if colWidth < cw {
- colWidth = cw
- }
- }
- }
- if cont {
- if widths[col] < colWidth {
- widths[col] = colWidth
- }
- widthAcc += widths[col]
- } else {
- break
- }
- }
- }
- if maxLineWidth, ok := this.hasMaxLineWidth(); ok {
- nCols := len(this.columns)
- maxContWidth := maxLineWidth - 3*nCols - 1
- if maxContWidth < nCols {
- // ensure at least 1 ascii char print
- maxContWidth = nCols
- }
- if widthAcc > maxContWidth {
- assured := maxContWidth / len(this.columns)
- rem := maxContWidth
- nwrap := 0
- for _, w := range widths {
- if w <= assured {
- rem -= w
- } else {
- nwrap += 1
- }
- }
- // break long lines
- widthAcc := 0
- wrapped := rem / nwrap
- wrappedRem := rem % nwrap
- for col, w := range widths {
- if w > assured {
- newW := wrapped
- if wrappedRem > 0 {
- newW += 1
- wrappedRem -= 1
- }
- this.columns[col].Title = wrapCell(this.columns[col].Title, col, widthAcc, newW)
- for _, line := range fields {
- line[col] = wrapCell(line[col], col, widthAcc, newW)
- }
- widths[col] = newW // later tabs may need correction
- widthAcc += newW
- } else {
- widthAcc += w
- }
- }
- }
- }
- var columns = make([]ptColumn, len(this.columns))
- var buf bytes.Buffer
- rowLine(&buf, widths)
- for i := 0; i < len(columns); i++ {
- columns[i].Title = this.columns[i].Title
- columns[i].Align = AlignCenter
- }
- textLine(&buf, columns, widths)
- rowLine(&buf, widths)
- for _, line := range fields {
- for i := 0; i < len(line); i++ {
- columns[i].Title = line[i]
- columns[i].Align = this.columns[i].Align
- }
- textLine(&buf, columns, widths)
- }
- rowLine(&buf, widths)
- return buf.String()
- }
|