package common import ( "fmt" "strings" "unicode/utf8" "github.com/olekukonko/tablewriter" ) const ( TruncSuffix = iota TruncPrefix TruncMiddle ) type WrapperTable struct { Labels []string Fields []string FieldsSize map[string][3]int // 列宽,列最小宽,列最大宽 Data []map[string]string TotalSize int TruncPolicy int totalSize int paddingSize int bolderSize int fieldsSize map[string]int // 计算后的最终宽度 Caption string } func (t *WrapperTable) Initial() { // 如果设置的宽度小于title的size, 重写 if t.Labels == nil { t.Labels = t.Fields } if len(t.Fields) != len(t.Labels) { panic("Labels should be equal with Fields") } t.paddingSize = 1 t.bolderSize = 1 for i, k := range t.Fields { titleSize := len(t.Labels[i]) sizeDefine := t.FieldsSize[k] if titleSize > sizeDefine[1] { sizeDefine[1] = titleSize t.FieldsSize[k] = sizeDefine } } } func (t *WrapperTable) CalculateColumnsSize() { t.fieldsSize = make(map[string]int) dataColMaxSize := make(map[string]int) for _, row := range t.Data { for _, colName := range t.Fields { if colValue, ok := row[colName]; ok { preSize, ok := dataColMaxSize[colName] colSize := len(colValue) if !ok || colSize > preSize { dataColMaxSize[colName] = colSize } } } } // 如果数据宽度大于设置最大值,则设置为准 // 如果数据最大值小彧最小值,已最小值为列宽 // 否则数据最大宽度为列宽 for k, v := range dataColMaxSize { size, min, max := t.FieldsSize[k][0], t.FieldsSize[k][1], t.FieldsSize[k][2] if size != 0 { t.fieldsSize[k] = size } else if max != 0 && v > max { t.fieldsSize[k] = max } else if min != 0 && v < min { t.fieldsSize[k] = min } else { t.fieldsSize[k] = v } } // 计算后列总长度 calSize := 0 for _, v := range t.fieldsSize { calSize += v } if t.TotalSize == 0 { t.totalSize = calSize return } // 总宽度计算时应当减去 border和padding t.totalSize = t.TotalSize - len(t.Fields)*2*t.paddingSize - (len(t.Fields)+1)*t.bolderSize // 计算可以扩容和缩容的列 delta := t.totalSize - calSize if delta == 0 { return } var step = 1 if delta < 0 { step = -1 } delta = Abs(delta) for delta > 0 { canChangeCols := make([]string, 0) for k, v := range t.FieldsSize { size, min, max := v[0], v[1], v[2] switch step { // 扩容 case 1: if size != 0 || (max != 0 && t.fieldsSize[k] >= max) { continue } // 缩容 case -1: if size != 0 || t.fieldsSize[k] <= min { continue } } canChangeCols = append(canChangeCols, k) } if len(canChangeCols) == 0 { break } for _, k := range canChangeCols { t.fieldsSize[k] += step delta-- if delta == 0 { break } } } } func (t *WrapperTable) convertDataToSlice() [][]string { data := make([][]string, len(t.Data)) for i, j := range t.Data { row := make([]string, len(t.Fields)) for m, n := range t.Fields { columSize := t.fieldsSize[n] if len(j[n]) <= columSize { row[m] = j[n] } else { switch t.TruncPolicy { case TruncSuffix: row[m] = fmt.Sprintf("%s...", GetValidString(j[n], columSize-3, true)) case TruncPrefix: row[m] = fmt.Sprintf("...%s", GetValidString(j[n], len(j[n])-columSize-3, false)) case TruncMiddle: midValue := (columSize - 3) / 2 row[m] = fmt.Sprintf("%s...%s", GetValidString(j[n], midValue, true), GetValidString(j[n], len(j[n])-midValue, false)) } } } data[i] = row } return data } func (t *WrapperTable) Display() string { t.CalculateColumnsSize() tableString := strings.Builder{} table := tablewriter.NewWriter(&tableString) table.SetBorder(false) table.SetHeader(t.Labels) colors := make([]tablewriter.Colors, len(t.Fields)) for i := 0; i < len(t.Fields); i++ { colors[i] = tablewriter.Colors{tablewriter.Bold, tablewriter.FgGreenColor} } table.SetHeaderColor(colors...) data := t.convertDataToSlice() table.AppendBulk(data) table.SetHeaderAlignment(tablewriter.ALIGN_LEFT) table.SetAlignment(tablewriter.ALIGN_LEFT) for i, j := range t.Fields { n := t.fieldsSize[j] table.SetColMinWidth(i, n) } table.SetColWidth(t.totalSize) if t.Caption != "" { table.SetCaption(true, t.Caption) } table.Render() return tableString.String() } func GetValidString(s string, position int, positive bool) string { step := 1 if positive { step = -1 } for position >= 0 && position <= len(s) { switch positive { case true: if utf8.ValidString(s[:position]) { return s[:position] } case false: if utf8.ValidString(s[position:]) { return s[position:] } } position += step } return "" }