1package str
2
3import (
4	"fmt"
5	"html"
6	//"log"
7	"math"
8	"regexp"
9	"runtime"
10	"strconv"
11	"strings"
12	"unicode/utf8"
13)
14
15// Pad pads string s on both sides with c until it has length of n.
16func Pad(s, c string, n int) string {
17	L := len(s)
18	if L >= n {
19		return s
20	}
21	n -= L
22
23	left := strings.Repeat(c, int(math.Ceil(float64(n)/2)))
24	right := strings.Repeat(c, int(math.Floor(float64(n)/2)))
25	return left + s + right
26}
27
28// PadF is the filter form of Pad.
29func PadF(c string, n int) func(string) string {
30	return func(s string) string {
31		return Pad(s, c, n)
32	}
33}
34
35// PadLeft pads s on left side with c until it has length of n.
36func PadLeft(s, c string, n int) string {
37	L := len(s)
38	if L > n {
39		return s
40	}
41	return strings.Repeat(c, (n-L)) + s
42}
43
44// PadLeftF is the filter form of PadLeft.
45func PadLeftF(c string, n int) func(string) string {
46	return func(s string) string {
47		return PadLeft(s, c, n)
48	}
49}
50
51// PadRight pads s on right side with c until it has length of n.
52func PadRight(s, c string, n int) string {
53	L := len(s)
54	if L > n {
55		return s
56	}
57	return s + strings.Repeat(c, n-L)
58}
59
60// PadRightF is the filter form of Padright
61func PadRightF(c string, n int) func(string) string {
62	return func(s string) string {
63		return PadRight(s, c, n)
64	}
65}
66
67// Pipe pipes s through one or more string filters.
68func Pipe(s string, funcs ...func(string) string) string {
69	for _, fn := range funcs {
70		s = fn(s)
71	}
72	return s
73}
74
75// QuoteItems quotes all items in array, mostly for debugging.
76func QuoteItems(arr []string) []string {
77	return Map(arr, func(s string) string {
78		return strconv.Quote(s)
79	})
80}
81
82// ReplaceF is the filter form of strings.Replace.
83func ReplaceF(old, new string, n int) func(string) string {
84	return func(s string) string {
85		return strings.Replace(s, old, new, n)
86	}
87}
88
89// ReplacePattern replaces string with regexp string.
90// ReplacePattern returns a copy of src, replacing matches of the Regexp with the replacement string repl. Inside repl, $ signs are interpreted as in Expand, so for instance $1 represents the text of the first submatch.
91func ReplacePattern(s, pattern, repl string) string {
92	r := regexp.MustCompile(pattern)
93	return r.ReplaceAllString(s, repl)
94}
95
96// ReplacePatternF is the filter form of ReplaceRegexp.
97func ReplacePatternF(pattern, repl string) func(string) string {
98	return func(s string) string {
99		return ReplacePattern(s, pattern, repl)
100	}
101}
102
103// Reverse a string
104func Reverse(s string) string {
105	cs := make([]rune, utf8.RuneCountInString(s))
106	i := len(cs)
107	for _, c := range s {
108		i--
109		cs[i] = c
110	}
111	return string(cs)
112}
113
114// Right returns the right substring of length n.
115func Right(s string, n int) string {
116	if n < 0 {
117		return Left(s, -n)
118	}
119	return Substr(s, len(s)-n, n)
120}
121
122// RightF is the Filter version of Right.
123func RightF(n int) func(string) string {
124	return func(s string) string {
125		return Right(s, n)
126	}
127}
128
129// RightOf returns the substring to the right of prefix.
130func RightOf(s string, prefix string) string {
131	return Between(s, prefix, "")
132}
133
134// SetTemplateDelimiters sets the delimiters for Template function. Defaults to "{{" and "}}"
135func SetTemplateDelimiters(opening, closing string) {
136	templateOpen = opening
137	templateClose = closing
138}
139
140// Slice slices a string. If end is negative then it is the from the end
141// of the string.
142func Slice(s string, start, end int) string {
143	if end > -1 {
144		return s[start:end]
145	}
146	L := len(s)
147	if L+end > 0 {
148		return s[start : L-end]
149	}
150	return s[start:]
151}
152
153// SliceF is the filter for Slice.
154func SliceF(start, end int) func(string) string {
155	return func(s string) string {
156		return Slice(s, start, end)
157	}
158}
159
160// SliceContains determines whether val is an element in slice.
161func SliceContains(slice []string, val string) bool {
162	if slice == nil {
163		return false
164	}
165
166	for _, it := range slice {
167		if it == val {
168			return true
169		}
170	}
171	return false
172}
173
174// SliceIndexOf gets the indx of val in slice. Returns -1 if not found.
175func SliceIndexOf(slice []string, val string) int {
176	if slice == nil {
177		return -1
178	}
179
180	for i, it := range slice {
181		if it == val {
182			return i
183		}
184	}
185	return -1
186}
187
188// Slugify converts s into a dasherized string suitable for URL segment.
189func Slugify(s string) string {
190	sl := slugifyRe.ReplaceAllString(s, "")
191	sl = strings.ToLower(sl)
192	sl = Dasherize(sl)
193	return sl
194}
195
196// StripPunctuation strips puncation from string.
197func StripPunctuation(s string) string {
198	s = stripPuncRe.ReplaceAllString(s, "")
199	s = nWhitespaceRe.ReplaceAllString(s, " ")
200	return s
201}
202
203// StripTags strips all of the html tags or tags specified by the parameters
204func StripTags(s string, tags ...string) string {
205	if len(tags) == 0 {
206		tags = append(tags, "")
207	}
208	for _, tag := range tags {
209		stripTagsRe := regexp.MustCompile(`(?i)<\/?` + tag + `[^<>]*>`)
210		s = stripTagsRe.ReplaceAllString(s, "")
211	}
212	return s
213}
214
215// Substr returns a substring of s starting at index of length n.
216func Substr(s string, index int, n int) string {
217	L := len(s)
218	if index < 0 || index >= L || s == "" {
219		return ""
220	}
221	end := index + n
222	if end >= L {
223		end = L
224	}
225	if end <= index {
226		return ""
227	}
228	return s[index:end]
229}
230
231// SubstrF is the filter form of Substr.
232func SubstrF(index, n int) func(string) string {
233	return func(s string) string {
234		return Substr(s, index, n)
235	}
236}
237
238// Template is a string template which replaces template placeholders delimited
239// by "{{" and "}}" with values from map. The global delimiters may be set with
240// SetTemplateDelimiters.
241func Template(s string, values map[string]interface{}) string {
242	return TemplateWithDelimiters(s, values, templateOpen, templateClose)
243}
244
245// TemplateDelimiters is the getter for the opening and closing delimiters for Template.
246func TemplateDelimiters() (opening string, closing string) {
247	return templateOpen, templateClose
248}
249
250// TemplateWithDelimiters is string template with user-defineable opening and closing delimiters.
251func TemplateWithDelimiters(s string, values map[string]interface{}, opening, closing string) string {
252	escapeDelimiter := func(delim string) string {
253		result := templateRe.ReplaceAllString(delim, "\\$1")
254		return templateRe2.ReplaceAllString(result, "\\$")
255	}
256
257	openingDelim := escapeDelimiter(opening)
258	closingDelim := escapeDelimiter(closing)
259	r := regexp.MustCompile(openingDelim + `(.+?)` + closingDelim)
260	matches := r.FindAllStringSubmatch(s, -1)
261	for _, submatches := range matches {
262		match := submatches[0]
263		key := submatches[1]
264		//log.Printf("match %s key %s\n", match, key)
265		if values[key] != nil {
266			v := fmt.Sprintf("%v", values[key])
267			s = strings.Replace(s, match, v, -1)
268		}
269	}
270
271	return s
272}
273
274// ToArgv converts string s into an argv for exec.
275func ToArgv(s string) []string {
276	const (
277		InArg = iota
278		InArgQuote
279		OutOfArg
280	)
281	currentState := OutOfArg
282	currentQuoteChar := "\x00" // to distinguish between ' and " quotations
283	// this allows to use "foo'bar"
284	currentArg := ""
285	argv := []string{}
286
287	isQuote := func(c string) bool {
288		return c == `"` || c == `'`
289	}
290
291	isEscape := func(c string) bool {
292		return c == `\`
293	}
294
295	isWhitespace := func(c string) bool {
296		return c == " " || c == "\t"
297	}
298
299	L := len(s)
300	for i := 0; i < L; i++ {
301		c := s[i : i+1]
302
303		//fmt.Printf("c %s state %v arg %s argv %v i %d\n", c, currentState, currentArg, args, i)
304		if isQuote(c) {
305			switch currentState {
306			case OutOfArg:
307				currentArg = ""
308				fallthrough
309			case InArg:
310				currentState = InArgQuote
311				currentQuoteChar = c
312
313			case InArgQuote:
314				if c == currentQuoteChar {
315					currentState = InArg
316				} else {
317					currentArg += c
318				}
319			}
320
321		} else if isWhitespace(c) {
322			switch currentState {
323			case InArg:
324				argv = append(argv, currentArg)
325				currentState = OutOfArg
326			case InArgQuote:
327				currentArg += c
328			case OutOfArg:
329				// nothing
330			}
331
332		} else if isEscape(c) {
333			switch currentState {
334			case OutOfArg:
335				currentArg = ""
336				currentState = InArg
337				fallthrough
338			case InArg:
339				fallthrough
340			case InArgQuote:
341				if i == L-1 {
342					if runtime.GOOS == "windows" {
343						// just add \ to end for windows
344						currentArg += c
345					} else {
346						panic("Escape character at end string")
347					}
348				} else {
349					if runtime.GOOS == "windows" {
350						peek := s[i+1 : i+2]
351						if peek != `"` {
352							currentArg += c
353						}
354					} else {
355						i++
356						c = s[i : i+1]
357						currentArg += c
358					}
359				}
360			}
361		} else {
362			switch currentState {
363			case InArg, InArgQuote:
364				currentArg += c
365
366			case OutOfArg:
367				currentArg = ""
368				currentArg += c
369				currentState = InArg
370			}
371		}
372	}
373
374	if currentState == InArg {
375		argv = append(argv, currentArg)
376	} else if currentState == InArgQuote {
377		panic("Starting quote has no ending quote.")
378	}
379
380	return argv
381}
382
383// ToBool fuzzily converts truthy values.
384func ToBool(s string) bool {
385	s = strings.ToLower(s)
386	return s == "true" || s == "yes" || s == "on" || s == "1"
387}
388
389// ToBoolOr parses s as a bool or returns defaultValue.
390func ToBoolOr(s string, defaultValue bool) bool {
391	b, err := strconv.ParseBool(s)
392	if err != nil {
393		return defaultValue
394	}
395	return b
396}
397
398// ToIntOr parses s as an int or returns defaultValue.
399func ToIntOr(s string, defaultValue int) int {
400	n, err := strconv.Atoi(s)
401	if err != nil {
402		return defaultValue
403	}
404	return n
405}
406
407// ToFloat32Or parses as a float32 or returns defaultValue on error.
408func ToFloat32Or(s string, defaultValue float32) float32 {
409	f, err := strconv.ParseFloat(s, 32)
410	if err != nil {
411		return defaultValue
412	}
413	return float32(f)
414}
415
416// ToFloat64Or parses s as a float64 or returns defaultValue.
417func ToFloat64Or(s string, defaultValue float64) float64 {
418	f, err := strconv.ParseFloat(s, 64)
419	if err != nil {
420		return defaultValue
421	}
422	return f
423}
424
425// ToFloatOr parses as a float64 or returns defaultValue.
426var ToFloatOr = ToFloat64Or
427
428// TODO This is not working yet. Go's regexp package does not have some
429// of the niceities in JavaScript
430//
431// Truncate truncates the string, accounting for word placement and chars count
432// adding a morestr (defaults to ellipsis)
433// func Truncate(s, morestr string, n int) string {
434// 	L := len(s)
435// 	if L <= n {
436// 		return s
437// 	}
438//
439// 	if morestr == "" {
440// 		morestr = "..."
441// 	}
442//
443// 	tmpl := func(c string) string {
444// 		if strings.ToUpper(c) != strings.ToLower(c) {
445// 			return "A"
446// 		}
447// 		return " "
448// 	}
449// 	template := s[0 : n+1]
450// 	var truncateRe = regexp.MustCompile(`.(?=\W*\w*$)`)
451// 	truncateRe.ReplaceAllStringFunc(template, tmpl) // 'Hello, world' -> 'HellAA AAAAA'
452// 	var wwRe = regexp.MustCompile(`\w\w`)
453// 	var whitespaceRe2 = regexp.MustCompile(`\s*\S+$`)
454// 	if wwRe.MatchString(template[len(template)-2:]) {
455// 		template = whitespaceRe2.ReplaceAllString(template, "")
456// 	} else {
457// 		template = strings.TrimRight(template, " \t\n")
458// 	}
459//
460// 	if len(template+morestr) > L {
461// 		return s
462// 	}
463// 	return s[0:len(template)] + morestr
464// }
465//
466//     truncate: function(length, pruneStr) { //from underscore.string, author: github.com/rwz
467//       var str = this.s;
468//
469//       length = ~~length;
470//       pruneStr = pruneStr || '...';
471//
472//       if (str.length <= length) return new this.constructor(str);
473//
474//       var tmpl = function(c){ return c.toUpperCase() !== c.toLowerCase() ? 'A' : ' '; },
475//         template = str.slice(0, length+1).replace(/.(?=\W*\w*$)/g, tmpl); // 'Hello, world' -> 'HellAA AAAAA'
476//
477//       if (template.slice(template.length-2).match(/\w\w/))
478//         template = template.replace(/\s*\S+$/, '');
479//       else
480//         template = new S(template.slice(0, template.length-1)).trimRight().s;
481//
482//       return (template+pruneStr).length > str.length ? new S(str) : new S(str.slice(0, template.length)+pruneStr);
483//     },
484
485// Underscore returns converted camel cased string into a string delimited by underscores.
486func Underscore(s string) string {
487	if s == "" {
488		return ""
489	}
490	u := strings.TrimSpace(s)
491
492	u = underscoreRe.ReplaceAllString(u, "${1}_$2")
493	u = dashSpaceRe.ReplaceAllString(u, "_")
494	u = strings.ToLower(u)
495	if IsUpper(s[0:1]) {
496		return "_" + u
497	}
498	return u
499}
500
501// UnescapeHTML is an alias for html.UnescapeString.
502func UnescapeHTML(s string) string {
503	if Verbose {
504		fmt.Println("Use html.UnescapeString instead of UnescapeHTML")
505	}
506	return html.UnescapeString(s)
507}
508
509// WrapHTML wraps s within HTML tag having attributes attrs. Note,
510// WrapHTML does not escape s value.
511func WrapHTML(s string, tag string, attrs map[string]string) string {
512	escapeHTMLAttributeQuotes := func(v string) string {
513		v = strings.Replace(v, "<", "&lt;", -1)
514		v = strings.Replace(v, "&", "&amp;", -1)
515		v = strings.Replace(v, "\"", "&quot;", -1)
516		return v
517	}
518	if tag == "" {
519		tag = "div"
520	}
521	el := "<" + tag
522	for name, val := range attrs {
523		el += " " + name + "=\"" + escapeHTMLAttributeQuotes(val) + "\""
524	}
525	el += ">" + s + "</" + tag + ">"
526	return el
527}
528
529// WrapHTMLF is the filter form of WrapHTML.
530func WrapHTMLF(tag string, attrs map[string]string) func(string) string {
531	return func(s string) string {
532		return WrapHTML(s, tag, attrs)
533	}
534}
535