github.com/insionng/yougam@v0.0.0-20170714101924-2bc18d833463/libraries/flosch/pongo2.v3/filters_builtin.go (about) 1 package pongo2 2 3 /* Filters that are provided through yougam/libraries/flosch/pongo2-addons: 4 ------------------------------------------------------------------ 5 6 filesizeformat 7 slugify 8 timesince 9 timeuntil 10 11 Filters that won't be added: 12 ---------------------------- 13 14 get_static_prefix (reason: web-framework specific) 15 pprint (reason: python-specific) 16 static (reason: web-framework specific) 17 18 Reconsideration (not implemented yet): 19 -------------------------------------- 20 21 force_escape (reason: not yet needed since this is the behaviour of pongo2's escape filter) 22 safeseq (reason: same reason as `force_escape`) 23 unordered_list (python-specific; not sure whether needed or not) 24 dictsort (python-specific; maybe one could add a filter to sort a list of structs by a specific field name) 25 dictsortreversed (see dictsort) 26 */ 27 28 import ( 29 "bytes" 30 "fmt" 31 "math/rand" 32 "net/url" 33 "regexp" 34 "strconv" 35 "strings" 36 "time" 37 "unicode/utf8" 38 ) 39 40 func init() { 41 rand.Seed(time.Now().Unix()) 42 43 RegisterFilter("escape", filterEscape) 44 RegisterFilter("safe", filterSafe) 45 RegisterFilter("escapejs", filterEscapejs) 46 47 RegisterFilter("add", filterAdd) 48 RegisterFilter("addslashes", filterAddslashes) 49 RegisterFilter("capfirst", filterCapfirst) 50 RegisterFilter("center", filterCenter) 51 RegisterFilter("cut", filterCut) 52 RegisterFilter("date", filterDate) 53 RegisterFilter("default", filterDefault) 54 RegisterFilter("default_if_none", filterDefaultIfNone) 55 RegisterFilter("divisibleby", filterDivisibleby) 56 RegisterFilter("first", filterFirst) 57 RegisterFilter("floatformat", filterFloatformat) 58 RegisterFilter("get_digit", filterGetdigit) 59 RegisterFilter("iriencode", filterIriencode) 60 RegisterFilter("join", filterJoin) 61 RegisterFilter("last", filterLast) 62 RegisterFilter("length", filterLength) 63 RegisterFilter("length_is", filterLengthis) 64 RegisterFilter("linebreaks", filterLinebreaks) 65 RegisterFilter("linebreaksbr", filterLinebreaksbr) 66 RegisterFilter("linenumbers", filterLinenumbers) 67 RegisterFilter("ljust", filterLjust) 68 RegisterFilter("lower", filterLower) 69 RegisterFilter("make_list", filterMakelist) 70 RegisterFilter("phone2numeric", filterPhone2numeric) 71 RegisterFilter("pluralize", filterPluralize) 72 RegisterFilter("random", filterRandom) 73 RegisterFilter("removetags", filterRemovetags) 74 RegisterFilter("rjust", filterRjust) 75 RegisterFilter("slice", filterSlice) 76 RegisterFilter("stringformat", filterStringformat) 77 RegisterFilter("striptags", filterStriptags) 78 RegisterFilter("time", filterDate) // time uses filterDate (same golang-format) 79 RegisterFilter("title", filterTitle) 80 RegisterFilter("truncatechars", filterTruncatechars) 81 RegisterFilter("truncatechars_html", filterTruncatecharsHtml) 82 RegisterFilter("truncatewords", filterTruncatewords) 83 RegisterFilter("truncatewords_html", filterTruncatewordsHtml) 84 RegisterFilter("upper", filterUpper) 85 RegisterFilter("urlencode", filterUrlencode) 86 RegisterFilter("urlize", filterUrlize) 87 RegisterFilter("urlizetrunc", filterUrlizetrunc) 88 RegisterFilter("wordcount", filterWordcount) 89 RegisterFilter("wordwrap", filterWordwrap) 90 RegisterFilter("yesno", filterYesno) 91 92 RegisterFilter("float", filterFloat) // pongo-specific 93 RegisterFilter("integer", filterInteger) // pongo-specific 94 } 95 96 func filterTruncatecharsHelper(s string, newLen int) string { 97 runes := []rune(s) 98 if newLen < len(runes) { 99 if newLen >= 3 { 100 return fmt.Sprintf("%s...", string(runes[:newLen-3])) 101 } 102 // Not enough space for the ellipsis 103 return string(runes[:newLen]) 104 } 105 return string(runes) 106 } 107 108 func filterTruncateHtmlHelper(value string, new_output *bytes.Buffer, cond func() bool, fn func(c rune, s int, idx int) int, finalize func()) { 109 vLen := len(value) 110 tag_stack := make([]string, 0) 111 idx := 0 112 113 for idx < vLen && !cond() { 114 c, s := utf8.DecodeRuneInString(value[idx:]) 115 if c == utf8.RuneError { 116 idx += s 117 continue 118 } 119 120 if c == '<' { 121 new_output.WriteRune(c) 122 idx += s // consume "<" 123 124 if idx+1 < vLen { 125 if value[idx] == '/' { 126 // Close tag 127 128 new_output.WriteString("/") 129 130 tag := "" 131 idx += 1 // consume "/" 132 133 for idx < vLen { 134 c2, size2 := utf8.DecodeRuneInString(value[idx:]) 135 if c2 == utf8.RuneError { 136 idx += size2 137 continue 138 } 139 140 // End of tag found 141 if c2 == '>' { 142 idx++ // consume ">" 143 break 144 } 145 tag += string(c2) 146 idx += size2 147 } 148 149 if len(tag_stack) > 0 { 150 // Ideally, the close tag is TOP of tag stack 151 // In malformed HTML, it must not be, so iterate through the stack and remove the tag 152 for i := len(tag_stack) - 1; i >= 0; i-- { 153 if tag_stack[i] == tag { 154 // Found the tag 155 tag_stack[i] = tag_stack[len(tag_stack)-1] 156 tag_stack = tag_stack[:len(tag_stack)-1] 157 break 158 } 159 } 160 } 161 162 new_output.WriteString(tag) 163 new_output.WriteString(">") 164 } else { 165 // Open tag 166 167 tag := "" 168 169 params := false 170 for idx < vLen { 171 c2, size2 := utf8.DecodeRuneInString(value[idx:]) 172 if c2 == utf8.RuneError { 173 idx += size2 174 continue 175 } 176 177 new_output.WriteRune(c2) 178 179 // End of tag found 180 if c2 == '>' { 181 idx++ // consume ">" 182 break 183 } 184 185 if !params { 186 if c2 == ' ' { 187 params = true 188 } else { 189 tag += string(c2) 190 } 191 } 192 193 idx += size2 194 } 195 196 // Add tag to stack 197 tag_stack = append(tag_stack, tag) 198 } 199 } 200 } else { 201 idx = fn(c, s, idx) 202 } 203 } 204 205 finalize() 206 207 for i := len(tag_stack) - 1; i >= 0; i-- { 208 tag := tag_stack[i] 209 // Close everything from the regular tag stack 210 new_output.WriteString(fmt.Sprintf("</%s>", tag)) 211 } 212 } 213 214 func filterTruncatechars(in *Value, param *Value) (*Value, *Error) { 215 s := in.String() 216 newLen := param.Integer() 217 return AsValue(filterTruncatecharsHelper(s, newLen)), nil 218 } 219 220 func filterTruncatecharsHtml(in *Value, param *Value) (*Value, *Error) { 221 value := in.String() 222 newLen := max(param.Integer()-3, 0) 223 224 new_output := bytes.NewBuffer(nil) 225 226 textcounter := 0 227 228 filterTruncateHtmlHelper(value, new_output, func() bool { 229 return textcounter >= newLen 230 }, func(c rune, s int, idx int) int { 231 textcounter++ 232 new_output.WriteRune(c) 233 234 return idx + s 235 }, func() { 236 if textcounter >= newLen && textcounter < len(value) { 237 new_output.WriteString("...") 238 } 239 }) 240 241 return AsSafeValue(new_output.String()), nil 242 } 243 244 func filterTruncatewords(in *Value, param *Value) (*Value, *Error) { 245 words := strings.Fields(in.String()) 246 n := param.Integer() 247 if n <= 0 { 248 return AsValue(""), nil 249 } 250 nlen := min(len(words), n) 251 out := make([]string, 0, nlen) 252 for i := 0; i < nlen; i++ { 253 out = append(out, words[i]) 254 } 255 256 if n < len(words) { 257 out = append(out, "...") 258 } 259 260 return AsValue(strings.Join(out, " ")), nil 261 } 262 263 func filterTruncatewordsHtml(in *Value, param *Value) (*Value, *Error) { 264 value := in.String() 265 newLen := max(param.Integer(), 0) 266 267 new_output := bytes.NewBuffer(nil) 268 269 wordcounter := 0 270 271 filterTruncateHtmlHelper(value, new_output, func() bool { 272 return wordcounter >= newLen 273 }, func(_ rune, _ int, idx int) int { 274 // Get next word 275 word_found := false 276 277 for idx < len(value) { 278 c2, size2 := utf8.DecodeRuneInString(value[idx:]) 279 if c2 == utf8.RuneError { 280 idx += size2 281 continue 282 } 283 284 if c2 == '<' { 285 // HTML tag start, don't consume it 286 return idx 287 } 288 289 new_output.WriteRune(c2) 290 idx += size2 291 292 if c2 == ' ' || c2 == '.' || c2 == ',' || c2 == ';' { 293 // Word ends here, stop capturing it now 294 break 295 } else { 296 word_found = true 297 } 298 } 299 300 if word_found { 301 wordcounter++ 302 } 303 304 return idx 305 }, func() { 306 if wordcounter >= newLen { 307 new_output.WriteString("...") 308 } 309 }) 310 311 return AsSafeValue(new_output.String()), nil 312 } 313 314 func filterEscape(in *Value, param *Value) (*Value, *Error) { 315 output := strings.Replace(in.String(), "&", "&", -1) 316 output = strings.Replace(output, ">", ">", -1) 317 output = strings.Replace(output, "<", "<", -1) 318 output = strings.Replace(output, "\"", """, -1) 319 output = strings.Replace(output, "'", "'", -1) 320 return AsValue(output), nil 321 } 322 323 func filterSafe(in *Value, param *Value) (*Value, *Error) { 324 return in, nil // nothing to do here, just to keep track of the safe application 325 } 326 327 func filterEscapejs(in *Value, param *Value) (*Value, *Error) { 328 sin := in.String() 329 330 var b bytes.Buffer 331 332 idx := 0 333 for idx < len(sin) { 334 c, size := utf8.DecodeRuneInString(sin[idx:]) 335 if c == utf8.RuneError { 336 idx += size 337 continue 338 } 339 340 if c == '\\' { 341 // Escape seq? 342 if idx+1 < len(sin) { 343 switch sin[idx+1] { 344 case 'r': 345 b.WriteString(fmt.Sprintf(`\u%04X`, '\r')) 346 idx += 2 347 continue 348 case 'n': 349 b.WriteString(fmt.Sprintf(`\u%04X`, '\n')) 350 idx += 2 351 continue 352 /*case '\'': 353 b.WriteString(fmt.Sprintf(`\u%04X`, '\'')) 354 idx += 2 355 continue 356 case '"': 357 b.WriteString(fmt.Sprintf(`\u%04X`, '"')) 358 idx += 2 359 continue*/ 360 } 361 } 362 } 363 364 if (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == ' ' || c == '/' { 365 b.WriteRune(c) 366 } else { 367 b.WriteString(fmt.Sprintf(`\u%04X`, c)) 368 } 369 370 idx += size 371 } 372 373 return AsValue(b.String()), nil 374 } 375 376 func filterAdd(in *Value, param *Value) (*Value, *Error) { 377 if in.IsNumber() && param.IsNumber() { 378 if in.IsFloat() || param.IsFloat() { 379 return AsValue(in.Float() + param.Float()), nil 380 } else { 381 return AsValue(in.Integer() + param.Integer()), nil 382 } 383 } 384 // If in/param is not a number, we're relying on the 385 // Value's String() convertion and just add them both together 386 return AsValue(in.String() + param.String()), nil 387 } 388 389 func filterAddslashes(in *Value, param *Value) (*Value, *Error) { 390 output := strings.Replace(in.String(), "\\", "\\\\", -1) 391 output = strings.Replace(output, "\"", "\\\"", -1) 392 output = strings.Replace(output, "'", "\\'", -1) 393 return AsValue(output), nil 394 } 395 396 func filterCut(in *Value, param *Value) (*Value, *Error) { 397 return AsValue(strings.Replace(in.String(), param.String(), "", -1)), nil 398 } 399 400 func filterLength(in *Value, param *Value) (*Value, *Error) { 401 return AsValue(in.Len()), nil 402 } 403 404 func filterLengthis(in *Value, param *Value) (*Value, *Error) { 405 return AsValue(in.Len() == param.Integer()), nil 406 } 407 408 func filterDefault(in *Value, param *Value) (*Value, *Error) { 409 if !in.IsTrue() { 410 return param, nil 411 } 412 return in, nil 413 } 414 415 func filterDefaultIfNone(in *Value, param *Value) (*Value, *Error) { 416 if in.IsNil() { 417 return param, nil 418 } 419 return in, nil 420 } 421 422 func filterDivisibleby(in *Value, param *Value) (*Value, *Error) { 423 if param.Integer() == 0 { 424 return AsValue(false), nil 425 } 426 return AsValue(in.Integer()%param.Integer() == 0), nil 427 } 428 429 func filterFirst(in *Value, param *Value) (*Value, *Error) { 430 if in.CanSlice() && in.Len() > 0 { 431 return in.Index(0), nil 432 } 433 return AsValue(""), nil 434 } 435 436 func filterFloatformat(in *Value, param *Value) (*Value, *Error) { 437 val := in.Float() 438 439 decimals := -1 440 if !param.IsNil() { 441 // Any argument provided? 442 decimals = param.Integer() 443 } 444 445 // if the argument is not a number (e. g. empty), the default 446 // behaviour is trim the result 447 trim := !param.IsNumber() 448 449 if decimals <= 0 { 450 // argument is negative or zero, so we 451 // want the output being trimmed 452 decimals = -decimals 453 trim = true 454 } 455 456 if trim { 457 // Remove zeroes 458 if float64(int(val)) == val { 459 return AsValue(in.Integer()), nil 460 } 461 } 462 463 return AsValue(strconv.FormatFloat(val, 'f', decimals, 64)), nil 464 } 465 466 func filterGetdigit(in *Value, param *Value) (*Value, *Error) { 467 i := param.Integer() 468 l := len(in.String()) // do NOT use in.Len() here! 469 if i <= 0 || i > l { 470 return in, nil 471 } 472 return AsValue(in.String()[l-i] - 48), nil 473 } 474 475 const filterIRIChars = "/#%[]=:;$&()+,!?*@'~" 476 477 func filterIriencode(in *Value, param *Value) (*Value, *Error) { 478 var b bytes.Buffer 479 480 sin := in.String() 481 for _, r := range sin { 482 if strings.IndexRune(filterIRIChars, r) >= 0 { 483 b.WriteRune(r) 484 } else { 485 b.WriteString(url.QueryEscape(string(r))) 486 } 487 } 488 489 return AsValue(b.String()), nil 490 } 491 492 func filterJoin(in *Value, param *Value) (*Value, *Error) { 493 if !in.CanSlice() { 494 return in, nil 495 } 496 sep := param.String() 497 sl := make([]string, 0, in.Len()) 498 for i := 0; i < in.Len(); i++ { 499 sl = append(sl, in.Index(i).String()) 500 } 501 return AsValue(strings.Join(sl, sep)), nil 502 } 503 504 func filterLast(in *Value, param *Value) (*Value, *Error) { 505 if in.CanSlice() && in.Len() > 0 { 506 return in.Index(in.Len() - 1), nil 507 } 508 return AsValue(""), nil 509 } 510 511 func filterUpper(in *Value, param *Value) (*Value, *Error) { 512 return AsValue(strings.ToUpper(in.String())), nil 513 } 514 515 func filterLower(in *Value, param *Value) (*Value, *Error) { 516 return AsValue(strings.ToLower(in.String())), nil 517 } 518 519 func filterMakelist(in *Value, param *Value) (*Value, *Error) { 520 s := in.String() 521 result := make([]string, 0, len(s)) 522 for _, c := range s { 523 result = append(result, string(c)) 524 } 525 return AsValue(result), nil 526 } 527 528 func filterCapfirst(in *Value, param *Value) (*Value, *Error) { 529 if in.Len() <= 0 { 530 return AsValue(""), nil 531 } 532 t := in.String() 533 r, size := utf8.DecodeRuneInString(t) 534 return AsValue(strings.ToUpper(string(r)) + t[size:]), nil 535 } 536 537 func filterCenter(in *Value, param *Value) (*Value, *Error) { 538 width := param.Integer() 539 slen := in.Len() 540 if width <= slen { 541 return in, nil 542 } 543 544 spaces := width - slen 545 left := spaces/2 + spaces%2 546 right := spaces / 2 547 548 return AsValue(fmt.Sprintf("%s%s%s", strings.Repeat(" ", left), 549 in.String(), strings.Repeat(" ", right))), nil 550 } 551 552 func filterDate(in *Value, param *Value) (*Value, *Error) { 553 t, is_time := in.Interface().(time.Time) 554 if !is_time { 555 return nil, &Error{ 556 Sender: "filter:date", 557 ErrorMsg: "Filter input argument must be of type 'time.Time'.", 558 } 559 } 560 return AsValue(t.Format(param.String())), nil 561 } 562 563 func filterFloat(in *Value, param *Value) (*Value, *Error) { 564 return AsValue(in.Float()), nil 565 } 566 567 func filterInteger(in *Value, param *Value) (*Value, *Error) { 568 return AsValue(in.Integer()), nil 569 } 570 571 func filterLinebreaks(in *Value, param *Value) (*Value, *Error) { 572 if in.Len() == 0 { 573 return in, nil 574 } 575 576 var b bytes.Buffer 577 578 // Newline = <br /> 579 // Double newline = <p>...</p> 580 lines := strings.Split(in.String(), "\n") 581 lenlines := len(lines) 582 583 opened := false 584 585 for idx, line := range lines { 586 587 if !opened { 588 b.WriteString("<p>") 589 opened = true 590 } 591 592 b.WriteString(line) 593 594 if idx < lenlines-1 && strings.TrimSpace(lines[idx]) != "" { 595 // We've not reached the end 596 if strings.TrimSpace(lines[idx+1]) == "" { 597 // Next line is empty 598 if opened { 599 b.WriteString("</p>") 600 opened = false 601 } 602 } else { 603 b.WriteString("<br />") 604 } 605 } 606 } 607 608 if opened { 609 b.WriteString("</p>") 610 } 611 612 return AsValue(b.String()), nil 613 } 614 615 func filterLinebreaksbr(in *Value, param *Value) (*Value, *Error) { 616 return AsValue(strings.Replace(in.String(), "\n", "<br />", -1)), nil 617 } 618 619 func filterLinenumbers(in *Value, param *Value) (*Value, *Error) { 620 lines := strings.Split(in.String(), "\n") 621 output := make([]string, 0, len(lines)) 622 for idx, line := range lines { 623 output = append(output, fmt.Sprintf("%d. %s", idx+1, line)) 624 } 625 return AsValue(strings.Join(output, "\n")), nil 626 } 627 628 func filterLjust(in *Value, param *Value) (*Value, *Error) { 629 times := param.Integer() - in.Len() 630 if times < 0 { 631 times = 0 632 } 633 return AsValue(fmt.Sprintf("%s%s", in.String(), strings.Repeat(" ", times))), nil 634 } 635 636 func filterUrlencode(in *Value, param *Value) (*Value, *Error) { 637 return AsValue(url.QueryEscape(in.String())), nil 638 } 639 640 // TODO: This regexp could do some work 641 var filterUrlizeURLRegexp = regexp.MustCompile(`((((http|https)://)|www\.|((^|[ ])[0-9A-Za-z_\-]+(\.com|\.net|\.org|\.info|\.biz|\.de))))(?U:.*)([ ]+|$)`) 642 var filterUrlizeEmailRegexp = regexp.MustCompile(`(\w+@\w+\.\w{2,4})`) 643 644 func filterUrlizeHelper(input string, autoescape bool, trunc int) string { 645 sout := filterUrlizeURLRegexp.ReplaceAllStringFunc(input, func(raw_url string) string { 646 var prefix string 647 var suffix string 648 if strings.HasPrefix(raw_url, " ") { 649 prefix = " " 650 } 651 if strings.HasSuffix(raw_url, " ") { 652 suffix = " " 653 } 654 655 raw_url = strings.TrimSpace(raw_url) 656 657 t, err := ApplyFilter("iriencode", AsValue(raw_url), nil) 658 if err != nil { 659 panic(err) 660 } 661 url := t.String() 662 663 if !strings.HasPrefix(url, "http") { 664 url = fmt.Sprintf("http://%s", url) 665 } 666 667 title := raw_url 668 669 if trunc > 3 && len(title) > trunc { 670 title = fmt.Sprintf("%s...", title[:trunc-3]) 671 } 672 673 if autoescape { 674 t, err := ApplyFilter("escape", AsValue(title), nil) 675 if err != nil { 676 panic(err) 677 } 678 title = t.String() 679 } 680 681 return fmt.Sprintf(`%s<a href="%s" rel="nofollow">%s</a>%s`, prefix, url, title, suffix) 682 }) 683 684 sout = filterUrlizeEmailRegexp.ReplaceAllStringFunc(sout, func(mail string) string { 685 686 title := mail 687 688 if trunc > 3 && len(title) > trunc { 689 title = fmt.Sprintf("%s...", title[:trunc-3]) 690 } 691 692 return fmt.Sprintf(`<a href="mailto:%s">%s</a>`, mail, title) 693 }) 694 695 return sout 696 } 697 698 func filterUrlize(in *Value, param *Value) (*Value, *Error) { 699 autoescape := true 700 if param.IsBool() { 701 autoescape = param.Bool() 702 } 703 704 return AsValue(filterUrlizeHelper(in.String(), autoescape, -1)), nil 705 } 706 707 func filterUrlizetrunc(in *Value, param *Value) (*Value, *Error) { 708 return AsValue(filterUrlizeHelper(in.String(), true, param.Integer())), nil 709 } 710 711 func filterStringformat(in *Value, param *Value) (*Value, *Error) { 712 return AsValue(fmt.Sprintf(param.String(), in.Interface())), nil 713 } 714 715 var re_striptags = regexp.MustCompile("<[^>]*?>") 716 717 func filterStriptags(in *Value, param *Value) (*Value, *Error) { 718 s := in.String() 719 720 // Strip all tags 721 s = re_striptags.ReplaceAllString(s, "") 722 723 return AsValue(strings.TrimSpace(s)), nil 724 } 725 726 // https://en.wikipedia.org/wiki/Phoneword 727 var filterPhone2numericMap = map[string]string{ 728 "a": "2", "b": "2", "c": "2", "d": "3", "e": "3", "f": "3", "g": "4", "h": "4", "i": "4", "j": "5", "k": "5", 729 "l": "5", "m": "6", "n": "6", "o": "6", "p": "7", "q": "7", "r": "7", "s": "7", "t": "8", "u": "8", "v": "8", 730 "w": "9", "x": "9", "y": "9", "z": "9", 731 } 732 733 func filterPhone2numeric(in *Value, param *Value) (*Value, *Error) { 734 sin := in.String() 735 for k, v := range filterPhone2numericMap { 736 sin = strings.Replace(sin, k, v, -1) 737 sin = strings.Replace(sin, strings.ToUpper(k), v, -1) 738 } 739 return AsValue(sin), nil 740 } 741 742 func filterPluralize(in *Value, param *Value) (*Value, *Error) { 743 if in.IsNumber() { 744 // Works only on numbers 745 if param.Len() > 0 { 746 endings := strings.Split(param.String(), ",") 747 if len(endings) > 2 { 748 return nil, &Error{ 749 Sender: "filter:pluralize", 750 ErrorMsg: "You cannot pass more than 2 arguments to filter 'pluralize'.", 751 } 752 } 753 if len(endings) == 1 { 754 // 1 argument 755 if in.Integer() != 1 { 756 return AsValue(endings[0]), nil 757 } 758 } else { 759 if in.Integer() != 1 { 760 // 2 arguments 761 return AsValue(endings[1]), nil 762 } 763 return AsValue(endings[0]), nil 764 } 765 } else { 766 if in.Integer() != 1 { 767 // return default 's' 768 return AsValue("s"), nil 769 } 770 } 771 772 return AsValue(""), nil 773 } else { 774 return nil, &Error{ 775 Sender: "filter:pluralize", 776 ErrorMsg: "Filter 'pluralize' does only work on numbers.", 777 } 778 } 779 } 780 781 func filterRandom(in *Value, param *Value) (*Value, *Error) { 782 if !in.CanSlice() || in.Len() <= 0 { 783 return in, nil 784 } 785 i := rand.Intn(in.Len()) 786 return in.Index(i), nil 787 } 788 789 func filterRemovetags(in *Value, param *Value) (*Value, *Error) { 790 s := in.String() 791 tags := strings.Split(param.String(), ",") 792 793 // Strip only specific tags 794 for _, tag := range tags { 795 re := regexp.MustCompile(fmt.Sprintf("</?%s/?>", tag)) 796 s = re.ReplaceAllString(s, "") 797 } 798 799 return AsValue(strings.TrimSpace(s)), nil 800 } 801 802 func filterRjust(in *Value, param *Value) (*Value, *Error) { 803 return AsValue(fmt.Sprintf(fmt.Sprintf("%%%ds", param.Integer()), in.String())), nil 804 } 805 806 func filterSlice(in *Value, param *Value) (*Value, *Error) { 807 comp := strings.Split(param.String(), ":") 808 if len(comp) != 2 { 809 return nil, &Error{ 810 Sender: "filter:slice", 811 ErrorMsg: "Slice string must have the format 'from:to' [from/to can be omitted, but the ':' is required]", 812 } 813 } 814 815 if !in.CanSlice() { 816 return in, nil 817 } 818 819 from := AsValue(comp[0]).Integer() 820 to := in.Len() 821 822 if from > to { 823 from = to 824 } 825 826 vto := AsValue(comp[1]).Integer() 827 if vto >= from && vto <= in.Len() { 828 to = vto 829 } 830 831 return in.Slice(from, to), nil 832 } 833 834 func filterTitle(in *Value, param *Value) (*Value, *Error) { 835 if !in.IsString() { 836 return AsValue(""), nil 837 } 838 return AsValue(strings.Title(strings.ToLower(in.String()))), nil 839 } 840 841 func filterWordcount(in *Value, param *Value) (*Value, *Error) { 842 return AsValue(len(strings.Fields(in.String()))), nil 843 } 844 845 func filterWordwrap(in *Value, param *Value) (*Value, *Error) { 846 words := strings.Fields(in.String()) 847 words_len := len(words) 848 wrap_at := param.Integer() 849 if wrap_at <= 0 { 850 return in, nil 851 } 852 853 linecount := words_len/wrap_at + words_len%wrap_at 854 lines := make([]string, 0, linecount) 855 for i := 0; i < linecount; i++ { 856 lines = append(lines, strings.Join(words[wrap_at*i:min(wrap_at*(i+1), words_len)], " ")) 857 } 858 return AsValue(strings.Join(lines, "\n")), nil 859 } 860 861 func filterYesno(in *Value, param *Value) (*Value, *Error) { 862 choices := map[int]string{ 863 0: "yes", 864 1: "no", 865 2: "maybe", 866 } 867 param_string := param.String() 868 custom_choices := strings.Split(param_string, ",") 869 if len(param_string) > 0 { 870 if len(custom_choices) > 3 { 871 return nil, &Error{ 872 Sender: "filter:yesno", 873 ErrorMsg: fmt.Sprintf("You cannot pass more than 3 options to the 'yesno'-filter (got: '%s').", param_string), 874 } 875 } 876 if len(custom_choices) < 2 { 877 return nil, &Error{ 878 Sender: "filter:yesno", 879 ErrorMsg: fmt.Sprintf("You must pass either no or at least 2 arguments to the 'yesno'-filter (got: '%s').", param_string), 880 } 881 } 882 883 // Map to the options now 884 choices[0] = custom_choices[0] 885 choices[1] = custom_choices[1] 886 if len(custom_choices) == 3 { 887 choices[2] = custom_choices[2] 888 } 889 } 890 891 // maybe 892 if in.IsNil() { 893 return AsValue(choices[2]), nil 894 } 895 896 // yes 897 if in.IsTrue() { 898 return AsValue(choices[0]), nil 899 } 900 901 // no 902 return AsValue(choices[1]), nil 903 }