Skip to content

Commit 51e6d96

Browse files
authored
Fix some template pad functions with wide charactors (#310)
Miscellaneous adjustments to accomodate string manipulation in languages other than english. * Fix some template pad functions with wild characters * Add wide characters support for SplitString and SplitStringNL * Fix SplitString with punctuation at eol * Add some SplitString tests See: internal/templates/templatesfunctions_test.go
1 parent c789eff commit 51e6d96

File tree

4 files changed

+509
-132
lines changed

4 files changed

+509
-132
lines changed

internal/templates/templatesfunctions.go

Lines changed: 27 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ import (
1717
"github.com/volte6/gomud/internal/language"
1818
"github.com/volte6/gomud/internal/mobs"
1919
"github.com/volte6/gomud/internal/skills"
20-
"github.com/volte6/gomud/internal/term"
2120
"github.com/volte6/gomud/internal/users"
2221
"github.com/volte6/gomud/internal/util"
2322
)
@@ -112,18 +111,9 @@ var (
112111
"pct": pct,
113112
"numberFormat": numberFormat,
114113
"mod": func(a, b int) int { return a % b },
115-
"stringor": func(a string, b string, padding ...int) string {
116-
str := a
117-
if str == "" {
118-
str = b
119-
}
120-
if len(padding) > 0 {
121-
str = fmt.Sprintf("%-"+strconv.Itoa(padding[0])+"s", str)
122-
}
123-
return str
124-
},
125-
"splitstring": SplitStringNL,
126-
"ansiparse": TplAnsiParse,
114+
"stringor": stringOr,
115+
"splitstring": util.SplitStringNL,
116+
"ansiparse": TplAnsiParse,
127117
"buffname": func(buffId int) string {
128118
buffSpec := buffs.GetBuffSpec(buffId)
129119
if buffSpec == nil {
@@ -245,10 +235,12 @@ func padLeft(totalWidth int, stringArgs ...string) string {
245235
}
246236
}
247237

248-
if len(stringIn) >= totalWidth {
238+
stringInWidth := runewidth.StringWidth(stringIn)
239+
240+
if stringInWidth >= totalWidth {
249241
return stringIn
250242
}
251-
paddingLength := totalWidth - len(stringIn)
243+
paddingLength := totalWidth - stringInWidth
252244
if paddingLength < 1 {
253245
return stringIn
254246
}
@@ -272,10 +264,12 @@ func padRight(totalWidth int, stringArgs ...string) string {
272264
}
273265
}
274266

275-
if len(stringIn) >= totalWidth {
267+
stringInWidth := runewidth.StringWidth(stringIn)
268+
269+
if stringInWidth >= totalWidth {
276270
return stringIn
277271
}
278-
paddingLength := totalWidth - len(stringIn)
272+
paddingLength := totalWidth - stringInWidth
279273
if paddingLength < 1 {
280274
return stringIn
281275
}
@@ -287,6 +281,10 @@ func padRightX(input, padding string, length int) string {
287281
padLen := runewidth.StringWidth(padding)
288282
inputLen := runewidth.StringWidth(input)
289283

284+
if length < inputLen {
285+
length = inputLen
286+
}
287+
290288
// Calculate how many times the padding string should be repeated
291289
paddingRepeats := int(math.Ceil((float64(length) - float64(inputLen)) / float64(padLen)))
292290
finalPadLength := length - inputLen
@@ -309,7 +307,7 @@ func padRightX(input, padding string, length int) string {
309307
// Usage:
310308
//
311309
// {{ pad 10 }}
312-
// OUTPUT: " "
310+
// OUTPUT: " "
313311
// {{ pad 11 "hello" "-" }}
314312
// OUTPUT: "---hello---"
315313
func pad(totalWidth int, stringArgs ...string) string {
@@ -323,10 +321,12 @@ func pad(totalWidth int, stringArgs ...string) string {
323321
}
324322
}
325323

326-
if len(stringIn) >= totalWidth {
324+
stringInWidth := runewidth.StringWidth(stringIn)
325+
326+
if stringInWidth >= totalWidth {
327327
return stringIn
328328
}
329-
paddingLength := totalWidth - len(stringIn)
329+
paddingLength := totalWidth - stringInWidth
330330
leftPad := paddingLength >> 1
331331
if leftPad < 1 {
332332
return stringIn
@@ -365,44 +365,15 @@ func TplAnsiParse(input string) string {
365365
return AnsiParse(input)
366366
}
367367

368-
// Splits a string by adding line breaks at the end of each line
369-
func SplitStringNL(input string, lineWidth int, nlPrefix ...string) string {
370-
371-
output := strings.Builder{}
372-
373-
words := strings.Fields(input) // Split the input into words
374-
375-
linePrefix := ""
376-
if len(nlPrefix) > 0 {
377-
linePrefix = nlPrefix[0]
368+
func stringOr(a string, b string, padding ...int) string {
369+
str := a
370+
if str == "" {
371+
str = b
378372
}
379-
380-
currentLine := ""
381-
for _, word := range words {
382-
if len(currentLine)+len(word)+1 <= lineWidth { // +1 for the space
383-
if currentLine == "" {
384-
currentLine = word
385-
} else {
386-
currentLine += " " + word
387-
}
388-
} else {
389-
if linePrefix != "" && output.Len() > 0 {
390-
output.WriteString(linePrefix)
391-
}
392-
output.WriteString(currentLine)
393-
output.WriteString(term.CRLFStr)
394-
currentLine = word
395-
}
373+
if len(padding) > 0 {
374+
str = padRight(padding[0], str)
396375
}
397-
398-
if currentLine != "" {
399-
if linePrefix != "" && output.Len() > 0 {
400-
output.WriteString(linePrefix)
401-
}
402-
output.WriteString(currentLine)
403-
}
404-
405-
return output.String()
376+
return str
406377
}
407378

408379
func formatDuration(seconds int) string {
Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
package templates
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
)
8+
9+
func TestPad(t *testing.T) {
10+
type args struct {
11+
width int
12+
stringIn string
13+
padString string
14+
}
15+
16+
tests := []struct {
17+
name string
18+
args args
19+
want string
20+
}{
21+
{"Pad empty string with space", args{10, "", ""}, " "},
22+
{"Pad empty string with minus", args{10, "", "-"}, "----------"},
23+
{"Pad with space", args{10, "test", ""}, " test "},
24+
{"Pad with space 2", args{10, "hello", ""}, " hello "},
25+
{"Pad with minus", args{10, "test", "-"}, "---test---"},
26+
{"Pad with space and zero width", args{0, "test", ""}, "test"},
27+
{"Pad with space and smaller width", args{3, "test", ""}, "test"},
28+
{"Pad with space and same width", args{4, "test", ""}, "test"},
29+
{"Pad wide charactors with space", args{10, "宽字符", ""}, " 宽字符 "},
30+
{"Pad wide charactors with space 2", args{10, "宽字符A", ""}, " 宽字符A "},
31+
{"Pad wide charactors with space and zero width", args{0, "宽字符", ""}, "宽字符"},
32+
{"Pad wide charactors with space and smaller width", args{5, "宽字符", ""}, "宽字符"},
33+
{"Pad wide charactors with space and same width", args{6, "宽字符", ""}, "宽字符"},
34+
}
35+
36+
for _, tt := range tests {
37+
t.Run(tt.name, func(t *testing.T) {
38+
var got string
39+
if tt.args.stringIn == "" && tt.args.padString == "" {
40+
got = pad(tt.args.width)
41+
} else if tt.args.padString == "" {
42+
got = pad(tt.args.width, tt.args.stringIn)
43+
} else {
44+
got = pad(tt.args.width, tt.args.stringIn, tt.args.padString)
45+
}
46+
assert.Equal(t, tt.want, got)
47+
})
48+
}
49+
}
50+
51+
func TestPadLeft(t *testing.T) {
52+
type args struct {
53+
width int
54+
stringIn string
55+
padString string
56+
}
57+
58+
tests := []struct {
59+
name string
60+
args args
61+
want string
62+
}{
63+
{"PadLeft empty string with space", args{10, "", ""}, " "},
64+
{"PadLeft empty string with minus", args{10, "", "-"}, "----------"},
65+
{"PadLeft with space", args{10, "test", ""}, " test"},
66+
{"PadLeft with space 2", args{10, "hello", ""}, " hello"},
67+
{"PadLeft with minus", args{10, "test", "-"}, "------test"},
68+
{"PadLeft with space and zero width", args{0, "test", ""}, "test"},
69+
{"PadLeft with space and smaller width", args{3, "test", ""}, "test"},
70+
{"PadLeft with space and same width", args{4, "test", ""}, "test"},
71+
{"PadLeft wide charactors with space", args{10, "宽字符", ""}, " 宽字符"},
72+
{"PadLeft wide charactors with space 2", args{10, "宽字符A", ""}, " 宽字符A"},
73+
{"PadLeft wide charactors with space and zero width", args{0, "宽字符", ""}, "宽字符"},
74+
{"PadLeft wide charactors with space and smaller width", args{5, "宽字符", ""}, "宽字符"},
75+
{"PadLeft wide charactors with space and same width", args{6, "宽字符", ""}, "宽字符"},
76+
}
77+
78+
for _, tt := range tests {
79+
t.Run(tt.name, func(t *testing.T) {
80+
var got string
81+
if tt.args.stringIn == "" && tt.args.padString == "" {
82+
got = padLeft(tt.args.width)
83+
} else if tt.args.padString == "" {
84+
got = padLeft(tt.args.width, tt.args.stringIn)
85+
} else {
86+
got = padLeft(tt.args.width, tt.args.stringIn, tt.args.padString)
87+
}
88+
assert.Equal(t, tt.want, got)
89+
})
90+
}
91+
}
92+
93+
func TestPadRight(t *testing.T) {
94+
type args struct {
95+
width int
96+
stringIn string
97+
padString string
98+
}
99+
100+
tests := []struct {
101+
name string
102+
args args
103+
want string
104+
}{
105+
{"PadRight empty string with space", args{10, "", ""}, " "},
106+
{"PadRight empty string with minus", args{10, "", "-"}, "----------"},
107+
{"PadRight with space", args{10, "test", ""}, "test "},
108+
{"PadRight with space 2", args{10, "hello", ""}, "hello "},
109+
{"PadRight with minus", args{10, "test", "-"}, "test------"},
110+
{"PadRight with space and zero width", args{0, "test", ""}, "test"},
111+
{"PadRight with space and smaller width", args{3, "test", ""}, "test"},
112+
{"PadRight with space and same width", args{4, "test", ""}, "test"},
113+
{"PadRight wide charactors with space", args{10, "宽字符", ""}, "宽字符 "},
114+
{"PadRight wide charactors with space 2", args{10, "宽字符A", ""}, "宽字符A "},
115+
{"PadRight wide charactors with space and zero width", args{0, "宽字符", ""}, "宽字符"},
116+
{"PadRight wide charactors with space and smaller width", args{5, "宽字符", ""}, "宽字符"},
117+
{"PadRight wide charactors with space and same width", args{6, "宽字符", ""}, "宽字符"},
118+
}
119+
120+
for _, tt := range tests {
121+
t.Run(tt.name, func(t *testing.T) {
122+
var got string
123+
if tt.args.stringIn == "" && tt.args.padString == "" {
124+
got = padRight(tt.args.width)
125+
} else if tt.args.padString == "" {
126+
got = padRight(tt.args.width, tt.args.stringIn)
127+
} else {
128+
got = padRight(tt.args.width, tt.args.stringIn, tt.args.padString)
129+
}
130+
assert.Equal(t, tt.want, got)
131+
})
132+
}
133+
}
134+
135+
func TestPadRightX(t *testing.T) {
136+
type args struct {
137+
width int
138+
stringIn string
139+
padString string
140+
}
141+
142+
tests := []struct {
143+
name string
144+
args args
145+
want string
146+
}{
147+
{"PadRightX empty string with space", args{10, "", " "}, " "},
148+
{"PadRightX empty string with minus", args{10, "", "-"}, "----------"},
149+
{"PadRightX empty string with minus and plus", args{10, "", "-+"}, "-+-+-+-+-+"},
150+
{"PadRightX empty string with minus plus and dot", args{10, "", "-+."}, "-+.-+.-+.-"},
151+
{"PadRightX with space", args{10, "test", " "}, "test "},
152+
{"PadRightX with space 2", args{10, "hello", " "}, "hello "},
153+
{"PadRightX with minus", args{10, "test", "-"}, "test------"},
154+
{"PadRightX with space and zero width", args{0, "test", " "}, "test"},
155+
{"PadRightX with space and smaller width", args{3, "test", " "}, "test"},
156+
{"PadRightX with space and same width", args{4, "test", " "}, "test"},
157+
{"PadRightX wide charactors with space", args{10, "宽字符", " "}, "宽字符 "},
158+
{"PadRightX wide charactors with space 2", args{10, "宽字符A", " "}, "宽字符A "},
159+
{"PadRightX wide charactors with space and zero width", args{0, "宽字符", " "}, "宽字符"},
160+
{"PadRightX wide charactors with space and smaller width", args{5, "宽字符", " "}, "宽字符"},
161+
{"PadRightX wide charactors with space and same width", args{6, "宽字符", " "}, "宽字符"},
162+
}
163+
164+
for _, tt := range tests {
165+
t.Run(tt.name, func(t *testing.T) {
166+
got := padRightX(tt.args.stringIn, tt.args.padString, tt.args.width)
167+
assert.Equal(t, tt.want, got)
168+
})
169+
}
170+
}
171+
172+
func TestStringOr(t *testing.T) {
173+
type args struct {
174+
stringA string
175+
stringB string
176+
padding []int
177+
}
178+
179+
tests := []struct {
180+
name string
181+
args args
182+
want string
183+
}{
184+
{"StringOr", args{"a", "b", []int{10}}, "a "},
185+
{"StringOr empty string a and b", args{"", "", []int{10}}, " "},
186+
{"StringOr empty string a", args{"", "b", []int{10}}, "b "},
187+
{"StringOr empty string b", args{"a", "", []int{10}}, "a "},
188+
{"StringOr without padding", args{"a", "b", nil}, "a"},
189+
{"StringOr with zero width", args{"test", "b", []int{0}}, "test"},
190+
{"StringOr with smaller width", args{"test", "b", []int{3}}, "test"},
191+
{"StringOr with same width", args{"test", "b", []int{4}}, "test"},
192+
{"StringOr wide charactors", args{"宽字符", "", []int{10}}, "宽字符 "},
193+
{"StringOr wide charactors 2", args{"宽字符A", "", []int{10}}, "宽字符A "},
194+
{"StringOr wide charactors with zero width", args{"宽字符", "", []int{0}}, "宽字符"},
195+
{"StringOr wide charactors with smaller width", args{"宽字符", "", []int{5}}, "宽字符"},
196+
{"StringOr wide charactors with same width", args{"宽字符", "", []int{6}}, "宽字符"},
197+
}
198+
199+
for _, tt := range tests {
200+
t.Run(tt.name, func(t *testing.T) {
201+
got := stringOr(tt.args.stringA, tt.args.stringB, tt.args.padding...)
202+
assert.Equal(t, tt.want, got)
203+
})
204+
}
205+
}

0 commit comments

Comments
 (0)