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, "<", "<", -1) 514 v = strings.Replace(v, "&", "&", -1) 515 v = strings.Replace(v, "\"", """, -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