1package str
2
3import (
4	"fmt"
5	"html"
6	//"log"
7	"regexp"
8	"strings"
9)
10
11// Verbose flag enables console output for those functions that have
12// counterparts in Go's excellent stadard packages.
13var Verbose = false
14var templateOpen = "{{"
15var templateClose = "}}"
16
17var beginEndSpacesRe = regexp.MustCompile("^\\s+|\\s+$")
18var camelizeRe = regexp.MustCompile(`(\-|_|\s)+(.)?`)
19var camelizeRe2 = regexp.MustCompile(`(\-|_|\s)+`)
20var capitalsRe = regexp.MustCompile("([A-Z])")
21var dashSpaceRe = regexp.MustCompile(`[-\s]+`)
22var dashesRe = regexp.MustCompile("-+")
23var isAlphaNumericRe = regexp.MustCompile(`[^0-9a-z\xC0-\xFF]`)
24var isAlphaRe = regexp.MustCompile(`[^a-z\xC0-\xFF]`)
25var nWhitespaceRe = regexp.MustCompile(`\s+`)
26var notDigitsRe = regexp.MustCompile(`[^0-9]`)
27var slugifyRe = regexp.MustCompile(`[^\w\s\-]`)
28var spaceUnderscoreRe = regexp.MustCompile("[_\\s]+")
29var spacesRe = regexp.MustCompile("[\\s\\xA0]+")
30var stripPuncRe = regexp.MustCompile(`[^\w\s]|_`)
31var templateRe = regexp.MustCompile(`([\-\[\]()*\s])`)
32var templateRe2 = regexp.MustCompile(`\$`)
33var underscoreRe = regexp.MustCompile(`([a-z\d])([A-Z]+)`)
34var whitespaceRe = regexp.MustCompile(`^[\s\xa0]*$`)
35
36func min(a, b int) int {
37	if a < b {
38		return a
39	}
40	return b
41}
42
43func max(a, b int) int {
44	if a > b {
45		return a
46	}
47	return b
48}
49
50// Between extracts a string between left and right strings.
51func Between(s, left, right string) string {
52	l := len(left)
53	startPos := strings.Index(s, left)
54	if startPos < 0 {
55		return ""
56	}
57	endPos := IndexOf(s, right, startPos+l)
58	//log.Printf("%s: left %s right %s start %d end %d", s, left, right, startPos+l, endPos)
59	if endPos < 0 {
60		return ""
61	} else if right == "" {
62		return s[endPos:]
63	} else {
64		return s[startPos+l : endPos]
65	}
66}
67
68// BetweenF is the filter form for Between.
69func BetweenF(left, right string) func(string) string {
70	return func(s string) string {
71		return Between(s, left, right)
72	}
73}
74
75// Camelize return new string which removes any underscores or dashes and convert a string into camel casing.
76func Camelize(s string) string {
77	return camelizeRe.ReplaceAllStringFunc(s, func(val string) string {
78		val = strings.ToUpper(val)
79		val = camelizeRe2.ReplaceAllString(val, "")
80		return val
81	})
82}
83
84// Capitalize uppercases the first char of s and lowercases the rest.
85func Capitalize(s string) string {
86	return strings.ToUpper(s[0:1]) + strings.ToLower(s[1:])
87}
88
89// CharAt returns a string from the character at the specified position.
90func CharAt(s string, index int) string {
91	l := len(s)
92	shortcut := index < 0 || index > l-1 || l == 0
93	if shortcut {
94		return ""
95	}
96	return s[index : index+1]
97}
98
99// CharAtF is the filter form of CharAt.
100func CharAtF(index int) func(string) string {
101	return func(s string) string {
102		return CharAt(s, index)
103	}
104}
105
106// ChompLeft removes prefix at the start of a string.
107func ChompLeft(s, prefix string) string {
108	if strings.HasPrefix(s, prefix) {
109		return s[len(prefix):]
110	}
111	return s
112}
113
114// ChompLeftF is the filter form of ChompLeft.
115func ChompLeftF(prefix string) func(string) string {
116	return func(s string) string {
117		return ChompLeft(s, prefix)
118	}
119}
120
121// ChompRight removes suffix from end of s.
122func ChompRight(s, suffix string) string {
123	if strings.HasSuffix(s, suffix) {
124		return s[:len(s)-len(suffix)]
125	}
126	return s
127}
128
129// ChompRightF is the filter form of ChompRight.
130func ChompRightF(suffix string) func(string) string {
131	return func(s string) string {
132		return ChompRight(s, suffix)
133	}
134}
135
136// Classify returns a camelized string with the first letter upper cased.
137func Classify(s string) string {
138	return Camelize("-" + s)
139}
140
141// ClassifyF is the filter form of Classify.
142func ClassifyF(s string) func(string) string {
143	return func(s string) string {
144		return Classify(s)
145	}
146}
147
148// Clean compresses all adjacent whitespace to a single space and trims s.
149func Clean(s string) string {
150	s = spacesRe.ReplaceAllString(s, " ")
151	s = beginEndSpacesRe.ReplaceAllString(s, "")
152	return s
153}
154
155// Dasherize  converts a camel cased string into a string delimited by dashes.
156func Dasherize(s string) string {
157	s = strings.TrimSpace(s)
158	s = spaceUnderscoreRe.ReplaceAllString(s, "-")
159	s = capitalsRe.ReplaceAllString(s, "-$1")
160	s = dashesRe.ReplaceAllString(s, "-")
161	s = strings.ToLower(s)
162	return s
163}
164
165// EscapeHTML is alias for html.EscapeString.
166func EscapeHTML(s string) string {
167	if Verbose {
168		fmt.Println("Use html.EscapeString instead of EscapeHTML")
169	}
170	return html.EscapeString(s)
171}
172
173// DecodeHTMLEntities decodes HTML entities into their proper string representation.
174// DecodeHTMLEntities is an alias for html.UnescapeString
175func DecodeHTMLEntities(s string) string {
176	if Verbose {
177		fmt.Println("Use html.UnescapeString instead of DecodeHTMLEntities")
178	}
179	return html.UnescapeString(s)
180}
181
182// EnsurePrefix ensures s starts with prefix.
183func EnsurePrefix(s, prefix string) string {
184	if strings.HasPrefix(s, prefix) {
185		return s
186	}
187	return prefix + s
188}
189
190// EnsurePrefixF is the filter form of EnsurePrefix.
191func EnsurePrefixF(prefix string) func(string) string {
192	return func(s string) string {
193		return EnsurePrefix(s, prefix)
194	}
195}
196
197// EnsureSuffix ensures s ends with suffix.
198func EnsureSuffix(s, suffix string) string {
199	if strings.HasSuffix(s, suffix) {
200		return s
201	}
202	return s + suffix
203}
204
205// EnsureSuffixF is the filter form of EnsureSuffix.
206func EnsureSuffixF(suffix string) func(string) string {
207	return func(s string) string {
208		return EnsureSuffix(s, suffix)
209	}
210}
211
212// Humanize transforms s into a human friendly form.
213func Humanize(s string) string {
214	if s == "" {
215		return s
216	}
217	s = Underscore(s)
218	var humanizeRe = regexp.MustCompile(`_id$`)
219	s = humanizeRe.ReplaceAllString(s, "")
220	s = strings.Replace(s, "_", " ", -1)
221	s = strings.TrimSpace(s)
222	s = Capitalize(s)
223	return s
224}
225
226// Iif is short for immediate if. If condition is true return truthy else falsey.
227func Iif(condition bool, truthy string, falsey string) string {
228	if condition {
229		return truthy
230	}
231	return falsey
232}
233
234// IndexOf finds the index of needle in s starting from start.
235func IndexOf(s string, needle string, start int) int {
236	l := len(s)
237	if needle == "" {
238		if start < 0 {
239			return 0
240		} else if start < l {
241			return start
242		} else {
243			return l
244		}
245	}
246	if start < 0 || start > l-1 {
247		return -1
248	}
249	pos := strings.Index(s[start:], needle)
250	if pos == -1 {
251		return -1
252	}
253	return start + pos
254}
255
256// IsAlpha returns true if a string contains only letters from ASCII (a-z,A-Z). Other letters from other languages are not supported.
257func IsAlpha(s string) bool {
258	return !isAlphaRe.MatchString(strings.ToLower(s))
259}
260
261// IsAlphaNumeric returns true if a string contains letters and digits.
262func IsAlphaNumeric(s string) bool {
263	return !isAlphaNumericRe.MatchString(strings.ToLower(s))
264}
265
266// IsLower returns true if s comprised of all lower case characters.
267func IsLower(s string) bool {
268	return IsAlpha(s) && s == strings.ToLower(s)
269}
270
271// IsNumeric returns true if a string contains only digits from 0-9. Other digits not in Latin (such as Arabic) are not currently supported.
272func IsNumeric(s string) bool {
273	return !notDigitsRe.MatchString(s)
274}
275
276// IsUpper returns true if s contains all upper case chracters.
277func IsUpper(s string) bool {
278	return IsAlpha(s) && s == strings.ToUpper(s)
279}
280
281// IsEmpty returns true if the string is solely composed of whitespace.
282func IsEmpty(s string) bool {
283	if s == "" {
284		return true
285	}
286	return whitespaceRe.MatchString(s)
287}
288
289// Left returns the left substring of length n.
290func Left(s string, n int) string {
291	if n < 0 {
292		return Right(s, -n)
293	}
294	return Substr(s, 0, n)
295}
296
297// LeftF is the filter form of Left.
298func LeftF(n int) func(string) string {
299	return func(s string) string {
300		return Left(s, n)
301	}
302}
303
304// LeftOf returns the substring left of needle.
305func LeftOf(s string, needle string) string {
306	return Between(s, "", needle)
307}
308
309// Letters returns an array of runes as strings so it can be indexed into.
310func Letters(s string) []string {
311	result := []string{}
312	for _, r := range s {
313		result = append(result, string(r))
314	}
315	return result
316}
317
318// Lines convert windows newlines to unix newlines then convert to an Array of lines.
319func Lines(s string) []string {
320	s = strings.Replace(s, "\r\n", "\n", -1)
321	return strings.Split(s, "\n")
322}
323
324// Map maps an array's iitem through an iterator.
325func Map(arr []string, iterator func(string) string) []string {
326	r := []string{}
327	for _, item := range arr {
328		r = append(r, iterator(item))
329	}
330	return r
331}
332
333// Match returns true if patterns matches the string
334func Match(s, pattern string) bool {
335	r := regexp.MustCompile(pattern)
336	return r.MatchString(s)
337}
338