mirror of
https://github.com/Luzifer/clean-github-branches.git
synced 2024-12-22 18:31:18 +00:00
Log branch age
Signed-off-by: Knut Ahlers <knut@ahlers.me>
This commit is contained in:
parent
2b085e2e87
commit
dd8da5cac4
7 changed files with 583 additions and 2 deletions
17
Gopkg.lock
generated
17
Gopkg.lock
generated
|
@ -1,6 +1,14 @@
|
||||||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||||
|
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
digest = "1:52b6c8beb19a31fe7b40b23c9b694caf4213cfa3d6cce711cd0636db68ce67aa"
|
||||||
|
name = "github.com/Luzifer/go_helpers"
|
||||||
|
packages = ["duration"]
|
||||||
|
pruneopts = "NUT"
|
||||||
|
revision = "4e368ddb27c0a08a0c84e5ceac52d57b464bffce"
|
||||||
|
version = "v2.7.0"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
digest = "1:3c2b6707179ff866ca06c5eecd544c9079a6ec35d735031a1bf61abfff336186"
|
digest = "1:3c2b6707179ff866ca06c5eecd544c9079a6ec35d735031a1bf61abfff336186"
|
||||||
name = "github.com/Luzifer/rconfig"
|
name = "github.com/Luzifer/rconfig"
|
||||||
|
@ -33,6 +41,14 @@
|
||||||
pruneopts = "NUT"
|
pruneopts = "NUT"
|
||||||
revision = "53e6ce116135b80d037921a7fdd5138cf32d7a8a"
|
revision = "53e6ce116135b80d037921a7fdd5138cf32d7a8a"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
digest = "1:071a57a67bde66b0e6438ffee4599ef99532a0e29de3ea12926b5438608f90a6"
|
||||||
|
name = "github.com/leekchan/gtf"
|
||||||
|
packages = ["."]
|
||||||
|
pruneopts = "NUT"
|
||||||
|
revision = "79e3a68ab435edb72c6490289a33ef8bedcee15a"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
digest = "1:5cf3f025cbee5951a4ee961de067c8a89fc95a5adabead774f82822efabab121"
|
digest = "1:5cf3f025cbee5951a4ee961de067c8a89fc95a5adabead774f82822efabab121"
|
||||||
name = "github.com/pkg/errors"
|
name = "github.com/pkg/errors"
|
||||||
|
@ -134,6 +150,7 @@
|
||||||
analyzer-name = "dep"
|
analyzer-name = "dep"
|
||||||
analyzer-version = 1
|
analyzer-version = 1
|
||||||
input-imports = [
|
input-imports = [
|
||||||
|
"github.com/Luzifer/go_helpers/duration",
|
||||||
"github.com/Luzifer/rconfig",
|
"github.com/Luzifer/rconfig",
|
||||||
"github.com/google/go-github/github",
|
"github.com/google/go-github/github",
|
||||||
"github.com/pkg/errors",
|
"github.com/pkg/errors",
|
||||||
|
|
|
@ -25,6 +25,10 @@
|
||||||
# unused-packages = true
|
# unused-packages = true
|
||||||
|
|
||||||
|
|
||||||
|
[[constraint]]
|
||||||
|
name = "github.com/Luzifer/go_helpers"
|
||||||
|
version = "2.7.0"
|
||||||
|
|
||||||
[[constraint]]
|
[[constraint]]
|
||||||
name = "github.com/Luzifer/rconfig"
|
name = "github.com/Luzifer/rconfig"
|
||||||
version = "2.1.0"
|
version = "2.1.0"
|
||||||
|
|
|
@ -28,8 +28,8 @@ Usage of clean-github-branches:
|
||||||
--version Prints current version and exits
|
--version Prints current version and exits
|
||||||
|
|
||||||
$ clean-github-branches -r '^[lL]uzifer(-docker|-ansible|)/'
|
$ clean-github-branches -r '^[lL]uzifer(-docker|-ansible|)/'
|
||||||
WARN[0013] Stale branch found ahead=1 behind=1 branch=develop dry-run=true repo=luzifer-docker/etherpad-lite
|
WARN[0012] Stale branch found age=2y174d23h ahead=1 behind=1 branch=develop dry-run=true repo=luzifer-docker/etherpad-lite
|
||||||
INFO[0013] Done.
|
INFO[0012] Done.
|
||||||
```
|
```
|
||||||
|
|
||||||
All parameters causing destructive actions are set to sane defaults: By default a `dry-run` is done which prevents any deletion. Also `delete-stale` is disabled as it might cause data loss as the branch is not merged and all commits in it will be lost.
|
All parameters causing destructive actions are set to sane defaults: By default a `dry-run` is done which prevents any deletion. Also `delete-stale` is disabled as it might cause data loss as the branch is not merged and all commits in it will be lost.
|
||||||
|
|
7
main.go
7
main.go
|
@ -10,6 +10,7 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/Luzifer/go_helpers/duration"
|
||||||
"github.com/Luzifer/rconfig"
|
"github.com/Luzifer/rconfig"
|
||||||
|
|
||||||
"github.com/google/go-github/github"
|
"github.com/google/go-github/github"
|
||||||
|
@ -18,6 +19,10 @@ import (
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const humanizeTemplate = `{{if gt .Years 0}}{{.Years}}y{{end}}` +
|
||||||
|
`{{if gt .Days 0}}{{.Days}}d{{end}}` +
|
||||||
|
`{{if gt .Hours 0}}{{.Hours}}h{{end}}`
|
||||||
|
|
||||||
var (
|
var (
|
||||||
cfg = struct {
|
cfg = struct {
|
||||||
BranchStaleness time.Duration `flag:"branch-staleness" default:"2160h" description:"When to see a branch as stale (default 90d)"`
|
BranchStaleness time.Duration `flag:"branch-staleness" default:"2160h" description:"When to see a branch as stale (default 90d)"`
|
||||||
|
@ -277,6 +282,8 @@ func processRepo(ctx context.Context, logger *log.Entry, client *github.Client,
|
||||||
float64(time.Since(b.GetCommit().GetCommit().GetAuthor().GetDate())),
|
float64(time.Since(b.GetCommit().GetCommit().GetAuthor().GetDate())),
|
||||||
float64(time.Since(b.GetCommit().GetCommit().GetCommitter().GetDate())),
|
float64(time.Since(b.GetCommit().GetCommit().GetCommitter().GetDate())),
|
||||||
))
|
))
|
||||||
|
d, _ := duration.CustomHumanizeDuration(branchLastModified, humanizeTemplate)
|
||||||
|
branchLogger = branchLogger.WithField("age", d)
|
||||||
|
|
||||||
// Check all PRs whether they match the branch (head) and are merged
|
// Check all PRs whether they match the branch (head) and are merged
|
||||||
hasValidMerge, hasOpenPR := analysePullRequests(branchLogger, prs, b)
|
hasValidMerge, hasOpenPR := analysePullRequests(branchLogger, prs, b)
|
||||||
|
|
61
vendor/github.com/Luzifer/go_helpers/duration/time.go
generated
vendored
Normal file
61
vendor/github.com/Luzifer/go_helpers/duration/time.go
generated
vendored
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
package duration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"math"
|
||||||
|
"strings"
|
||||||
|
"text/template"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/leekchan/gtf"
|
||||||
|
)
|
||||||
|
|
||||||
|
const defaultDurationFormat = `{{if gt .Years 0}}{{.Years}} year{{.Years|pluralize "s"}}, {{end}}` +
|
||||||
|
`{{if gt .Days 0}}{{.Days}} day{{.Days|pluralize "s"}}, {{end}}` +
|
||||||
|
`{{if gt .Hours 0}}{{.Hours}} hour{{.Hours|pluralize "s"}}, {{end}}` +
|
||||||
|
`{{if gt .Minutes 0}}{{.Minutes}} minute{{.Minutes|pluralize "s"}}, {{end}}` +
|
||||||
|
`{{if gt .Seconds 0}}{{.Seconds}} second{{.Seconds|pluralize "s"}}{{end}}`
|
||||||
|
|
||||||
|
func HumanizeDuration(in time.Duration) string {
|
||||||
|
f, err := CustomHumanizeDuration(in, defaultDurationFormat)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return strings.Trim(f, " ,")
|
||||||
|
}
|
||||||
|
|
||||||
|
func CustomHumanizeDuration(in time.Duration, tpl string) (string, error) {
|
||||||
|
result := struct{ Years, Days, Hours, Minutes, Seconds int64 }{}
|
||||||
|
|
||||||
|
in = time.Duration(math.Abs(float64(in)))
|
||||||
|
|
||||||
|
for in > 0 {
|
||||||
|
switch {
|
||||||
|
case in > 365.25*24*time.Hour:
|
||||||
|
result.Years = int64(in / (365 * 24 * time.Hour))
|
||||||
|
in = in - time.Duration(result.Years)*365*24*time.Hour
|
||||||
|
case in > 24*time.Hour:
|
||||||
|
result.Days = int64(in / (24 * time.Hour))
|
||||||
|
in = in - time.Duration(result.Days)*24*time.Hour
|
||||||
|
case in > time.Hour:
|
||||||
|
result.Hours = int64(in / time.Hour)
|
||||||
|
in = in - time.Duration(result.Hours)*time.Hour
|
||||||
|
case in > time.Minute:
|
||||||
|
result.Minutes = int64(in / time.Minute)
|
||||||
|
in = in - time.Duration(result.Minutes)*time.Minute
|
||||||
|
default:
|
||||||
|
result.Seconds = int64(in / time.Second)
|
||||||
|
in = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpl, err := template.New("timeformat").Funcs(template.FuncMap(gtf.GtfFuncMap)).Parse(tpl)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := bytes.NewBuffer([]byte{})
|
||||||
|
tmpl.Execute(buf, result)
|
||||||
|
|
||||||
|
return buf.String(), nil
|
||||||
|
}
|
22
vendor/github.com/leekchan/gtf/LICENSE
generated
vendored
Normal file
22
vendor/github.com/leekchan/gtf/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
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.
|
||||||
|
|
470
vendor/github.com/leekchan/gtf/gtf.go
generated
vendored
Normal file
470
vendor/github.com/leekchan/gtf/gtf.go
generated
vendored
Normal file
|
@ -0,0 +1,470 @@
|
||||||
|
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)
|
||||||
|
},
|
||||||
|
"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 ""
|
||||||
|
},
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue