Browse Source

First version @ 2020-05-18 01:53

zry 4 years ago
parent
commit
adac402cad
3 changed files with 248 additions and 0 deletions
  1. 10 0
      coldef.go
  2. 48 0
      example/simple_example/main.go
  3. 190 0
      term_table_printer.go

+ 10 - 0
coldef.go

@@ -0,0 +1,10 @@
+package ztermtab
+
+type columnDefine struct {
+	title            string
+	minWidth         int
+	maxWidth         int
+	truncateOverflow bool
+	autoWidth        bool
+	currentWidth     int
+}

+ 48 - 0
example/simple_example/main.go

@@ -0,0 +1,48 @@
+package main
+
+import (
+	"fmt"
+	"git.swzry.com/zry/ztermtab"
+)
+
+func main() {
+	test1()
+}
+
+func test1() {
+	// 两列,第二列自由宽度的示例
+	ttp := ztermtab.NewTerminalTablePrinter(80, " | ")
+	// 参数说明:
+	// title: 标题
+	// minWidth: 最小宽度。对于自由列,该参数无效。
+	// maxWidth: 最大宽度。请提前规划好各列最大宽度,应确保所有列最大宽度的和加上所有分隔符宽度小于终端宽度。
+	//           对于自由列,该参数无效。另:若表格有自由列,则需要确保所有列的最大宽度的和加上所有分隔符宽
+	//           度,再加上自由列的最小宽度仍然小于终端宽度。
+	// truncateOverflow: 截断超长串。若为true,超出maxWidth的字符串会被截断;若为false,
+	//                   则超出maxWidth的字符串会换行。
+	ttp.AddColumn("command", 10, 20, false)
+	ttp.AddColumn("description", 0, 0, false)
+	// 设置第二列为自由列,索引从0开始
+	ttp.SetAutoWidthColumn(1)
+	// 在开始添加第一行数据以后,AddColumn和SetAutoWidthColumen不再起作用。
+	ttp.AddRow([]string{
+		"aaa",
+		"设置3A (Authentication & Authorization & Accounting)认证相关的选项",
+	})
+	ttp.AddRow([]string{
+		"dhcp",
+		"DHCP Settings",
+	})
+	ttp.AddRow([]string{
+		"emoji😀",
+		"😂😂😂😂😂😂💻🖥🖨⌨🖱🖲💾💿📼📹📡📽🎬👩👨🧑👧👦🧒👶☝👇👈👉💪💪💪🤘🖐✋👌👍👎✊",
+	})
+	lines := ttp.RenderToTerminalLines()
+	printLines(lines)
+}
+
+func printLines(lines []string) {
+	for _, v := range lines {
+		fmt.Println(v)
+	}
+}

+ 190 - 0
term_table_printer.go

@@ -0,0 +1,190 @@
+package ztermtab
+
+import (
+	"github.com/mattn/go-runewidth"
+	"strings"
+)
+
+type TerminalTablePrinter struct {
+	colsdef       []columnDefine
+	colCnt        int
+	schLock       bool
+	rowData       [][]string
+	termWidth     int
+	hasFreeLine   bool
+	freeLineIndex int
+	split         string
+	splitWidth    int
+}
+
+func NewTerminalTablePrinter(termWidth int, splitString string) *TerminalTablePrinter {
+	ttp := &TerminalTablePrinter{
+		colsdef:     make([]columnDefine, 0),
+		colCnt:      0,
+		schLock:     false,
+		termWidth:   termWidth,
+		hasFreeLine: false,
+		split:       splitString,
+		splitWidth:  runewidth.StringWidth(splitString),
+	}
+	return ttp
+}
+
+func (p *TerminalTablePrinter) AddColumn(title string, minWidth int, maxWidth int, truncateOverflow bool) {
+	if p.schLock {
+		return
+	}
+	p.colsdef = append(p.colsdef, columnDefine{
+		title:            title,
+		minWidth:         minWidth,
+		maxWidth:         maxWidth,
+		truncateOverflow: truncateOverflow,
+		autoWidth:        false,
+	})
+	p.colCnt++
+}
+
+func (p *TerminalTablePrinter) SetAutoWidthColumn(index int) {
+	if p.schLock {
+		return
+	}
+	if index >= p.colCnt {
+		return
+	}
+	for i := range p.colsdef {
+		if i == index {
+			p.colsdef[i].autoWidth = true
+		} else {
+			p.colsdef[i].autoWidth = false
+		}
+	}
+	p.freeLineIndex = index
+	p.hasFreeLine = true
+}
+
+func (p *TerminalTablePrinter) ClearData() {
+	p.schLock = true
+	p.rowData = make([][]string, 0)
+	for i := range p.colsdef {
+		p.colsdef[i].currentWidth = p.colsdef[i].minWidth
+		updateCurrentWidth(&p.colsdef[i], runewidth.StringWidth(p.colsdef[i].title))
+	}
+}
+
+func (p *TerminalTablePrinter) AddRow(data []string) {
+	if !p.schLock {
+		p.ClearData()
+	}
+	line := make([]string, p.colCnt)
+	for i, v := range data {
+		if i >= p.colCnt {
+			break
+		}
+		line[i] = v
+		updateCurrentWidth(&p.colsdef[i], runewidth.StringWidth(v))
+	}
+	p.rowData = append(p.rowData, line)
+}
+
+func (p *TerminalTablePrinter) RenderToTerminalLines() []string {
+	var fll int
+	if p.hasFreeLine {
+		nfll := 0
+		for i, v := range p.colsdef {
+			if !v.autoWidth {
+				nfll += v.currentWidth
+			}
+			if i < p.colCnt-1 {
+				nfll += p.splitWidth
+			}
+		}
+		if nfll < p.termWidth {
+			fll = p.termWidth - nfll
+		} else {
+			fll = 1
+		}
+	}
+	colwidtab := make([]int, p.colCnt)
+	for i, v := range p.colsdef {
+		if v.autoWidth {
+			colwidtab[i] = fll
+		} else {
+			colwidtab[i] = v.currentWidth
+		}
+	}
+	out := make([]string, 0, len(p.rowData))
+	for _, v := range p.rowData {
+		maxline := 0
+		sublines := make([][]string, len(v))
+		for j, cell := range v {
+			osl := printStringIntoLines(cell, colwidtab[j])
+			if len(osl) > maxline {
+				maxline = len(osl)
+			}
+			sublines[j] = osl
+		}
+		for k := 0; k < maxline; k++ {
+			sb := strings.Builder{}
+			for kj := 0; kj < p.colCnt; kj++ {
+				kjv := sublines[kj]
+				if k < len(kjv) {
+					sb.WriteString(fillString(kjv[k], colwidtab[kj]))
+				} else {
+					sb.WriteString(strings.Repeat(" ", colwidtab[kj]))
+				}
+				if kj != p.colCnt-1 {
+					sb.WriteString(p.split)
+				}
+			}
+			out = append(out, sb.String())
+		}
+	}
+	return out
+}
+
+func fillString(s string, fn int) string {
+	l := runewidth.StringWidth(s)
+	fill := fn - l
+	sb := strings.Builder{}
+	sb.WriteString(s)
+	if fill > 0 {
+		sb.WriteString(strings.Repeat(" ", fill))
+	}
+	return sb.String()
+}
+
+func updateCurrentWidth(cd *columnDefine, newwid int) {
+	if newwid > cd.currentWidth {
+		if newwid >= cd.maxWidth {
+			cd.currentWidth = cd.maxWidth
+		} else {
+			cd.currentWidth = newwid
+		}
+	}
+}
+
+func printStringIntoLines(s string, termWidth int) []string {
+	if runewidth.StringWidth(s) <= termWidth {
+		return []string{s}
+	}
+	rlns := []string{}
+	cln := strings.Builder{}
+	clcnt := 0
+	ra := []rune(s)
+	for _, v := range ra {
+		rw := runewidth.RuneWidth(v)
+		if rw+clcnt > termWidth {
+			rlns = append(rlns, cln.String())
+			cln = strings.Builder{}
+			cln.WriteRune(v)
+			clcnt = rw
+		} else {
+			cln.WriteRune(v)
+			clcnt += rw
+		}
+	}
+	if cln.Len() > 0 {
+		rlns = append(rlns, cln.String())
+	}
+	return rlns
+}