Repository: leekchan/gtf
Branch: master
Commit: 5fba33c5b00b
Files: 7
Total size: 47.4 KB
Directory structure:
gitextract_790f95lo/
├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── gtf.go
├── gtf_test.go
└── gtf_text_test.go
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe
*.test
*.prof
================================================
FILE: .travis.yml
================================================
language: go
go:
- 1.4
- 1.5
- 1.6
- 1.7
- 1.8
- tip
env:
global:
- BUILD_GOARCH=amd64
matrix:
- BUILD_GOOS=linux
- BUILD_GOOS=darwin
- BUILD_GOOS=windows
before_install:
- go get github.com/axw/gocov/gocov
- go get github.com/mattn/goveralls
- if ! go get code.google.com/p/go.tools/cmd/cover; then go get golang.org/x/tools/cmd/cover; fi
script:
- $HOME/gopath/bin/goveralls -service=travis-ci
================================================
FILE: LICENSE
================================================
The MIT License (MIT)
Copyright (c) 2015 Kyoung-chan Lee (leekchan@gmail.com)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# gtf - a useful set of Golang Template Functions
[](https://travis-ci.org/leekchan/gtf)
[](https://coveralls.io/github/leekchan/gtf?branch=master)
[](https://godoc.org/github.com/leekchan/gtf)
gtf is a useful set of Golang Template Functions. The goal of this project is implementing all built-in template filters of Django & Jinja2.
## Basic usages
### Method 1 : Uses gtf.New
gtf.New is a wrapper function of [template.New](https://golang.org/pkg/html/template/#New). It automatically adds the gtf functions to the template's function map and returns [template.Template](http://golang.org/pkg/html/template/#Template).
```Go
package main
import (
"net/http"
"github.com/leekchan/gtf"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
filesize := 554832114
tpl, _ := gtf.New("test").Parse("{{ . | filesizeformat }}")
tpl.Execute(w, filesize)
})
http.ListenAndServe(":8080", nil)
}
```
### Method 2 : Adds gtf functions to the existing template.
You can also add the gtf functions to the existing template(html/template package). Just call ".Funcs(gtf.GtfFuncMap)".
```Go
package main
import (
"net/http"
"html/template"
"github.com/leekchan/gtf"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
filesize := 554832114
tpl, _ := template.New("test").Funcs(gtf.GtfFuncMap).Parse("{{ . | filesizeformat }}")
tpl.Execute(w, filesize)
})
http.ListenAndServe(":8080", nil)
}
```
When you use the "text/template" package, call ".Funcs(gtf.GtfTextFuncMap)".
```Go
package main
import (
"os"
"text/template"
"github.com/leekchan/gtf"
)
func main() {
filesize := 554832114
tpl, _ := template.New("test").Funcs(gtf.GtfTextFuncMap).Parse("{{ . | filesizeformat }}")
tpl.Execute(os.Stdout, filesize)
}
```
## Integration
You can use gtf with any web frameworks (revel, beego, martini, gin, etc) which use the Golang's built-in [html/template package](http://golang.org/pkg/html/template/).
### Injection
You can inject gtf functions into your webframework's original FuncMap by calling "gtf.Inject" / "gtf.ForceInject" / "gtf.InjectWithPrefix".
#### gtf.Inject
gtf.Inject injects gtf functions into the passed FuncMap. It does not overwrite the original function which have same name as a gtf function.
```Go
Inject(originalFuncMap)
```
#### gtf.ForceInject
gtf.ForceInject injects gtf functions into the passed FuncMap. It overwrites the original function which have same name as a gtf function.
```Go
ForceInject(originalFuncMap)
```
#### gtf.InjectWithPrefix
gtf.Inject injects gtf functions into the passed FuncMap. It prefixes the gtf functions with the specified prefix. If there are many function which have same names as the gtf functions, you can use this function to prefix the gtf functions.
```Go
InjectWithPrefix(originalFuncMap, "gtf_") // prefix : gtf_
```
### [Revel](http://revel.github.io/) integration
Calling "gtf.Inject(revel.TemplateFuncs)" injects gtf functions into revel.TemplateFuncs. Just add this one line in init() of init.go, and use gtf functions in your templates! :)
```Go
// init.go
package app
import "github.com/revel/revel"
import "github.com/leekchan/gtf"
func init() {
gtf.Inject(revel.TemplateFuncs)
}
```
### [Beego](http://beego.me/) integration
Add these three lines before "beego.Run()" in your main() function. This code snippet will inject gtf functions into beego's FuncMap.
```Go
for k, v := range gtf.GtfFuncMap {
beego.AddFuncMap(k, v)
}
```
**Full example:**
```Go
package main
import (
"github.com/astaxie/beego"
"github.com/beego/i18n"
"github.com/beego/samples/WebIM/controllers"
"github.com/leekchan/gtf"
)
const (
APP_VER = "0.1.1.0227"
)
func main() {
beego.Info(beego.AppName, APP_VER)
// Register routers.
beego.Router("/", &controllers.AppController{})
// Indicate AppController.Join method to handle POST requests.
beego.Router("/join", &controllers.AppController{}, "post:Join")
// Long polling.
beego.Router("/lp", &controllers.LongPollingController{}, "get:Join")
beego.Router("/lp/post", &controllers.LongPollingController{})
beego.Router("/lp/fetch", &controllers.LongPollingController{}, "get:Fetch")
// WebSocket.
beego.Router("/ws", &controllers.WebSocketController{})
beego.Router("/ws/join", &controllers.WebSocketController{}, "get:Join")
// Register template functions.
beego.AddFuncMap("i18n", i18n.Tr)
// Register gtf functions.
for k, v := range gtf.GtfFuncMap {
beego.AddFuncMap(k, v)
}
beego.Run()
}
```
### Other web frameworks (TODO)
I will add the detailed integration guides for other web frameworks soon!
## Safety
All gtf functions have their own recovery logics. The basic behavior of the recovery logic is silently swallowing all unexpected panics. All gtf functions would not make any panics in runtime. (**Production Ready!**)
If a panic occurs inside a gtf function, the function will silently swallow the panic and return "" (empty string). If you meet any unexpected empty output, [please make an issue](https://github.com/leekchan/gtf/issues/new)! :)
## Reference
### Index
* [replace](#replace)
* [findreplace](#findreplace)
* [default](#default)
* [length](#length)
* [lower](#lower)
* [upper](#upper)
* [truncatechars](#truncatechars)
* [urlencode](#urlencode)
* [wordcount](#wordcount)
* [divisibleby](#divisibleby)
* [lengthis](#lengthis)
* [trim](#trim)
* [capfirst](#capfirst)
* [pluralize](#pluralize)
* [yesno](#yesno)
* [rjust](#rjust)
* [ljust](#ljust)
* [center](#center)
* [filesizeformat](#filesizeformat)
* [apnumber](#apnumber)
* [intcomma](#intcomma)
* [ordinal](#ordinal)
* [first](#first)
* [last](#last)
* [join](#join)
* [slice](#slice)
* [random](#random)
* [striptags](#striptags)
#### replace
Removes all values of arg from the given string.
* supported value types : string
* supported argument types : string
```
{{ value | replace " " }}
```
If value is "The Go Programming Language", the output will be "TheGoProgrammingLanguage".
#### findreplace
Replaces all instances of the first argument with the second.
* supported value types : string
* supported argument types : string
```
{{ value | findreplace " " "-" }}
```
If value is "The Go Programming Language", the output will be "The-Go-Programming-Language".
#### default
1. If the given string is ""(empty string), uses the given default argument.
1. If the given array/slice/map is empty, uses the given default argument.
1. If the given boolean value is false, uses the given default argument.
* supported value types : string, array, slice, map, boolean
* supported argument types : all
```
{{ value | default "default value" }}
```
If value is ""(the empty string), the output will be "default value".
#### length
Returns the length of the given string/array/slice/map.
* supported value types : string, array, slice, map
This function also supports unicode strings.
```
{{ value | length }}
```
If value is "The Go Programming Language", the output will be 27.
#### lower
Converts the given string into all lowercase.
* supported value types : string
```
{{ value | lower }}
```
If value is "The Go Programming Language", the output will be "the go programming language".
#### upper
Converts the given string into all uppercase.
* supported value types : string
```
{{ value | upper }}
```
If value is "The Go Programming Language", the output will be "THE GO PROGRAMMING LANGUAGE".
#### truncatechars
Truncates the given string if it is longer than the specified number of characters. Truncated strings will end with a translatable ellipsis sequence ("...")
* supported value types : string
**Argument:** Number of characters to truncate to
This function also supports unicode strings.
```
{{ value | truncatechars 12 }}
```
**Examples**
1. If input is {{ "The Go Programming Language" | truncatechars 12 }}, the output will be "The Go Pr...". (basic string)
1. If input is {{ "안녕하세요. 반갑습니다." | truncatechars 12 }}, the output will be "안녕하세요. 반갑...". (unicode)
1. If input is {{ "안녕하세요. The Go Programming Language" | truncatechars 30 }}, the output will be "안녕하세요. The Go Programming L...". (unicode)
1. If input is {{ "The" | truncatechars 30 }}, the output will be "The". (If the length of the given string is shorter than the argument, the output will be the original string.)
1. If input is {{ "The Go Programming Language" | truncatechars 3 }}, the output will be "The". (If the argument is less than or equal to 3, the output will not contain "...".)
1. If input is {{ "The Go" | truncatechars -1 }}, the output will be "The Go". (If the argument is less than 0, the argument will be ignored.)
#### urlencode
Escapes the given string for use in a URL.
* supported value types : string
```
{{ value | urlencode }}
```
If value is "http://www.example.org/foo?a=b&c=d", the output will be "http%3A%2F%2Fwww.example.org%2Ffoo%3Fa%3Db%26c%3Dd".
#### wordcount
Returns the number of words.
* supported value types : string
```
{{ value | wordcount }}
```
If value is "The Go Programming Language", the output will be 4.
#### divisibleby
Returns true if the value is divisible by the argument.
* supported value types : int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64
* supported argument types : int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64
```
{{ value | divisibleby 3 }}
```
**Examples**
1. If input is {{ 21 | divisibleby 3 }}, the output will be true.
1. If input is {{ 21 | divisibleby 4 }}, the output will be false.
1. If input is {{ 3.0 | divisibleby 1.5 }}, the output will be true.
#### lengthis
Returns true if the value's length is the argument, or false otherwise.
* supported value types : string, array, slice, map
* supported argument types : int
```
{{ value | lengthis 3 }}
```
This function also supports unicode strings.
**Examples**
1. If input is {{ "Go" | lengthis 2 }}, the output will be true.
1. If input is {{ "안녕하세요. Go!" | lengthis 10 }}, the output will be true.
#### trim
Strips leading and trailing whitespace.
* supported value types : string
```
{{ value | trim }}
```
#### capfirst
Capitalizes the first character of the given string.
* supported value types : string
```
{{ value | capfirst }}
```
If value is "the go programming language", the output will be "The go programming language".
#### pluralize
Returns a plural suffix if the value is not 1. You can specify both a singular and plural suffix, separated by a comma.
**Argument:** singular and plural suffix.
1. "s" --> specify a singular suffix.
2. "y,ies" --> specify both a singular and plural suffix.
* supported value types : int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64
* supported argument types : string
```
{{ value | pluralize "s" }}
{{ value | pluralize "y,ies" }}
```
**Examples**
1. You have 0 message{{ 0 | pluralize "s" }} --> You have 0 messages
2. You have 1 message{{ 1 | pluralize "s" }} --> You have 1 message
3. 0 cand{{ 0 | pluralize "y,ies" }} --> 0 candies
4. 1 cand{{ 1 | pluralize "y,ies" }} --> 1 candy
5. 2 cand{{ 2 | pluralize "y,ies" }} --> 2 candies
#### yesno
Returns argument strings according to the given boolean value.
* supported value types : boolean
* supported argument types : string
**Argument:** any value for true and false
```
{{ value | yesno "yes!" "no!" }}
```
#### rjust
Right-aligns the given string in a field of a given width. This function also supports unicode strings.
* supported value types : string
```
{{ value | rjust 10 }}
```
**Examples**
1. If input is {{ "Go" | rjust 10 }}, the output will be " Go".
1. If input is {{ "안녕하세요" | rjust 10 }}, the output will be " 안녕하세요".
#### ljust
Left-aligns the given string in a field of a given width. This function also supports unicode strings.
* supported value types : string
```
{{ value | ljust 10 }}
```
**Examples**
1. If input is {{ "Go" | ljust 10 }}, the output will be "Go ".
1. If input is {{ "안녕하세요" | ljust 10 }}, the output will be "안녕하세요 ".
#### center
Centers the given string in a field of a given width. This function also supports unicode strings.
* supported value types : string
```
{{ value | center 10 }}
```
**Examples**
1. If input is {{ "Go" | center 10 }}, the output will be " Go ".
1. If input is {{ "안녕하세요" | center 10 }}, the output will be " 안녕하세요 ".
#### filesizeformat
Formats the value like a human readable file size.
* supported value types : int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64
```
{{ value | filesizeformat }}
```
**Examples**
1. {{ 234 | filesizeformat }} --> "234 bytes"
1. {{ 12345 | filesizeformat }} --> "12.1 KB"
1. {{ 12345.35335 | filesizeformat }} --> "12.1 KB"
1. {{ 1048576 | filesizeformat } --> "1 MB"
1. {{ 554832114 | filesizeformat }} --> "529.1 MB"
1. {{ 14868735121 | filesizeformat }} --> "13.8 GB"
1. {{ 14868735121365 | filesizeformat }} --> "13.5 TB"
1. {{ 1486873512136523 | filesizeformat }} --> "1.3 PB"
#### apnumber
For numbers 1-9, returns the number spelled out. Otherwise, returns the number.
* supported value types : int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64
```
{{ value | apnumber }}
```
**Examples**
1. {{ 1 | apnumber }} --> one
1. {{ 2 | apnumber }} --> two
1. {{ 3 | apnumber }} --> three
1. {{ 9 | apnumber }} --> nine
1. {{ 10 | apnumber }} --> 10
1. {{ 1000 | apnumber }} --> 1000
#### intcomma
Converts an integer to a string containing commas every three digits.
* supported value types : int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64
```
{{ value | intcomma }}
```
**Examples**
1. {{ 1000 | intcomma }} --> 1,000
1. {{ -1000 | intcomma }} --> -1,000
1. {{ 1578652313 | intcomma }} --> 1,578,652,313
#### ordinal
Converts an integer to its ordinal as a string.
* supported value types : int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64
```
{{ value | ordinal }}
```
**Examples**
1. {{ 1 | ordinal }} --> 1st
1. {{ 2 | ordinal }} --> 2nd
1. {{ 3 | ordinal }} --> 3rd
1. {{ 11 | ordinal }} --> 11th
1. {{ 12 | ordinal }} --> 12th
1. {{ 13 | ordinal }} --> 13th
1. {{ 14 | ordinal }} --> 14th
#### first
Returns the first item in the given value.
* supported value types : string, slice, array
This function also supports unicode strings.
```
{{ value | first }}
```
**Examples**
1. If value is the string "The go programming language", the output will be the string "T".
1. If value is the string "안녕하세요", the output will be the string "안". (unicode)
1. If value is the slice []string{"go", "python", "ruby"}, the output will be the string "go".
1. If value is the array [3]string{"go", "python", "ruby"}, the output will be the string "go".
#### last
Returns the last item in the given value.
* supported value types : string, slice, array
This function also supports unicode strings.
```
{{ value | last }}
```
**Examples**
1. If value is the string "The go programming language", the output will be the string "e".
1. If value is the string "안녕하세요", the output will be the string "요". (unicode)
1. If value is the slice []string{"go", "python", "ruby"}, the output will be the string "ruby".
1. If value is the array [3]string{"go", "python", "ruby"}, the output will be the string "ruby".
#### join
Concatenates the given slice to create a single string. The given argument (separator) will be placed between elements in the resulting string.
```
{{ value | join " " }}
```
If value is the slice []string{"go", "python", "ruby"}, the output will be the string "go python ruby"
#### slice
Returns a slice of the given value. The first argument is the start position, and the second argument is the end position.
* supported value types : string, slice
* supported argument types : int
This function also supports unicode strings.
```
{{ value | slice 0 2 }}
```
**Examples**
1. If input is {{ "The go programming language" | slice 0 6 }}, the output will be "The go".
1. If input is {{ "안녕하세요" | slice 0 2 }}, the output will be "안녕". (unicode)
1. If input is {{ []string{"go", "python", "ruby"} | slice 0 2 }}, the output will be []string{"go", "python"}.
#### random
Returns a random item from the given value.
* supported value types : string, slice, array
This function also supports unicode strings.
```
{{ value | random }}
```
**Examples**
1. If input is {{ "The go programming language" | random }}, the output could be "T".
1. If input is {{ "안녕하세요" | random }}, the output could be "안". (unicode)
1. If input is {{ []string{"go", "python", "ruby"} | random }}, the output could be "go".
1. If input is {{ [3]string{"go", "python", "ruby"} | random }}, the output could be "go".
#### randomintrange
Returns a random integer value between the first and second argument
* supported value types : int
```
{{ value | randomintrange 0 5}}
```
**Examples**
1. If input is {{ randomintrange 0 5 }}, the output could be "4".
#### striptags
Makes all possible efforts to strip all [X]HTML tags from given value.
* supported value types : string
This function also supports unicode strings.
```
{{ value | striptags }}
```
**Examples**
1. If input is {{ "<strong>text</strong>" | striptags }}, the output will be "text".
1. If input is {{ "<strong><em>안녕하세요</em></strong>" | striptags }}, the output will be "안녕하세요". (unicode)
1. If input is {{ "<a href="/link">text <strong>안녕하세요</strong></a>" | striptags }}, the output will be "text 안녕하세요".
## Goal
The first goal is implementing all built-in template filters of Django & Jinja2.
* [Django | Built-in filter reference](https://docs.djangoproject.com/en/1.8/ref/templates/builtins/#built-in-filter-reference)
* [Jinja2 | List of Builtin Filters](http://jinja.pocoo.org/docs/dev/templates/#builtin-filters)
The final goal is building a ultimate set which contains hundreds of useful template functions.
## Contributing
I love pull requests :) You can add any useful template functions by submitting a pull request!
================================================
FILE: gtf.go
================================================
package gtf
import (
"fmt"
htmlTemplate "html/template"
"math"
"math/rand"
"net/url"
"reflect"
"regexp"
"strings"
textTemplate "text/template"
"time"
)
var striptagsRegexp = regexp.MustCompile("<[^>]*?>")
// recovery will silently swallow all unexpected panics.
func recovery() {
recover()
}
var GtfTextFuncMap = textTemplate.FuncMap{
"replace": func(s1 string, s2 string) string {
defer recovery()
return strings.Replace(s2, s1, "", -1)
},
"findreplace": func(s1 string, s2 string, s3 string) string {
defer recovery()
return strings.Replace(s3, s1, s2, -1)
},
"title": func(s string) string {
defer recovery()
return strings.Title(s)
},
"default": func(arg interface{}, value interface{}) interface{} {
defer recovery()
v := reflect.ValueOf(value)
switch v.Kind() {
case reflect.String, reflect.Slice, reflect.Array, reflect.Map:
if v.Len() == 0 {
return arg
}
case reflect.Bool:
if !v.Bool() {
return arg
}
default:
return value
}
return value
},
"length": func(value interface{}) int {
defer recovery()
v := reflect.ValueOf(value)
switch v.Kind() {
case reflect.Slice, reflect.Array, reflect.Map:
return v.Len()
case reflect.String:
return len([]rune(v.String()))
}
return 0
},
"lower": func(s string) string {
defer recovery()
return strings.ToLower(s)
},
"upper": func(s string) string {
defer recovery()
return strings.ToUpper(s)
},
"truncatechars": func(n int, s string) string {
defer recovery()
if n < 0 {
return s
}
r := []rune(s)
rLength := len(r)
if n >= rLength {
return s
}
if n > 3 && rLength > 3 {
return string(r[:n-3]) + "..."
}
return string(r[:n])
},
"urlencode": func(s string) string {
defer recovery()
return url.QueryEscape(s)
},
"wordcount": func(s string) int {
defer recovery()
return len(strings.Fields(s))
},
"divisibleby": func(arg interface{}, value interface{}) bool {
defer recovery()
var v float64
switch value.(type) {
case int, int8, int16, int32, int64:
v = float64(reflect.ValueOf(value).Int())
case uint, uint8, uint16, uint32, uint64:
v = float64(reflect.ValueOf(value).Uint())
case float32, float64:
v = reflect.ValueOf(value).Float()
default:
return false
}
var a float64
switch arg.(type) {
case int, int8, int16, int32, int64:
a = float64(reflect.ValueOf(arg).Int())
case uint, uint8, uint16, uint32, uint64:
a = float64(reflect.ValueOf(arg).Uint())
case float32, float64:
a = reflect.ValueOf(arg).Float()
default:
return false
}
return math.Mod(v, a) == 0
},
"lengthis": func(arg int, value interface{}) bool {
defer recovery()
v := reflect.ValueOf(value)
switch v.Kind() {
case reflect.Slice, reflect.Array, reflect.Map:
return v.Len() == arg
case reflect.String:
return len([]rune(v.String())) == arg
}
return false
},
"trim": func(s string) string {
defer recovery()
return strings.TrimSpace(s)
},
"capfirst": func(s string) string {
defer recovery()
return strings.ToUpper(string(s[0])) + s[1:]
},
"pluralize": func(arg string, value interface{}) string {
defer recovery()
flag := false
v := reflect.ValueOf(value)
switch v.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
flag = v.Int() == 1
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
flag = v.Uint() == 1
default:
return ""
}
if !strings.Contains(arg, ",") {
arg = "," + arg
}
bits := strings.Split(arg, ",")
if len(bits) > 2 {
return ""
}
if flag {
return bits[0]
}
return bits[1]
},
"yesno": func(yes string, no string, value bool) string {
defer recovery()
if value {
return yes
}
return no
},
"rjust": func(arg int, value string) string {
defer recovery()
n := arg - len([]rune(value))
if n > 0 {
value = strings.Repeat(" ", n) + value
}
return value
},
"ljust": func(arg int, value string) string {
defer recovery()
n := arg - len([]rune(value))
if n > 0 {
value = value + strings.Repeat(" ", n)
}
return value
},
"center": func(arg int, value string) string {
defer recovery()
n := arg - len([]rune(value))
if n > 0 {
left := n / 2
right := n - left
value = strings.Repeat(" ", left) + value + strings.Repeat(" ", right)
}
return value
},
"filesizeformat": func(value interface{}) string {
defer recovery()
var size float64
v := reflect.ValueOf(value)
switch v.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
size = float64(v.Int())
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
size = float64(v.Uint())
case reflect.Float32, reflect.Float64:
size = v.Float()
default:
return ""
}
var KB float64 = 1 << 10
var MB float64 = 1 << 20
var GB float64 = 1 << 30
var TB float64 = 1 << 40
var PB float64 = 1 << 50
filesizeFormat := func(filesize float64, suffix string) string {
return strings.Replace(fmt.Sprintf("%.1f %s", filesize, suffix), ".0", "", -1)
}
var result string
if size < KB {
result = filesizeFormat(size, "bytes")
} else if size < MB {
result = filesizeFormat(size/KB, "KB")
} else if size < GB {
result = filesizeFormat(size/MB, "MB")
} else if size < TB {
result = filesizeFormat(size/GB, "GB")
} else if size < PB {
result = filesizeFormat(size/TB, "TB")
} else {
result = filesizeFormat(size/PB, "PB")
}
return result
},
"apnumber": func(value interface{}) interface{} {
defer recovery()
name := [10]string{"one", "two", "three", "four", "five",
"six", "seven", "eight", "nine"}
v := reflect.ValueOf(value)
switch v.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
if v.Int() < 10 {
return name[v.Int()-1]
}
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
if v.Uint() < 10 {
return name[v.Uint()-1]
}
}
return value
},
"intcomma": func(value interface{}) string {
defer recovery()
v := reflect.ValueOf(value)
var x uint
minus := false
switch v.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
if v.Int() < 0 {
minus = true
x = uint(-v.Int())
} else {
x = uint(v.Int())
}
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
x = uint(v.Uint())
default:
return ""
}
var result string
for x >= 1000 {
result = fmt.Sprintf(",%03d%s", x%1000, result)
x /= 1000
}
result = fmt.Sprintf("%d%s", x, result)
if minus {
result = "-" + result
}
return result
},
"ordinal": func(value interface{}) string {
defer recovery()
v := reflect.ValueOf(value)
var x uint
switch v.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
if v.Int() < 0 {
return ""
}
x = uint(v.Int())
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
x = uint(v.Uint())
default:
return ""
}
suffixes := [10]string{"th", "st", "nd", "rd", "th", "th", "th", "th", "th", "th"}
switch x % 100 {
case 11, 12, 13:
return fmt.Sprintf("%d%s", x, suffixes[0])
}
return fmt.Sprintf("%d%s", x, suffixes[x%10])
},
"first": func(value interface{}) interface{} {
defer recovery()
v := reflect.ValueOf(value)
switch v.Kind() {
case reflect.String:
return string([]rune(v.String())[0])
case reflect.Slice, reflect.Array:
return v.Index(0).Interface()
}
return ""
},
"last": func(value interface{}) interface{} {
defer recovery()
v := reflect.ValueOf(value)
switch v.Kind() {
case reflect.String:
str := []rune(v.String())
return string(str[len(str)-1])
case reflect.Slice, reflect.Array:
return v.Index(v.Len() - 1).Interface()
}
return ""
},
"join": func(arg string, value []string) string {
defer recovery()
return strings.Join(value, arg)
},
"slice": func(start int, end int, value interface{}) interface{} {
defer recovery()
v := reflect.ValueOf(value)
if start < 0 {
start = 0
}
switch v.Kind() {
case reflect.String:
str := []rune(v.String())
if end > len(str) {
end = len(str)
}
return string(str[start:end])
case reflect.Slice:
return v.Slice(start, end).Interface()
}
return ""
},
"random": func(value interface{}) interface{} {
defer recovery()
rand.Seed(time.Now().UTC().UnixNano())
v := reflect.ValueOf(value)
switch v.Kind() {
case reflect.String:
str := []rune(v.String())
return string(str[rand.Intn(len(str))])
case reflect.Slice, reflect.Array:
return v.Index(rand.Intn(v.Len())).Interface()
}
return ""
},
"randomintrange": func(min, max int, value interface{}) int {
defer recovery()
rand.Seed(time.Now().UTC().UnixNano())
return rand.Intn(max-min) + min
},
"striptags": func(s string) string {
return strings.TrimSpace(striptagsRegexp.ReplaceAllString(s, ""))
},
}
var GtfFuncMap = htmlTemplate.FuncMap(GtfTextFuncMap)
// gtf.New is a wrapper function of template.New(https://golang.org/pkg/html/template/#New).
// It automatically adds the gtf functions to the template's function map
// and returns template.Template(http://golang.org/pkg/html/template/#Template).
func New(name string) *htmlTemplate.Template {
return htmlTemplate.New(name).Funcs(GtfFuncMap)
}
// gtf.Inject injects gtf functions into the passed FuncMap.
// It does not overwrite the original function which have same name as a gtf function.
func Inject(funcs map[string]interface{}) {
for k, v := range GtfFuncMap {
if _, ok := funcs[k]; !ok {
funcs[k] = v
}
}
}
// gtf.ForceInject injects gtf functions into the passed FuncMap.
// It overwrites the original function which have same name as a gtf function.
func ForceInject(funcs map[string]interface{}) {
for k, v := range GtfFuncMap {
funcs[k] = v
}
}
// gtf.Inject injects gtf functions into the passed FuncMap.
// It prefixes the gtf functions with the specified prefix.
// If there are many function which have same names as the gtf functions,
// you can use this function to prefix the gtf functions.
func InjectWithPrefix(funcs map[string]interface{}, prefix string) {
for k, v := range GtfFuncMap {
funcs[prefix+k] = v
}
}
================================================
FILE: gtf_test.go
================================================
package gtf
import (
"bytes"
"html/template"
"strconv"
"testing"
)
func AssertEqual(t *testing.T, buffer *bytes.Buffer, testString string) {
if buffer.String() != testString {
t.Errorf("Expected %s, got %s", testString, buffer.String())
}
buffer.Reset()
}
func AssertIntInRange(t *testing.T, buffer *bytes.Buffer, min, max int) {
parsedInt, err := strconv.Atoi(buffer.String())
if err != nil {
t.Error(err)
}
if parsedInt < min || parsedInt > max {
t.Errorf("Expected to be within the range of %d and %d, got %d", min, max, parsedInt)
}
buffer.Reset()
}
func ParseTest(buffer *bytes.Buffer, body string, data interface{}) {
tpl := New("test")
tpl.Parse(body)
tpl.Execute(buffer, data)
}
func CustomParseTest(funcMap map[string]interface{}, buffer *bytes.Buffer, body string, data interface{}) {
tpl := template.New("test").Funcs(funcMap)
tpl.Parse(body)
tpl.Execute(buffer, data)
}
func TestGtfFuncMap(t *testing.T) {
var buffer bytes.Buffer
ParseTest(&buffer, "{{ \"The Go Programming Language\" | replace \" \" }}", "")
AssertEqual(t, &buffer, "TheGoProgrammingLanguage")
ParseTest(&buffer, "{{ \"The Go Programming Language\" | findreplace \" \" \"X\" }}", "")
AssertEqual(t, &buffer, "TheXGoXProgrammingXLanguage")
ParseTest(&buffer, "{{ \"the go programming language\" | title }}", "")
AssertEqual(t, &buffer, "The Go Programming Language")
ParseTest(&buffer, "{{ \"The Go Programming Language\" | default \"default value\" }}", "")
AssertEqual(t, &buffer, "The Go Programming Language")
ParseTest(&buffer, "{{ \"\" | default \"default value\" }}", "")
AssertEqual(t, &buffer, "default value")
ParseTest(&buffer, "{{ . | default \"default value\" }}", []string{"go", "python", "ruby"})
AssertEqual(t, &buffer, "[go python ruby]")
ParseTest(&buffer, "{{ . | default 10 }}", []int{})
AssertEqual(t, &buffer, "10")
ParseTest(&buffer, "{{ . | default \"empty\" }}", false)
AssertEqual(t, &buffer, "empty")
ParseTest(&buffer, "{{ . | default \"empty\" }}", 1)
AssertEqual(t, &buffer, "1")
ParseTest(&buffer, "{{ \"The Go Programming Language\" | length }}", "")
AssertEqual(t, &buffer, "27")
ParseTest(&buffer, "{{ \"안녕하세요\" | length }}", "")
AssertEqual(t, &buffer, "5")
ParseTest(&buffer, "{{ . | length }}", []string{"go", "python", "ruby"})
AssertEqual(t, &buffer, "3")
ParseTest(&buffer, "{{ . | length }}", false)
AssertEqual(t, &buffer, "0")
ParseTest(&buffer, "{{ \"The Go Programming Language\" | lower }}", "")
AssertEqual(t, &buffer, "the go programming language")
ParseTest(&buffer, "{{ \"The Go Programming Language\" | upper }}", "")
AssertEqual(t, &buffer, "THE GO PROGRAMMING LANGUAGE")
ParseTest(&buffer, "{{ \"안녕하세요. 반갑습니다.\" | truncatechars 12 }}", "")
AssertEqual(t, &buffer, "안녕하세요. 반갑...")
ParseTest(&buffer, "{{ \"The Go Programming Language\" | truncatechars 12 }}", "")
AssertEqual(t, &buffer, "The Go Pr...")
ParseTest(&buffer, "{{ \"안녕하세요. The Go Programming Language\" | truncatechars 30 }}", "")
AssertEqual(t, &buffer, "안녕하세요. The Go Programming L...")
ParseTest(&buffer, "{{ \"The\" | truncatechars 30 }}", "")
AssertEqual(t, &buffer, "The")
ParseTest(&buffer, "{{ \"The Go Programming Language\" | truncatechars 3 }}", "")
AssertEqual(t, &buffer, "The")
ParseTest(&buffer, "{{ \"The Go\" | truncatechars 6 }}", "")
AssertEqual(t, &buffer, "The Go")
ParseTest(&buffer, "{{ \"The Go\" | truncatechars 30 }}", "")
AssertEqual(t, &buffer, "The Go")
ParseTest(&buffer, "{{ \"The Go\" | truncatechars 0 }}", "")
AssertEqual(t, &buffer, "")
ParseTest(&buffer, "{{ \"The Go\" | truncatechars -1 }}", "")
AssertEqual(t, &buffer, "The Go")
ParseTest(&buffer, "{{ \"http://www.example.org/foo?a=b&c=d\" | urlencode }}", "")
AssertEqual(t, &buffer, "http%3A%2F%2Fwww.example.org%2Ffoo%3Fa%3Db%26c%3Dd")
ParseTest(&buffer, "{{ \"The Go Programming Language\" | wordcount }}", "")
AssertEqual(t, &buffer, "4")
ParseTest(&buffer, "{{ \" The Go Programming Language \" | wordcount }}", "")
AssertEqual(t, &buffer, "4")
ParseTest(&buffer, "{{ 21 | divisibleby 3 }}", "")
AssertEqual(t, &buffer, "true")
ParseTest(&buffer, "{{ 21 | divisibleby 4 }}", "")
AssertEqual(t, &buffer, "false")
ParseTest(&buffer, "{{ 3.0 | divisibleby 3 }}", "")
AssertEqual(t, &buffer, "true")
ParseTest(&buffer, "{{ 3.0 | divisibleby 1.5 }}", "")
AssertEqual(t, &buffer, "true")
ParseTest(&buffer, "{{ . | divisibleby 1.5 }}", uint(300))
AssertEqual(t, &buffer, "true")
ParseTest(&buffer, "{{ 12 | divisibleby . }}", uint(3))
AssertEqual(t, &buffer, "true")
ParseTest(&buffer, "{{ 21 | divisibleby 4 }}", "")
AssertEqual(t, &buffer, "false")
ParseTest(&buffer, "{{ false | divisibleby 3 }}", "")
AssertEqual(t, &buffer, "false")
ParseTest(&buffer, "{{ 3 | divisibleby false }}", "")
AssertEqual(t, &buffer, "false")
ParseTest(&buffer, "{{ \"Go\" | lengthis 2 }}", "")
AssertEqual(t, &buffer, "true")
ParseTest(&buffer, "{{ \"안녕하세요.\" | lengthis 6 }}", "")
AssertEqual(t, &buffer, "true")
ParseTest(&buffer, "{{ \"안녕하세요. Go!\" | lengthis 10 }}", "")
AssertEqual(t, &buffer, "true")
ParseTest(&buffer, "{{ . | lengthis 3 }}", []string{"go", "python", "ruby"})
AssertEqual(t, &buffer, "true")
ParseTest(&buffer, "{{ . | lengthis 3 }}", false)
AssertEqual(t, &buffer, "false")
ParseTest(&buffer, "{{ \" The Go Programming Language \" | trim }}", "")
AssertEqual(t, &buffer, "The Go Programming Language")
ParseTest(&buffer, "{{ \"the go programming language\" | capfirst }}", "")
AssertEqual(t, &buffer, "The go programming language")
ParseTest(&buffer, "You have 0 message{{ 0 | pluralize \"s\" }}", "")
AssertEqual(t, &buffer, "You have 0 messages")
ParseTest(&buffer, "You have 1 message{{ 1 | pluralize \"s\" }}", "")
AssertEqual(t, &buffer, "You have 1 message")
ParseTest(&buffer, "0 cand{{ 0 | pluralize \"y,ies\" }}", "")
AssertEqual(t, &buffer, "0 candies")
ParseTest(&buffer, "1 cand{{ 1 | pluralize \"y,ies\" }}", "")
AssertEqual(t, &buffer, "1 candy")
ParseTest(&buffer, "2 cand{{ 2 | pluralize \"y,ies\" }}", "")
AssertEqual(t, &buffer, "2 candies")
ParseTest(&buffer, "{{ 2 | pluralize \"y,ies,s\" }}", "")
AssertEqual(t, &buffer, "")
ParseTest(&buffer, "2 cand{{ . | pluralize \"y,ies\" }}", uint(2))
AssertEqual(t, &buffer, "2 candies")
ParseTest(&buffer, "1 cand{{ . | pluralize \"y,ies\" }}", uint(1))
AssertEqual(t, &buffer, "1 candy")
ParseTest(&buffer, "{{ . | pluralize \"y,ies\" }}", "test")
AssertEqual(t, &buffer, "")
ParseTest(&buffer, "{{ true | yesno \"yes~\" \"no~\" }}", "")
AssertEqual(t, &buffer, "yes~")
ParseTest(&buffer, "{{ false | yesno \"yes~\" \"no~\" }}", "")
AssertEqual(t, &buffer, "no~")
ParseTest(&buffer, "{{ \"Go\" | rjust 10 }}", "")
AssertEqual(t, &buffer, " Go")
ParseTest(&buffer, "{{ \"안녕하세요\" | rjust 10 }}", "")
AssertEqual(t, &buffer, " 안녕하세요")
ParseTest(&buffer, "{{ \"Go\" | ljust 10 }}", "")
AssertEqual(t, &buffer, "Go ")
ParseTest(&buffer, "{{ \"안녕하세요\" | ljust 10 }}", "")
AssertEqual(t, &buffer, "안녕하세요 ")
ParseTest(&buffer, "{{ \"Go\" | center 10 }}", "")
AssertEqual(t, &buffer, " Go ")
ParseTest(&buffer, "{{ \"안녕하세요\" | center 10 }}", "")
AssertEqual(t, &buffer, " 안녕하세요 ")
ParseTest(&buffer, "{{ 123456789 | filesizeformat }}", "")
AssertEqual(t, &buffer, "117.7 MB")
ParseTest(&buffer, "{{ 234 | filesizeformat }}", "")
AssertEqual(t, &buffer, "234 bytes")
ParseTest(&buffer, "{{ 12345 | filesizeformat }}", "")
AssertEqual(t, &buffer, "12.1 KB")
ParseTest(&buffer, "{{ 554832114 | filesizeformat }}", "")
AssertEqual(t, &buffer, "529.1 MB")
ParseTest(&buffer, "{{ 1048576 | filesizeformat }}", "")
AssertEqual(t, &buffer, "1 MB")
ParseTest(&buffer, "{{ 14868735121 | filesizeformat }}", "")
AssertEqual(t, &buffer, "13.8 GB")
ParseTest(&buffer, "{{ 14868735121365 | filesizeformat }}", "")
AssertEqual(t, &buffer, "13.5 TB")
ParseTest(&buffer, "{{ 1486873512136523 | filesizeformat }}", "")
AssertEqual(t, &buffer, "1.3 PB")
ParseTest(&buffer, "{{ 12345.35335 | filesizeformat }}", "")
AssertEqual(t, &buffer, "12.1 KB")
ParseTest(&buffer, "{{ 4294967293 | filesizeformat }}", "")
AssertEqual(t, &buffer, "4 GB")
ParseTest(&buffer, "{{ \"Go\" | filesizeformat }}", "")
AssertEqual(t, &buffer, "")
ParseTest(&buffer, "{{ . | filesizeformat }}", uint(500))
AssertEqual(t, &buffer, "500 bytes")
ParseTest(&buffer, "{{ . | apnumber }}", uint(500))
AssertEqual(t, &buffer, "500")
ParseTest(&buffer, "{{ . | apnumber }}", uint(7))
AssertEqual(t, &buffer, "seven")
ParseTest(&buffer, "{{ . | apnumber }}", int8(3))
AssertEqual(t, &buffer, "three")
ParseTest(&buffer, "{{ 2 | apnumber }}", "")
AssertEqual(t, &buffer, "two")
ParseTest(&buffer, "{{ 1000 | apnumber }}", "")
AssertEqual(t, &buffer, "1000")
ParseTest(&buffer, "{{ 1000 | intcomma }}", "")
AssertEqual(t, &buffer, "1,000")
ParseTest(&buffer, "{{ -1000 | intcomma }}", "")
AssertEqual(t, &buffer, "-1,000")
ParseTest(&buffer, "{{ 1578652313 | intcomma }}", "")
AssertEqual(t, &buffer, "1,578,652,313")
ParseTest(&buffer, "{{ . | intcomma }}", uint64(12315358198))
AssertEqual(t, &buffer, "12,315,358,198")
ParseTest(&buffer, "{{ . | intcomma }}", 25.352)
AssertEqual(t, &buffer, "")
ParseTest(&buffer, "{{ 1 | ordinal }}", "")
AssertEqual(t, &buffer, "1st")
ParseTest(&buffer, "{{ 2 | ordinal }}", "")
AssertEqual(t, &buffer, "2nd")
ParseTest(&buffer, "{{ 3 | ordinal }}", "")
AssertEqual(t, &buffer, "3rd")
ParseTest(&buffer, "{{ 11 | ordinal }}", "")
AssertEqual(t, &buffer, "11th")
ParseTest(&buffer, "{{ 12 | ordinal }}", "")
AssertEqual(t, &buffer, "12th")
ParseTest(&buffer, "{{ 13 | ordinal }}", "")
AssertEqual(t, &buffer, "13th")
ParseTest(&buffer, "{{ 14 | ordinal }}", "")
AssertEqual(t, &buffer, "14th")
ParseTest(&buffer, "{{ -1 | ordinal }}", "")
AssertEqual(t, &buffer, "")
ParseTest(&buffer, "{{ . | ordinal }}", uint(14))
AssertEqual(t, &buffer, "14th")
ParseTest(&buffer, "{{ . | ordinal }}", false)
AssertEqual(t, &buffer, "")
ParseTest(&buffer, "{{ . | first }}", "The go programming language")
AssertEqual(t, &buffer, "T")
ParseTest(&buffer, "{{ . | first }}", "안녕하세요")
AssertEqual(t, &buffer, "안")
ParseTest(&buffer, "{{ . | first }}", []string{"go", "python", "ruby"})
AssertEqual(t, &buffer, "go")
ParseTest(&buffer, "{{ . | first }}", [3]string{"go", "python", "ruby"})
AssertEqual(t, &buffer, "go")
ParseTest(&buffer, "{{ . | first }}", false)
AssertEqual(t, &buffer, "")
ParseTest(&buffer, "{{ . | last }}", "The go programming language")
AssertEqual(t, &buffer, "e")
ParseTest(&buffer, "{{ . | last }}", "안녕하세요")
AssertEqual(t, &buffer, "요")
ParseTest(&buffer, "{{ . | last }}", []string{"go", "python", "ruby"})
AssertEqual(t, &buffer, "ruby")
ParseTest(&buffer, "{{ . | last }}", false)
AssertEqual(t, &buffer, "")
ParseTest(&buffer, "{{ . | join \" \" }}", []string{"go", "python", "ruby"})
AssertEqual(t, &buffer, "go python ruby")
ParseTest(&buffer, "{{ . | slice 0 2 }}", []string{"go", "python", "ruby"})
AssertEqual(t, &buffer, "[go python]")
ParseTest(&buffer, "{{ . | slice 0 6 }}", "The go programming language")
AssertEqual(t, &buffer, "The go")
ParseTest(&buffer, "{{ . | slice 0 2 }}", "안녕하세요")
AssertEqual(t, &buffer, "안녕")
ParseTest(&buffer, "{{ . | slice -10 10 }}", "안녕하세요")
AssertEqual(t, &buffer, "안녕하세요")
ParseTest(&buffer, "{{ . | slice 0 2 }}", false)
AssertEqual(t, &buffer, "")
ParseTest(&buffer, "{{ . | random }}", "T")
AssertEqual(t, &buffer, "T")
ParseTest(&buffer, "{{ . | random }}", "안")
AssertEqual(t, &buffer, "안")
ParseTest(&buffer, "{{ . | random }}", []string{"go"})
AssertEqual(t, &buffer, "go")
ParseTest(&buffer, "{{ . | random }}", [1]string{"go"})
AssertEqual(t, &buffer, "go")
ParseTest(&buffer, "{{ . | randomintrange 1 5 }}", false)
AssertIntInRange(t, &buffer, 1, 5)
ParseTest(&buffer, "{{ . | random }}", false)
AssertEqual(t, &buffer, "")
ParseTest(&buffer, "{{ . | striptags }}", "<strong>text</strong>")
AssertEqual(t, &buffer, "text")
ParseTest(&buffer, "{{ . | striptags }}", "<strong><em>안녕하세요</em></strong>")
AssertEqual(t, &buffer, "안녕하세요")
ParseTest(&buffer, "{{ . | striptags }}", "<a href=\"http://example.com/\">text <strong>안녕하세요</strong></a>")
AssertEqual(t, &buffer, "text 안녕하세요")
}
func TestInject(t *testing.T) {
var buffer bytes.Buffer
var originalFuncMap = template.FuncMap{
// originalFuncMap is made for test purpose.
// It tests that Inject function does not overwrite the original functions
// which have same names.
"length": func(value interface{}) int {
return -1
},
"lower": func(s string) string {
return "foo"
},
}
CustomParseTest(originalFuncMap, &buffer, "{{ \"The Go Programming Language\" | length }}", "")
AssertEqual(t, &buffer, "-1")
CustomParseTest(originalFuncMap, &buffer, "{{ \"The Go Programming Language\" | lower }}", "")
AssertEqual(t, &buffer, "foo")
Inject(originalFuncMap) // Inject!
// Check if Inject function does not overwrite the original functions which have same names.
CustomParseTest(originalFuncMap, &buffer, "{{ \"The Go Programming Language\" | length }}", "")
AssertEqual(t, &buffer, "-1")
CustomParseTest(originalFuncMap, &buffer, "{{ \"The Go Programming Language\" | lower }}", "")
AssertEqual(t, &buffer, "foo")
// Now, I can use gtf functions because they are injected into originalFuncMap.
CustomParseTest(originalFuncMap, &buffer, "{{ . | first }}", []string{"go", "python", "ruby"})
AssertEqual(t, &buffer, "go")
CustomParseTest(originalFuncMap, &buffer, "{{ . | slice 0 6 }}", "The go programming language")
AssertEqual(t, &buffer, "The go")
CustomParseTest(originalFuncMap, &buffer, "{{ 13 | ordinal }}", "")
AssertEqual(t, &buffer, "13th")
}
func TestForceInject(t *testing.T) {
var buffer bytes.Buffer
var originalFuncMap = template.FuncMap{
"length": func(value interface{}) int {
return -1
},
"lower": func(s string) string {
return "foo"
},
}
CustomParseTest(originalFuncMap, &buffer, "{{ \"The Go Programming Language\" | length }}", "")
AssertEqual(t, &buffer, "-1")
CustomParseTest(originalFuncMap, &buffer, "{{ \"The Go Programming Language\" | lower }}", "")
AssertEqual(t, &buffer, "foo")
ForceInject(originalFuncMap) // ForceInject!
// Check if ForceInject function overwrites the original functions which have same names.
CustomParseTest(originalFuncMap, &buffer, "{{ \"The Go Programming Language\" | length }}", "")
AssertEqual(t, &buffer, "27")
CustomParseTest(originalFuncMap, &buffer, "{{ \"The Go Programming Language\" | lower }}", "")
AssertEqual(t, &buffer, "the go programming language")
// Now, I can use gtf functions because they are injected into originalFuncMap.
CustomParseTest(originalFuncMap, &buffer, "{{ . | first }}", []string{"go", "python", "ruby"})
AssertEqual(t, &buffer, "go")
CustomParseTest(originalFuncMap, &buffer, "{{ . | slice 0 6 }}", "The go programming language")
AssertEqual(t, &buffer, "The go")
CustomParseTest(originalFuncMap, &buffer, "{{ 13 | ordinal }}", "")
AssertEqual(t, &buffer, "13th")
}
func TestInjectWithPrefix(t *testing.T) {
var buffer bytes.Buffer
var originalFuncMap = template.FuncMap{
"length": func(value interface{}) int {
return -1
},
"lower": func(s string) string {
return "foo"
},
}
CustomParseTest(originalFuncMap, &buffer, "{{ \"The Go Programming Language\" | length }}", "")
AssertEqual(t, &buffer, "-1")
CustomParseTest(originalFuncMap, &buffer, "{{ \"The Go Programming Language\" | lower }}", "")
AssertEqual(t, &buffer, "foo")
InjectWithPrefix(originalFuncMap, "gtf_") // InjectWithPrefix! (prefix : gtf_)
// Check if Inject function does not overwrite the original functions which have same names.
CustomParseTest(originalFuncMap, &buffer, "{{ \"The Go Programming Language\" | length }}", "")
AssertEqual(t, &buffer, "-1")
CustomParseTest(originalFuncMap, &buffer, "{{ \"The Go Programming Language\" | lower }}", "")
AssertEqual(t, &buffer, "foo")
// Now, I can use gtf functions because they are injected into originalFuncMap.
CustomParseTest(originalFuncMap, &buffer, "{{ . | gtf_first }}", []string{"go", "python", "ruby"})
AssertEqual(t, &buffer, "go")
CustomParseTest(originalFuncMap, &buffer, "{{ . | gtf_slice 0 6 }}", "The go programming language")
AssertEqual(t, &buffer, "The go")
CustomParseTest(originalFuncMap, &buffer, "{{ 13 | gtf_ordinal }}", "")
AssertEqual(t, &buffer, "13th")
}
================================================
FILE: gtf_text_test.go
================================================
package gtf
import (
"bytes"
"testing"
"text/template"
)
func TextTemplateParseTest(buffer *bytes.Buffer, body string, data interface{}) {
tpl := template.New("test").Funcs(GtfTextFuncMap)
tpl.Parse(body)
tpl.Execute(buffer, data)
}
func TestTextTemplateGtfFuncMap(t *testing.T) {
var buffer bytes.Buffer
TextTemplateParseTest(&buffer, "{{ \"The Go Programming Language\" | replace \" \" }}", "")
AssertEqual(t, &buffer, "TheGoProgrammingLanguage")
TextTemplateParseTest(&buffer, "{{ \"The Go Programming Language\" | findreplace \" \" \"X\" }}", "")
AssertEqual(t, &buffer, "TheXGoXProgrammingXLanguage")
TextTemplateParseTest(&buffer, "{{ \"The Go Programming Language\" | length }}", "")
AssertEqual(t, &buffer, "27")
TextTemplateParseTest(&buffer, "{{ 21 | divisibleby 3 }}", "")
AssertEqual(t, &buffer, "true")
}
gitextract_790f95lo/ ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── gtf.go ├── gtf_test.go └── gtf_text_test.go
SYMBOL INDEX (15 symbols across 3 files)
FILE: gtf.go
function recovery (line 19) | func recovery() {
function New (line 451) | func New(name string) *htmlTemplate.Template {
function Inject (line 457) | func Inject(funcs map[string]interface{}) {
function ForceInject (line 467) | func ForceInject(funcs map[string]interface{}) {
function InjectWithPrefix (line 477) | func InjectWithPrefix(funcs map[string]interface{}, prefix string) {
FILE: gtf_test.go
function AssertEqual (line 10) | func AssertEqual(t *testing.T, buffer *bytes.Buffer, testString string) {
function AssertIntInRange (line 17) | func AssertIntInRange(t *testing.T, buffer *bytes.Buffer, min, max int) {
function ParseTest (line 28) | func ParseTest(buffer *bytes.Buffer, body string, data interface{}) {
function CustomParseTest (line 34) | func CustomParseTest(funcMap map[string]interface{}, buffer *bytes.Buffe...
function TestGtfFuncMap (line 40) | func TestGtfFuncMap(t *testing.T) {
function TestInject (line 392) | func TestInject(t *testing.T) {
function TestForceInject (line 429) | func TestForceInject(t *testing.T) {
function TestInjectWithPrefix (line 463) | func TestInjectWithPrefix(t *testing.T) {
FILE: gtf_text_test.go
function TextTemplateParseTest (line 9) | func TextTemplateParseTest(buffer *bytes.Buffer, body string, data inter...
function TestTextTemplateGtfFuncMap (line 15) | func TestTextTemplateGtfFuncMap(t *testing.T) {
Condensed preview — 7 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (54K chars).
[
{
"path": ".gitignore",
"chars": 266,
"preview": "# Compiled Object files, Static and Dynamic libs (Shared Objects)\n*.o\n*.a\n*.so\n\n# Folders\n_obj\n_test\n\n# Architecture spe"
},
{
"path": ".travis.yml",
"chars": 438,
"preview": "language: go\ngo:\n - 1.4\n - 1.5\n - 1.6\n - 1.7\n - 1.8\n - tip\nenv:\n global:\n - BUILD_GOARCH=amd64\n matrix:\n -"
},
{
"path": "LICENSE",
"chars": 1104,
"preview": "The MIT License (MIT)\n\nCopyright (c) 2015 Kyoung-chan Lee (leekchan@gmail.com)\n\nPermission is hereby granted, free of ch"
},
{
"path": "README.md",
"chars": 18863,
"preview": "# gtf - a useful set of Golang Template Functions\n["
},
{
"path": "gtf.go",
"chars": 10426,
"preview": "package gtf\n\nimport (\n\t\"fmt\"\n\thtmlTemplate \"html/template\"\n\t\"math\"\n\t\"math/rand\"\n\t\"net/url\"\n\t\"reflect\"\n\t\"regexp\"\n\t\"string"
},
{
"path": "gtf_test.go",
"chars": 16614,
"preview": "package gtf\n\nimport (\n\t\"bytes\"\n\t\"html/template\"\n\t\"strconv\"\n\t\"testing\"\n)\n\nfunc AssertEqual(t *testing.T, buffer *bytes.Bu"
},
{
"path": "gtf_text_test.go",
"chars": 840,
"preview": "package gtf\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n\t\"text/template\"\n)\n\nfunc TextTemplateParseTest(buffer *bytes.Buffer, body stri"
}
]
About this extraction
This page contains the full source code of the leekchan/gtf GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 7 files (47.4 KB), approximately 14.0k tokens, and a symbol index with 15 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.