summaryrefslogtreecommitdiffstats
path: root/pkg/utils/formatting.go
blob: d33028063fceeaff9c193b2263e37740edb43e43 (plain)
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
package utils

import (
	"strings"

	"github.com/mattn/go-runewidth"
)

// WithPadding pads a string as much as you want
func WithPadding(str string, padding int) string {
	uncoloredStr := Decolorise(str)
	width := runewidth.StringWidth(uncoloredStr)
	if padding < width {
		return str
	}
	return str + strings.Repeat(" ", padding-width)
}

func RenderDisplayStrings(displayStringsArr [][]string) string {
	displayStringsArr = excludeBlankColumns(displayStringsArr)
	padWidths := getPadWidths(displayStringsArr)
	output := getPaddedDisplayStrings(displayStringsArr, padWidths)

	return output
}

// NOTE: this mutates the input slice for the sake of performance
func excludeBlankColumns(displayStringsArr [][]string) [][]string {
	if len(displayStringsArr) == 0 {
		return displayStringsArr
	}

	// if all rows share a blank column, we want to remove that column
	toRemove := []int{}
outer:
	for i := range displayStringsArr[0] {
		for _, strings := range displayStringsArr {
			if strings[i] != "" {
				continue outer
			}
		}
		toRemove = append(toRemove, i)
	}

	if len(toRemove) == 0 {
		return displayStringsArr
	}

	// remove the columns
	for i, strings := range displayStringsArr {
		for j := len(toRemove) - 1; j >= 0; j-- {
			strings = append(strings[:toRemove[j]], strings[toRemove[j]+1:]...)
		}
		displayStringsArr[i] = strings
	}

	return displayStringsArr
}

func getPaddedDisplayStrings(stringArrays [][]string, padWidths []int) string {
	builder := strings.Builder{}
	for i, stringArray := range stringArrays {
		if len(stringArray) == 0 {
			continue
		}
		for j, padWidth := range padWidths {
			if len(stringArray)-1 < j {
				continue
			}
			builder.WriteString(WithPadding(stringArray[j], padWidth))
			builder.WriteString(" ")
		}
		if len(stringArray)-1 < len(padWidths) {
			continue
		}
		builder.WriteString(stringArray[len(padWidths)])

		if i < len(stringArrays)-1 {
			builder.WriteString("\n")
		}
	}
	return builder.String()
}

func getPadWidths(stringArrays [][]string) []int {
	maxWidth := 0
	for _, stringArray := range stringArrays {
		if len(stringArray) > maxWidth {
			maxWidth = len(stringArray)
		}
	}
	if maxWidth-1 < 0 {
		return []int{}
	}
	padWidths := make([]int, maxWidth-1)
	for i := range padWidths {
		for _, strings := range stringArrays {
			uncoloredStr := Decolorise(strings[i])

			width := runewidth.StringWidth(uncoloredStr)
			if width > padWidths[i] {
				padWidths[i] = width
			}
		}
	}
	return padWidths
}

// TruncateWithEllipsis returns a string, truncated to a certain length, with an ellipsis
func TruncateWithEllipsis(str string, limit int) string {
	if runewidth.StringWidth(str) > limit && limit <= 3 {
		return strings.Repeat(".", limit)
	}
	return runewidth.Truncate(str, limit, "...")
}

func SafeTruncate(str string, limit int) string {
	if len(str) > limit {
		return str[0:limit]
	} else {
		return str
	}
}

func ShortSha(sha string) string {
	if len(sha) < 8 {
		return sha
	}
	return sha[:8]
}