From 690b0f8ff5795318dfa3834a5a75d6623e7d934a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Erik=20Pedersen?= Date: Mon, 1 May 2017 18:40:34 +0200 Subject: [PATCH] tpl: Add docshelper for template funcs And fix some other minor related issues. Updates #3418 --- docs/data/docs.json | 1223 +++++++++++++++++++++ tpl/cast/docshelper.go | 41 + tpl/cast/init.go | 28 +- tpl/collections/init.go | 150 ++- tpl/compare/init.go | 60 +- tpl/crypto/init.go | 39 +- tpl/data/init.go | 22 +- tpl/encoding/init.go | 39 +- tpl/fmt/fmt.go | 13 +- tpl/fmt/init.go | 35 +- tpl/images/init.go | 17 +- tpl/inflect/init.go | 43 +- tpl/internal/templatefuncRegistry_test.go | 33 + tpl/internal/templatefuncsRegistry.go | 83 +- tpl/lang/init.go | 18 +- tpl/math/init.go | 64 +- tpl/os/init.go | 34 +- tpl/partials/init.go | 25 +- tpl/safe/init.go | 67 +- tpl/strings/init.go | 152 ++- tpl/time/init.go | 34 +- tpl/tplimpl/template_funcs.go | 10 +- tpl/tplimpl/template_funcs_test.go | 26 +- tpl/transform/init.go | 99 +- tpl/urls/init.go | 56 +- 25 files changed, 2064 insertions(+), 347 deletions(-) create mode 100644 tpl/cast/docshelper.go create mode 100644 tpl/internal/templatefuncRegistry_test.go diff --git a/docs/data/docs.json b/docs/data/docs.json index 12bda6e3c..9aaf52302 100644 --- a/docs/data/docs.json +++ b/docs/data/docs.json @@ -258,5 +258,1228 @@ ] } ] + }, + "tpl": { + "funcs": [ + { + "Name": "cast", + "Funcs": [ + { + "Name": "ToInt", + "Description": "", + "Aliases": [ + "int" + ], + "Examples": [ + [ + "{{ \"1234\" | int | printf \"%T\" }}", + "int" + ] + ] + }, + { + "Name": "ToString", + "Description": "", + "Aliases": [ + "string" + ], + "Examples": [ + [ + "{{ 1234 | string | printf \"%T\" }}", + "string" + ] + ] + } + ] + }, + { + "Name": "compare", + "Funcs": [ + { + "Name": "Default", + "Description": "", + "Aliases": [ + "default" + ], + "Examples": [ + [ + "{{ \"Hugo Rocks!\" | default \"Hugo Rules!\" }}", + "Hugo Rocks!" + ], + [ + "{{ \"\" | default \"Hugo Rules!\" }}", + "Hugo Rules!" + ] + ] + }, + { + "Name": "Eq", + "Description": "", + "Aliases": [ + "eq" + ], + "Examples": [ + [ + "{{ if eq .Section \"blog\" }}current{{ end }}", + "current" + ] + ] + }, + { + "Name": "Ge", + "Description": "", + "Aliases": [ + "ge" + ], + "Examples": [] + }, + { + "Name": "Gt", + "Description": "", + "Aliases": [ + "gt" + ], + "Examples": [] + }, + { + "Name": "Le", + "Description": "", + "Aliases": [ + "le" + ], + "Examples": [] + }, + { + "Name": "Lt", + "Description": "", + "Aliases": [ + "lt" + ], + "Examples": [] + }, + { + "Name": "Ne", + "Description": "", + "Aliases": [ + "ne" + ], + "Examples": [] + } + ] + }, + { + "Name": "collections", + "Funcs": [ + { + "Name": "After", + "Description": "", + "Aliases": [ + "after" + ], + "Examples": [] + }, + { + "Name": "Apply", + "Description": "", + "Aliases": [ + "apply" + ], + "Examples": [] + }, + { + "Name": "Delimit", + "Description": "", + "Aliases": [ + "delimit" + ], + "Examples": [ + [ + "{{ delimit (slice \"A\" \"B\" \"C\") \", \" \" and \" }}", + "A, B and C" + ] + ] + }, + { + "Name": "Dictionary", + "Description": "", + "Aliases": [ + "dict" + ], + "Examples": [] + }, + { + "Name": "EchoParam", + "Description": "", + "Aliases": [ + "echoParam" + ], + "Examples": [ + [ + "{{ echoParam .Params \"langCode\" }}", + "en" + ] + ] + }, + { + "Name": "First", + "Description": "", + "Aliases": [ + "first" + ], + "Examples": [] + }, + { + "Name": "In", + "Description": "", + "Aliases": [ + "in" + ], + "Examples": [ + [ + "{{ if in \"this string contains a substring\" \"substring\" }}Substring found!{{ end }}", + "Substring found!" + ] + ] + }, + { + "Name": "Index", + "Description": "", + "Aliases": [ + "index" + ], + "Examples": [] + }, + { + "Name": "Intersect", + "Description": "", + "Aliases": [ + "intersect" + ], + "Examples": [] + }, + { + "Name": "IsSet", + "Description": "", + "Aliases": [ + "isSet", + "isset" + ], + "Examples": [] + }, + { + "Name": "Last", + "Description": "", + "Aliases": [ + "last" + ], + "Examples": [] + }, + { + "Name": "Querify", + "Description": "", + "Aliases": [ + "querify" + ], + "Examples": [ + [ + "{{ (querify \"foo\" 1 \"bar\" 2 \"baz\" \"with spaces\" \"qux\" \"this\u0026that=those\") | safeHTML }}", + "bar=2\u0026baz=with+spaces\u0026foo=1\u0026qux=this%26that%3Dthose" + ], + [ + "\u003ca href=\"https://www.google.com?{{ (querify \"q\" \"test\" \"page\" 3) | safeURL }}\"\u003eSearch\u003c/a\u003e", + "\u003ca href=\"https://www.google.com?page=3\u0026amp;q=test\"\u003eSearch\u003c/a\u003e" + ] + ] + }, + { + "Name": "Seq", + "Description": "", + "Aliases": [ + "seq" + ], + "Examples": [ + [ + "{{ seq 3 }}", + "[1 2 3]" + ] + ] + }, + { + "Name": "Shuffle", + "Description": "", + "Aliases": [ + "shuffle" + ], + "Examples": [] + }, + { + "Name": "Slice", + "Description": "", + "Aliases": [ + "slice" + ], + "Examples": [ + [ + "{{ slice \"B\" \"C\" \"A\" | sort }}", + "[A B C]" + ] + ] + }, + { + "Name": "Sort", + "Description": "", + "Aliases": [ + "sort" + ], + "Examples": [] + }, + { + "Name": "Union", + "Description": "", + "Aliases": [ + "union" + ], + "Examples": [ + [ + "{{ union (slice 1 2 3) (slice 3 4 5) }}", + "[1 2 3 4 5]" + ] + ] + }, + { + "Name": "Where", + "Description": "", + "Aliases": [ + "where" + ], + "Examples": [] + } + ] + }, + { + "Name": "crypto", + "Funcs": [ + { + "Name": "MD5", + "Description": "", + "Aliases": [ + "md5" + ], + "Examples": [ + [ + "{{ md5 \"Hello world, gophers!\" }}", + "b3029f756f98f79e7f1b7f1d1f0dd53b" + ], + [ + "{{ crypto.MD5 \"Hello world, gophers!\" }}", + "b3029f756f98f79e7f1b7f1d1f0dd53b" + ] + ] + }, + { + "Name": "SHA1", + "Description": "", + "Aliases": [ + "sha1" + ], + "Examples": [ + [ + "{{ sha1 \"Hello world, gophers!\" }}", + "c8b5b0e33d408246e30f53e32b8f7627a7a649d4" + ] + ] + }, + { + "Name": "SHA256", + "Description": "", + "Aliases": [ + "sha256" + ], + "Examples": [ + [ + "{{ sha256 \"Hello world, gophers!\" }}", + "6ec43b78da9669f50e4e422575c54bf87536954ccd58280219c393f2ce352b46" + ] + ] + } + ] + }, + { + "Name": "data", + "Funcs": [ + { + "Name": "GetCSV", + "Description": "", + "Aliases": [ + "getCSV" + ], + "Examples": [] + }, + { + "Name": "GetJSON", + "Description": "", + "Aliases": [ + "getJSON" + ], + "Examples": [] + } + ] + }, + { + "Name": "encoding", + "Funcs": [ + { + "Name": "Base64Decode", + "Description": "", + "Aliases": [ + "base64Decode" + ], + "Examples": [ + [ + "{{ \"SGVsbG8gd29ybGQ=\" | base64Decode }}", + "Hello world" + ], + [ + "{{ 42 | base64Encode | base64Decode }}", + "42" + ] + ] + }, + { + "Name": "Base64Encode", + "Description": "", + "Aliases": [ + "base64Encode" + ], + "Examples": [ + [ + "{{ \"Hello world\" | base64Encode }}", + "SGVsbG8gd29ybGQ=" + ] + ] + }, + { + "Name": "Jsonify", + "Description": "", + "Aliases": [ + "jsonify" + ], + "Examples": [ + [ + "{{ (slice \"A\" \"B\" \"C\") | jsonify }}", + "[\"A\",\"B\",\"C\"]" + ] + ] + } + ] + }, + { + "Name": "fmt", + "Funcs": [ + { + "Name": "Print", + "Description": "", + "Aliases": [ + "print" + ], + "Examples": [ + [ + "{{ print \"works!\" }}", + "works!" + ] + ] + }, + { + "Name": "Printf", + "Description": "", + "Aliases": [ + "printf" + ], + "Examples": [ + [ + "{{ printf \"%s!\" \"works\" }}", + "works!" + ] + ] + }, + { + "Name": "Println", + "Description": "", + "Aliases": [ + "println" + ], + "Examples": [ + [ + "{{ println \"works!\" }}", + "works!\n" + ] + ] + } + ] + }, + { + "Name": "images", + "Funcs": [ + { + "Name": "Config", + "Description": "", + "Aliases": [ + "imageConfig" + ], + "Examples": [] + }, + { + "Name": "Lock", + "Description": "", + "Aliases": null, + "Examples": null + }, + { + "Name": "RLock", + "Description": "", + "Aliases": null, + "Examples": null + }, + { + "Name": "RLocker", + "Description": "", + "Aliases": null, + "Examples": null + }, + { + "Name": "RUnlock", + "Description": "", + "Aliases": null, + "Examples": null + }, + { + "Name": "Unlock", + "Description": "", + "Aliases": null, + "Examples": null + } + ] + }, + { + "Name": "inflect", + "Funcs": [ + { + "Name": "Humanize", + "Description": "", + "Aliases": [ + "humanize" + ], + "Examples": [ + [ + "{{ humanize \"my-first-post\" }}", + "My first post" + ], + [ + "{{ humanize \"myCamelPost\" }}", + "My camel post" + ], + [ + "{{ humanize \"52\" }}", + "52nd" + ], + [ + "{{ humanize 103 }}", + "103rd" + ] + ] + }, + { + "Name": "Pluralize", + "Description": "", + "Aliases": [ + "pluralize" + ], + "Examples": [ + [ + "{{ \"cat\" | pluralize }}", + "cats" + ] + ] + }, + { + "Name": "Singularize", + "Description": "", + "Aliases": [ + "singularize" + ], + "Examples": [ + [ + "{{ \"cats\" | singularize }}", + "cat" + ] + ] + } + ] + }, + { + "Name": "lang", + "Funcs": [ + { + "Name": "Translate", + "Description": "", + "Aliases": [ + "i18n", + "T" + ], + "Examples": [] + } + ] + }, + { + "Name": "math", + "Funcs": [ + { + "Name": "Add", + "Description": "", + "Aliases": [ + "add" + ], + "Examples": [ + [ + "{{add 1 2}}", + "3" + ] + ] + }, + { + "Name": "Div", + "Description": "", + "Aliases": [ + "div" + ], + "Examples": [ + [ + "{{div 6 3}}", + "2" + ] + ] + }, + { + "Name": "Mod", + "Description": "", + "Aliases": [ + "mod" + ], + "Examples": [ + [ + "{{mod 15 3}}", + "0" + ] + ] + }, + { + "Name": "ModBool", + "Description": "", + "Aliases": [ + "modBool" + ], + "Examples": [ + [ + "{{modBool 15 3}}", + "true" + ] + ] + }, + { + "Name": "Mul", + "Description": "", + "Aliases": [ + "mul" + ], + "Examples": [ + [ + "{{mul 2 3}}", + "6" + ] + ] + }, + { + "Name": "Sub", + "Description": "", + "Aliases": [ + "sub" + ], + "Examples": [ + [ + "{{sub 3 2}}", + "1" + ] + ] + } + ] + }, + { + "Name": "os", + "Funcs": [ + { + "Name": "Getenv", + "Description": "", + "Aliases": [ + "getenv" + ], + "Examples": [] + }, + { + "Name": "ReadDir", + "Description": "", + "Aliases": [ + "readDir" + ], + "Examples": [ + [ + "{{ range (readDir \".\") }}{{ .Name }}{{ end }}", + "README.txt" + ] + ] + }, + { + "Name": "ReadFile", + "Description": "", + "Aliases": [ + "readFile" + ], + "Examples": [ + [ + "{{ readFile \"README.txt\" }}", + "Hugo Rocks!" + ] + ] + } + ] + }, + { + "Name": "partials", + "Funcs": [ + { + "Name": "Include", + "Description": "", + "Aliases": [ + "partial" + ], + "Examples": [ + [ + "{{ partial \"header.html\" . }}", + "\u003ctitle\u003eHugo Rocks!\u003c/title\u003e" + ] + ] + } + ] + }, + { + "Name": "safe", + "Funcs": [ + { + "Name": "CSS", + "Description": "", + "Aliases": [ + "safeCSS" + ], + "Examples": [ + [ + "{{ \"Bat\u0026Man\" | safeCSS | safeCSS }}", + "Bat\u0026amp;Man" + ] + ] + }, + { + "Name": "HTML", + "Description": "", + "Aliases": [ + "safeHTML" + ], + "Examples": [ + [ + "{{ \"Bat\u0026Man\" | safeHTML | safeHTML }}", + "Bat\u0026Man" + ], + [ + "{{ \"Bat\u0026Man\" | safeHTML }}", + "Bat\u0026Man" + ] + ] + }, + { + "Name": "HTMLAttr", + "Description": "", + "Aliases": [ + "safeHTMLAttr" + ], + "Examples": [] + }, + { + "Name": "JS", + "Description": "", + "Aliases": [ + "safeJS" + ], + "Examples": [ + [ + "{{ \"(1*2)\" | safeJS | safeJS }}", + "(1*2)" + ] + ] + }, + { + "Name": "JSStr", + "Description": "", + "Aliases": [ + "safeJSStr" + ], + "Examples": [] + }, + { + "Name": "SanitizeURL", + "Description": "", + "Aliases": [ + "sanitizeURL", + "sanitizeurl" + ], + "Examples": [] + }, + { + "Name": "URL", + "Description": "", + "Aliases": [ + "safeURL" + ], + "Examples": [ + [ + "{{ \"http://gohugo.io\" | safeURL | safeURL }}", + "http://gohugo.io" + ] + ] + } + ] + }, + { + "Name": "strings", + "Funcs": [ + { + "Name": "Chomp", + "Description": "", + "Aliases": [ + "chomp" + ], + "Examples": [ + [ + "{{chomp \"\u003cp\u003eBlockhead\u003c/p\u003e\\n\" }}", + "\u003cp\u003eBlockhead\u003c/p\u003e" + ] + ] + }, + { + "Name": "Contains", + "Description": "", + "Aliases": null, + "Examples": null + }, + { + "Name": "ContainsAny", + "Description": "", + "Aliases": null, + "Examples": null + }, + { + "Name": "CountRunes", + "Description": "", + "Aliases": [ + "countrunes" + ], + "Examples": [] + }, + { + "Name": "CountWords", + "Description": "", + "Aliases": [ + "countwords" + ], + "Examples": [] + }, + { + "Name": "FindRE", + "Description": "", + "Aliases": [ + "findRE" + ], + "Examples": [ + [ + "{{ findRE \"[G|g]o\" \"Hugo is a static side generator written in Go.\" \"1\" }}", + "[go]" + ] + ] + }, + { + "Name": "HasPrefix", + "Description": "", + "Aliases": [ + "hasPrefix" + ], + "Examples": [ + [ + "{{ hasPrefix \"Hugo\" \"Hu\" }}", + "true" + ], + [ + "{{ hasPrefix \"Hugo\" \"Fu\" }}", + "false" + ] + ] + }, + { + "Name": "HasSuffix", + "Description": "", + "Aliases": null, + "Examples": null + }, + { + "Name": "Replace", + "Description": "", + "Aliases": [ + "replace" + ], + "Examples": [ + [ + "{{ replace \"Batman and Robin\" \"Robin\" \"Catwoman\" }}", + "Batman and Catwoman" + ] + ] + }, + { + "Name": "ReplaceRE", + "Description": "", + "Aliases": [ + "replaceRE" + ], + "Examples": [] + }, + { + "Name": "SliceString", + "Description": "", + "Aliases": [ + "slicestr" + ], + "Examples": [ + [ + "{{slicestr \"BatMan\" 0 3}}", + "Bat" + ], + [ + "{{slicestr \"BatMan\" 3}}", + "Man" + ] + ] + }, + { + "Name": "Split", + "Description": "", + "Aliases": [ + "split" + ], + "Examples": [] + }, + { + "Name": "Substr", + "Description": "", + "Aliases": [ + "substr" + ], + "Examples": [ + [ + "{{substr \"BatMan\" 0 -3}}", + "Bat" + ], + [ + "{{substr \"BatMan\" 3 3}}", + "Man" + ] + ] + }, + { + "Name": "Title", + "Description": "", + "Aliases": [ + "title" + ], + "Examples": [ + [ + "{{title \"Bat man\"}}", + "Bat Man" + ] + ] + }, + { + "Name": "ToLower", + "Description": "", + "Aliases": [ + "lower" + ], + "Examples": [ + [ + "{{lower \"BatMan\"}}", + "batman" + ] + ] + }, + { + "Name": "ToUpper", + "Description": "", + "Aliases": [ + "upper" + ], + "Examples": [ + [ + "{{upper \"BatMan\"}}", + "BATMAN" + ] + ] + }, + { + "Name": "Trim", + "Description": "", + "Aliases": [ + "trim" + ], + "Examples": [ + [ + "{{ trim \"++Batman--\" \"+-\" }}", + "Batman" + ] + ] + }, + { + "Name": "TrimPrefix", + "Description": "", + "Aliases": null, + "Examples": null + }, + { + "Name": "TrimSuffix", + "Description": "", + "Aliases": null, + "Examples": null + }, + { + "Name": "Truncate", + "Description": "", + "Aliases": [ + "truncate" + ], + "Examples": [ + [ + "{{ \"this is a very long text\" | truncate 10 \" ...\" }}", + "this is a ..." + ], + [ + "{{ \"With [Markdown](/markdown) inside.\" | markdownify | truncate 14 }}", + "With \u003ca href=\"/markdown\"\u003eMarkdown …\u003c/a\u003e" + ] + ] + } + ] + }, + { + "Name": "time", + "Funcs": [ + { + "Name": "AsTime", + "Description": "", + "Aliases": [ + "asTime" + ], + "Examples": [ + [ + "{{ (asTime \"2015-01-21\").Year }}", + "2015" + ] + ] + }, + { + "Name": "Format", + "Description": "", + "Aliases": [ + "dateFormat" + ], + "Examples": [ + [ + "dateFormat: {{ dateFormat \"Monday, Jan 2, 2006\" \"2015-01-21\" }}", + "dateFormat: Wednesday, Jan 21, 2015" + ] + ] + }, + { + "Name": "Now", + "Description": "", + "Aliases": [ + "now" + ], + "Examples": [] + } + ] + }, + { + "Name": "transform", + "Funcs": [ + { + "Name": "Emojify", + "Description": "", + "Aliases": [ + "emojify" + ], + "Examples": [ + [ + "{{ \"I :heart: Hugo\" | emojify }}", + "I ❤️ Hugo" + ] + ] + }, + { + "Name": "HTMLEscape", + "Description": "", + "Aliases": [ + "htmlEscape" + ], + "Examples": [ + [ + "{{ htmlEscape \"Cathal Garvey \u0026 The Sunshine Band \u003ccathal@foo.bar\u003e\" | safeHTML}}", + "Cathal Garvey \u0026amp; The Sunshine Band \u0026lt;cathal@foo.bar\u0026gt;" + ], + [ + "{{ htmlEscape \"Cathal Garvey \u0026 The Sunshine Band \u003ccathal@foo.bar\u003e\"}}", + "Cathal Garvey \u0026amp;amp; The Sunshine Band \u0026amp;lt;cathal@foo.bar\u0026amp;gt;" + ], + [ + "{{ htmlEscape \"Cathal Garvey \u0026 The Sunshine Band \u003ccathal@foo.bar\u003e\" | htmlUnescape | safeHTML }}", + "Cathal Garvey \u0026 The Sunshine Band \u003ccathal@foo.bar\u003e" + ] + ] + }, + { + "Name": "HTMLUnescape", + "Description": "", + "Aliases": [ + "htmlUnescape" + ], + "Examples": [ + [ + "{{ htmlUnescape \"Cathal Garvey \u0026amp; The Sunshine Band \u0026lt;cathal@foo.bar\u0026gt;\" | safeHTML}}", + "Cathal Garvey \u0026 The Sunshine Band \u003ccathal@foo.bar\u003e" + ], + [ + "{{\"Cathal Garvey \u0026amp;amp; The Sunshine Band \u0026amp;lt;cathal@foo.bar\u0026amp;gt;\" | htmlUnescape | htmlUnescape | safeHTML}}", + "Cathal Garvey \u0026 The Sunshine Band \u003ccathal@foo.bar\u003e" + ], + [ + "{{\"Cathal Garvey \u0026amp;amp; The Sunshine Band \u0026amp;lt;cathal@foo.bar\u0026amp;gt;\" | htmlUnescape | htmlUnescape }}", + "Cathal Garvey \u0026amp; The Sunshine Band \u0026lt;cathal@foo.bar\u0026gt;" + ], + [ + "{{ htmlUnescape \"Cathal Garvey \u0026amp; The Sunshine Band \u0026lt;cathal@foo.bar\u0026gt;\" | htmlEscape | safeHTML }}", + "Cathal Garvey \u0026amp; The Sunshine Band \u0026lt;cathal@foo.bar\u0026gt;" + ] + ] + }, + { + "Name": "Highlight", + "Description": "", + "Aliases": [ + "highlight" + ], + "Examples": [] + }, + { + "Name": "Markdownify", + "Description": "", + "Aliases": [ + "markdownify" + ], + "Examples": [ + [ + "{{ .Title | markdownify}}", + "\u003cstrong\u003eBatMan\u003c/strong\u003e" + ] + ] + }, + { + "Name": "Plainify", + "Description": "", + "Aliases": [ + "plainify" + ], + "Examples": [ + [ + "{{ plainify \"Hello \u003cstrong\u003eworld\u003c/strong\u003e, gophers!\" }}", + "Hello world, gophers!" + ] + ] + } + ] + }, + { + "Name": "urls", + "Funcs": [ + { + "Name": "AbsLangURL", + "Description": "", + "Aliases": [ + "absLangURL" + ], + "Examples": [] + }, + { + "Name": "AbsURL", + "Description": "", + "Aliases": [ + "absURL" + ], + "Examples": [] + }, + { + "Name": "Ref", + "Description": "", + "Aliases": [ + "ref" + ], + "Examples": [] + }, + { + "Name": "RelLangURL", + "Description": "", + "Aliases": [ + "relLangURL" + ], + "Examples": [] + }, + { + "Name": "RelRef", + "Description": "", + "Aliases": [ + "relref" + ], + "Examples": [] + }, + { + "Name": "RelURL", + "Description": "", + "Aliases": [ + "relURL" + ], + "Examples": [] + }, + { + "Name": "URLize", + "Description": "", + "Aliases": [ + "urlize" + ], + "Examples": [] + } + ] + } + ] } } diff --git a/tpl/cast/docshelper.go b/tpl/cast/docshelper.go new file mode 100644 index 000000000..5220ca570 --- /dev/null +++ b/tpl/cast/docshelper.go @@ -0,0 +1,41 @@ +// Copyright 2017 The Hugo Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cast + +import ( + "github.com/spf13/hugo/deps" + "github.com/spf13/hugo/docshelper" + "github.com/spf13/hugo/tpl/internal" +) + +// This file provides documentation support and is randomly put into this package. +func init() { + docsProvider := func() map[string]interface{} { + docs := make(map[string]interface{}) + d := &deps.Deps{} + + var namespaces []*internal.TemplateFuncsNamespace + + for _, nsf := range internal.TemplateFuncsNamespaceRegistry { + nf := nsf(d) + namespaces = append(namespaces, nf) + + } + + docs["funcs"] = namespaces + return docs + } + + docshelper.AddDocProvider("tpl", docsProvider) +} diff --git a/tpl/cast/init.go b/tpl/cast/init.go index acddaa91a..1c737ee58 100644 --- a/tpl/cast/init.go +++ b/tpl/cast/init.go @@ -24,21 +24,27 @@ func init() { f := func(d *deps.Deps) *internal.TemplateFuncsNamespace { ctx := New() - examples := [][2]string{ - {`{{ "1234" | int | printf "%T" }}`, `int`}, - {`{{ 1234 | string | printf "%T" }}`, `string`}, - } - - return &internal.TemplateFuncsNamespace{ + ns := &internal.TemplateFuncsNamespace{ Name: name, Context: func() interface{} { return ctx }, - Aliases: map[string]interface{}{ - "int": ctx.ToInt, - "string": ctx.ToString, - }, - Examples: examples, } + ns.AddMethodMapping(ctx.ToInt, + []string{"int"}, + [][2]string{ + {`{{ "1234" | int | printf "%T" }}`, `int`}, + }, + ) + + ns.AddMethodMapping(ctx.ToString, + []string{"string"}, + [][2]string{ + {`{{ 1234 | string | printf "%T" }}`, `string`}, + }, + ) + + return ns + } internal.AddTemplateFuncsNamespace(f) diff --git a/tpl/collections/init.go b/tpl/collections/init.go index 29f6809c3..289228901 100644 --- a/tpl/collections/init.go +++ b/tpl/collections/init.go @@ -24,48 +24,122 @@ func init() { f := func(d *deps.Deps) *internal.TemplateFuncsNamespace { ctx := New(d) - examples := [][2]string{ - {`{{ delimit (slice "A" "B" "C") ", " " and " }}`, `A, B and C`}, - {`{{ echoParam .Params "langCode" }}`, `en`}, - {`{{ if in "this string contains a substring" "substring" }}Substring found!{{ end }}`, `Substring found!`}, - { - `{{ (querify "foo" 1 "bar" 2 "baz" "with spaces" "qux" "this&that=those") | safeHTML }}`, - `bar=2&baz=with+spaces&foo=1&qux=this%26that%3Dthose`}, - { - `Search`, - `Search`}, - {`{{ slice "B" "C" "A" | sort }}`, `[A B C]`}, - {`{{ seq 3 }}`, `[1 2 3]`}, - {`{{ union (slice 1 2 3) (slice 3 4 5) }}`, `[1 2 3 4 5]`}, - } - - return &internal.TemplateFuncsNamespace{ + ns := &internal.TemplateFuncsNamespace{ Name: name, Context: func() interface{} { return ctx }, - Aliases: map[string]interface{}{ - "after": ctx.After, - "apply": ctx.Apply, - "delimit": ctx.Delimit, - "dict": ctx.Dictionary, - "echoParam": ctx.EchoParam, - "first": ctx.First, - "in": ctx.In, - "index": ctx.Index, - "intersect": ctx.Intersect, - "isSet": ctx.IsSet, - "isset": ctx.IsSet, - "last": ctx.Last, - "querify": ctx.Querify, - "shuffle": ctx.Shuffle, - "slice": ctx.Slice, - "sort": ctx.Sort, - "union": ctx.Union, - "where": ctx.Where, - "seq": ctx.Seq, - }, - Examples: examples, } + ns.AddMethodMapping(ctx.After, + []string{"after"}, + [][2]string{}, + ) + + ns.AddMethodMapping(ctx.Apply, + []string{"apply"}, + [][2]string{}, + ) + + ns.AddMethodMapping(ctx.Delimit, + []string{"delimit"}, + [][2]string{ + {`{{ delimit (slice "A" "B" "C") ", " " and " }}`, `A, B and C`}, + }, + ) + + ns.AddMethodMapping(ctx.Dictionary, + []string{"dict"}, + [][2]string{}, + ) + + ns.AddMethodMapping(ctx.EchoParam, + []string{"echoParam"}, + [][2]string{ + {`{{ echoParam .Params "langCode" }}`, `en`}, + }, + ) + + ns.AddMethodMapping(ctx.First, + []string{"first"}, + [][2]string{}, + ) + + ns.AddMethodMapping(ctx.In, + []string{"in"}, + [][2]string{ + {`{{ if in "this string contains a substring" "substring" }}Substring found!{{ end }}`, `Substring found!`}, + }, + ) + + ns.AddMethodMapping(ctx.Index, + []string{"index"}, + [][2]string{}, + ) + + ns.AddMethodMapping(ctx.Intersect, + []string{"intersect"}, + [][2]string{}, + ) + + ns.AddMethodMapping(ctx.IsSet, + []string{"isSet", "isset"}, + [][2]string{}, + ) + + ns.AddMethodMapping(ctx.Last, + []string{"last"}, + [][2]string{}, + ) + + ns.AddMethodMapping(ctx.Querify, + []string{"querify"}, + [][2]string{ + { + `{{ (querify "foo" 1 "bar" 2 "baz" "with spaces" "qux" "this&that=those") | safeHTML }}`, + `bar=2&baz=with+spaces&foo=1&qux=this%26that%3Dthose`}, + { + `Search`, + `Search`}, + }, + ) + + ns.AddMethodMapping(ctx.Shuffle, + []string{"shuffle"}, + [][2]string{}, + ) + + ns.AddMethodMapping(ctx.Slice, + []string{"slice"}, + [][2]string{ + {`{{ slice "B" "C" "A" | sort }}`, `[A B C]`}, + }, + ) + + ns.AddMethodMapping(ctx.Sort, + []string{"sort"}, + [][2]string{}, + ) + + ns.AddMethodMapping(ctx.Union, + []string{"union"}, + [][2]string{ + {`{{ union (slice 1 2 3) (slice 3 4 5) }}`, `[1 2 3 4 5]`}, + }, + ) + + ns.AddMethodMapping(ctx.Where, + []string{"where"}, + [][2]string{}, + ) + + ns.AddMethodMapping(ctx.Seq, + []string{"seq"}, + [][2]string{ + {`{{ seq 3 }}`, `[1 2 3]`}, + }, + ) + + return ns + } internal.AddTemplateFuncsNamespace(f) diff --git a/tpl/compare/init.go b/tpl/compare/init.go index bf8227353..0285abff5 100644 --- a/tpl/compare/init.go +++ b/tpl/compare/init.go @@ -24,27 +24,53 @@ func init() { f := func(d *deps.Deps) *internal.TemplateFuncsNamespace { ctx := New() - examples := [][2]string{ - {`{{ if eq .Section "blog" }}current{{ end }}`, `current`}, - {`{{ "Hugo Rocks!" | default "Hugo Rules!" }}`, `Hugo Rocks!`}, - {`{{ "" | default "Hugo Rules!" }}`, `Hugo Rules!`}, - } - - return &internal.TemplateFuncsNamespace{ + ns := &internal.TemplateFuncsNamespace{ Name: name, Context: func() interface{} { return ctx }, - Aliases: map[string]interface{}{ - "default": ctx.Default, - "eq": ctx.Eq, - "ge": ctx.Ge, - "gt": ctx.Gt, - "le": ctx.Le, - "lt": ctx.Lt, - "ne": ctx.Ne, - }, - Examples: examples, } + ns.AddMethodMapping(ctx.Default, + []string{"default"}, + [][2]string{ + {`{{ "Hugo Rocks!" | default "Hugo Rules!" }}`, `Hugo Rocks!`}, + {`{{ "" | default "Hugo Rules!" }}`, `Hugo Rules!`}, + }, + ) + + ns.AddMethodMapping(ctx.Eq, + []string{"eq"}, + [][2]string{ + {`{{ if eq .Section "blog" }}current{{ end }}`, `current`}, + }, + ) + + ns.AddMethodMapping(ctx.Ge, + []string{"ge"}, + [][2]string{}, + ) + + ns.AddMethodMapping(ctx.Gt, + []string{"gt"}, + [][2]string{}, + ) + + ns.AddMethodMapping(ctx.Le, + []string{"le"}, + [][2]string{}, + ) + + ns.AddMethodMapping(ctx.Lt, + []string{"lt"}, + [][2]string{}, + ) + + ns.AddMethodMapping(ctx.Ne, + []string{"ne"}, + [][2]string{}, + ) + + return ns + } internal.AddTemplateFuncsNamespace(f) diff --git a/tpl/crypto/init.go b/tpl/crypto/init.go index 7c1899f88..a47c3369f 100644 --- a/tpl/crypto/init.go +++ b/tpl/crypto/init.go @@ -24,24 +24,35 @@ func init() { f := func(d *deps.Deps) *internal.TemplateFuncsNamespace { ctx := New() - examples := [][2]string{ - {`{{ md5 "Hello world, gophers!" }}`, `b3029f756f98f79e7f1b7f1d1f0dd53b`}, - {`{{ crypto.MD5 "Hello world, gophers!" }}`, `b3029f756f98f79e7f1b7f1d1f0dd53b`}, - {`{{ sha1 "Hello world, gophers!" }}`, `c8b5b0e33d408246e30f53e32b8f7627a7a649d4`}, - {`{{ sha256 "Hello world, gophers!" }}`, `6ec43b78da9669f50e4e422575c54bf87536954ccd58280219c393f2ce352b46`}, - } - - return &internal.TemplateFuncsNamespace{ + ns := &internal.TemplateFuncsNamespace{ Name: name, Context: func() interface{} { return ctx }, - Aliases: map[string]interface{}{ - "md5": ctx.MD5, - "sha1": ctx.SHA1, - "sha256": ctx.SHA256, - }, - Examples: examples, } + ns.AddMethodMapping(ctx.MD5, + []string{"md5"}, + [][2]string{ + {`{{ md5 "Hello world, gophers!" }}`, `b3029f756f98f79e7f1b7f1d1f0dd53b`}, + {`{{ crypto.MD5 "Hello world, gophers!" }}`, `b3029f756f98f79e7f1b7f1d1f0dd53b`}, + }, + ) + + ns.AddMethodMapping(ctx.SHA1, + []string{"sha1"}, + [][2]string{ + {`{{ sha1 "Hello world, gophers!" }}`, `c8b5b0e33d408246e30f53e32b8f7627a7a649d4`}, + }, + ) + + ns.AddMethodMapping(ctx.SHA256, + []string{"sha256"}, + [][2]string{ + {`{{ sha256 "Hello world, gophers!" }}`, `6ec43b78da9669f50e4e422575c54bf87536954ccd58280219c393f2ce352b46`}, + }, + ) + + return ns + } internal.AddTemplateFuncsNamespace(f) diff --git a/tpl/data/init.go b/tpl/data/init.go index 476e9ab75..e9e4ca2f9 100644 --- a/tpl/data/init.go +++ b/tpl/data/init.go @@ -24,19 +24,21 @@ func init() { f := func(d *deps.Deps) *internal.TemplateFuncsNamespace { ctx := New(d) - examples := [][2]string{ - {}, - } - - return &internal.TemplateFuncsNamespace{ + ns := &internal.TemplateFuncsNamespace{ Name: name, Context: func() interface{} { return ctx }, - Aliases: map[string]interface{}{ - "getCSV": ctx.GetCSV, - "getJSON": ctx.GetJSON, - }, - Examples: examples, } + + ns.AddMethodMapping(ctx.GetCSV, + []string{"getCSV"}, + [][2]string{}, + ) + + ns.AddMethodMapping(ctx.GetJSON, + []string{"getJSON"}, + [][2]string{}, + ) + return ns } internal.AddTemplateFuncsNamespace(f) diff --git a/tpl/encoding/init.go b/tpl/encoding/init.go index 5bc0eb145..b46394203 100644 --- a/tpl/encoding/init.go +++ b/tpl/encoding/init.go @@ -24,24 +24,35 @@ func init() { f := func(d *deps.Deps) *internal.TemplateFuncsNamespace { ctx := New() - examples := [][2]string{ - {`{{ (slice "A" "B" "C") | jsonify }}`, `["A","B","C"]`}, - {`{{ "SGVsbG8gd29ybGQ=" | base64Decode }}`, `Hello world`}, - {`{{ 42 | base64Encode | base64Decode }}`, `42`}, - {`{{ "Hello world" | base64Encode }}`, `SGVsbG8gd29ybGQ=`}, - } - - return &internal.TemplateFuncsNamespace{ + ns := &internal.TemplateFuncsNamespace{ Name: name, Context: func() interface{} { return ctx }, - Aliases: map[string]interface{}{ - "base64Decode": ctx.Base64Decode, - "base64Encode": ctx.Base64Encode, - "jsonify": ctx.Jsonify, - }, - Examples: examples, } + ns.AddMethodMapping(ctx.Base64Decode, + []string{"base64Decode"}, + [][2]string{ + {`{{ "SGVsbG8gd29ybGQ=" | base64Decode }}`, `Hello world`}, + {`{{ 42 | base64Encode | base64Decode }}`, `42`}, + }, + ) + + ns.AddMethodMapping(ctx.Base64Encode, + []string{"base64Encode"}, + [][2]string{ + {`{{ "Hello world" | base64Encode }}`, `SGVsbG8gd29ybGQ=`}, + }, + ) + + ns.AddMethodMapping(ctx.Jsonify, + []string{"jsonify"}, + [][2]string{ + {`{{ (slice "A" "B" "C") | jsonify }}`, `["A","B","C"]`}, + }, + ) + + return ns + } internal.AddTemplateFuncsNamespace(f) diff --git a/tpl/fmt/fmt.go b/tpl/fmt/fmt.go index 5e320fede..ca31ec522 100644 --- a/tpl/fmt/fmt.go +++ b/tpl/fmt/fmt.go @@ -26,14 +26,15 @@ func New() *Namespace { type Namespace struct { } -func (ns *Namespace) Print(a ...interface{}) (n int, err error) { - return _fmt.Print(a...) +func (ns *Namespace) Print(a ...interface{}) string { + return _fmt.Sprint(a...) } -func (ns *Namespace) Printf(format string, a ...interface{}) (n int, err error) { - return _fmt.Printf(format, a...) +func (ns *Namespace) Printf(format string, a ...interface{}) string { + return _fmt.Sprintf(format, a...) + } -func (ns *Namespace) Println(a ...interface{}) (n int, err error) { - return _fmt.Println(a...) +func (ns *Namespace) Println(a ...interface{}) string { + return _fmt.Sprintln(a...) } diff --git a/tpl/fmt/init.go b/tpl/fmt/init.go index 0f4296263..98070b777 100644 --- a/tpl/fmt/init.go +++ b/tpl/fmt/init.go @@ -24,18 +24,33 @@ func init() { f := func(d *deps.Deps) *internal.TemplateFuncsNamespace { ctx := New() - examples := [][2]string{ - {`{{ print "works!" }}`, `works!`}, - {`{{ printf "%s!" "works" }}`, `works!`}, - {`{{ println "works!" }}`, "works!\n"}, + ns := &internal.TemplateFuncsNamespace{ + Name: name, + Context: func() interface{} { return ctx }, } - return &internal.TemplateFuncsNamespace{ - Name: name, - Context: func() interface{} { return ctx }, - Aliases: map[string]interface{}{}, - Examples: examples, - } + ns.AddMethodMapping(ctx.Print, + []string{"print"}, + [][2]string{ + {`{{ print "works!" }}`, `works!`}, + }, + ) + + ns.AddMethodMapping(ctx.Println, + []string{"println"}, + [][2]string{ + {`{{ println "works!" }}`, "works!\n"}, + }, + ) + + ns.AddMethodMapping(ctx.Printf, + []string{"printf"}, + [][2]string{ + {`{{ printf "%s!" "works" }}`, `works!`}, + }, + ) + + return ns } diff --git a/tpl/images/init.go b/tpl/images/init.go index 0c2cb57c4..8e829f300 100644 --- a/tpl/images/init.go +++ b/tpl/images/init.go @@ -24,19 +24,18 @@ func init() { f := func(d *deps.Deps) *internal.TemplateFuncsNamespace { ctx := New(d) - examples := [][2]string{ - {}, - } - - return &internal.TemplateFuncsNamespace{ + ns := &internal.TemplateFuncsNamespace{ Name: name, Context: func() interface{} { return ctx }, - Aliases: map[string]interface{}{ - "imageConfig": ctx.Config, - }, - Examples: examples, } + ns.AddMethodMapping(ctx.Config, + []string{"imageConfig"}, + [][2]string{}, + ) + + return ns + } internal.AddTemplateFuncsNamespace(f) diff --git a/tpl/inflect/init.go b/tpl/inflect/init.go index b42ae5af3..50d012d35 100644 --- a/tpl/inflect/init.go +++ b/tpl/inflect/init.go @@ -24,26 +24,37 @@ func init() { f := func(d *deps.Deps) *internal.TemplateFuncsNamespace { ctx := New() - examples := [][2]string{ - {`{{ humanize "my-first-post" }}`, `My first post`}, - {`{{ humanize "myCamelPost" }}`, `My camel post`}, - {`{{ humanize "52" }}`, `52nd`}, - {`{{ humanize 103 }}`, `103rd`}, - {`{{ "cat" | pluralize }}`, `cats`}, - {`{{ "cats" | singularize }}`, `cat`}, - } - - return &internal.TemplateFuncsNamespace{ + ns := &internal.TemplateFuncsNamespace{ Name: name, Context: func() interface{} { return ctx }, - Aliases: map[string]interface{}{ - "humanize": ctx.Humanize, - "pluralize": ctx.Pluralize, - "singularize": ctx.Singularize, - }, - Examples: examples, } + ns.AddMethodMapping(ctx.Humanize, + []string{"humanize"}, + [][2]string{ + {`{{ humanize "my-first-post" }}`, `My first post`}, + {`{{ humanize "myCamelPost" }}`, `My camel post`}, + {`{{ humanize "52" }}`, `52nd`}, + {`{{ humanize 103 }}`, `103rd`}, + }, + ) + + ns.AddMethodMapping(ctx.Pluralize, + []string{"pluralize"}, + [][2]string{ + {`{{ "cat" | pluralize }}`, `cats`}, + }, + ) + + ns.AddMethodMapping(ctx.Singularize, + []string{"singularize"}, + [][2]string{ + {`{{ "cats" | singularize }}`, `cat`}, + }, + ) + + return ns + } internal.AddTemplateFuncsNamespace(f) diff --git a/tpl/internal/templatefuncRegistry_test.go b/tpl/internal/templatefuncRegistry_test.go new file mode 100644 index 000000000..dfc4ba09b --- /dev/null +++ b/tpl/internal/templatefuncRegistry_test.go @@ -0,0 +1,33 @@ +// Copyright 2017 The Hugo Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package internal + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +type Test struct { +} + +func (t *Test) MyTestMethod() string { + return "abcde" +} + +func TestMethodToName(t *testing.T) { + test := &Test{} + + require.Equal(t, "MyTestMethod", methodToName(test.MyTestMethod)) +} diff --git a/tpl/internal/templatefuncsRegistry.go b/tpl/internal/templatefuncsRegistry.go index aa3196ca3..c9c931579 100644 --- a/tpl/internal/templatefuncsRegistry.go +++ b/tpl/internal/templatefuncsRegistry.go @@ -16,6 +16,12 @@ package internal import ( + "encoding/json" + "path/filepath" + "reflect" + "runtime" + "strings" + "github.com/spf13/hugo/deps" ) @@ -32,12 +38,46 @@ type TemplateFuncsNamespace struct { // This is the method receiver. Context interface{} + // Additional info, aliases and examples, per method name. + MethodMappings map[string]TemplateFuncMethodMapping +} + +func (t *TemplateFuncsNamespace) AddMethodMapping(m interface{}, aliases []string, examples [][2]string) { + if t.MethodMappings == nil { + t.MethodMappings = make(map[string]TemplateFuncMethodMapping) + } + + name := methodToName(m) + + // sanity check + for _, e := range examples { + if e[0] == "" { + panic(t.Name + ": Empty example for " + name) + } + } + for _, a := range aliases { + if a == "" { + panic(t.Name + ": Empty alias for " + name) + } + } + + t.MethodMappings[name] = TemplateFuncMethodMapping{ + Method: m, + Aliases: aliases, + Examples: examples, + } + +} + +type TemplateFuncMethodMapping struct { + Method interface{} + // Any template funcs aliases. This is mainly motivated by keeping // backwards compability, but some new template funcs may also make // sense to give short and snappy aliases. // Note that these aliases are global and will be merged, so the last // key will win. - Aliases map[string]interface{} + Aliases []string // A slice of input/expected examples. // We keep it a the namespace level for now, but may find a way to keep track @@ -45,3 +85,44 @@ type TemplateFuncsNamespace struct { // Some of these, hopefully just a few, may depend on some test data to run. Examples [][2]string } + +func methodToName(m interface{}) string { + name := runtime.FuncForPC(reflect.ValueOf(m).Pointer()).Name() + name = filepath.Ext(name) + name = strings.TrimPrefix(name, ".") + name = strings.TrimSuffix(name, "-fm") + return name +} + +func (t *TemplateFuncsNamespace) MarshalJSON() ([]byte, error) { + type Func struct { + Name string + Description string // TODO(bep) + Aliases []string + Examples [][2]string + } + // TODO(bep) map/lookup from docs template Namespace + Func name. + var funcs []Func + + ctx := t.Context.(func() interface{})() + ctxType := reflect.TypeOf(ctx) + for i := 0; i < ctxType.NumMethod(); i++ { + method := ctxType.Method(i) + f := Func{ + Name: method.Name, + } + if mapping, ok := t.MethodMappings[method.Name]; ok { + f.Aliases = mapping.Aliases + f.Examples = mapping.Examples + } + funcs = append(funcs, f) + } + + return json.Marshal(&struct { + Name string + Funcs []Func + }{ + Name: t.Name, + Funcs: funcs, + }) +} diff --git a/tpl/lang/init.go b/tpl/lang/init.go index a902c7a66..6cf8e790d 100644 --- a/tpl/lang/init.go +++ b/tpl/lang/init.go @@ -24,20 +24,18 @@ func init() { f := func(d *deps.Deps) *internal.TemplateFuncsNamespace { ctx := New(d) - examples := [][2]string{ - {}, - } - - return &internal.TemplateFuncsNamespace{ + ns := &internal.TemplateFuncsNamespace{ Name: name, Context: func() interface{} { return ctx }, - Aliases: map[string]interface{}{ - "i18n": ctx.Translate, - "T": ctx.Translate, - }, - Examples: examples, } + ns.AddMethodMapping(ctx.Translate, + []string{"i18n", "T"}, + [][2]string{}, + ) + + return ns + } internal.AddTemplateFuncsNamespace(f) diff --git a/tpl/math/init.go b/tpl/math/init.go index 69ba2cc17..65b22c516 100644 --- a/tpl/math/init.go +++ b/tpl/math/init.go @@ -24,29 +24,55 @@ func init() { f := func(d *deps.Deps) *internal.TemplateFuncsNamespace { ctx := New() - examples := [][2]string{ - {"{{add 1 2}}", "3"}, - {"{{div 6 3}}", "2"}, - {"{{mod 15 3}}", "0"}, - {"{{modBool 15 3}}", "true"}, - {"{{mul 2 3}}", "6"}, - {"{{sub 3 2}}", "1"}, - } - - return &internal.TemplateFuncsNamespace{ + ns := &internal.TemplateFuncsNamespace{ Name: name, Context: func() interface{} { return ctx }, - Aliases: map[string]interface{}{ - "add": ctx.Add, - "div": ctx.Div, - "mod": ctx.Mod, - "modBool": ctx.ModBool, - "mul": ctx.Mul, - "sub": ctx.Sub, - }, - Examples: examples, } + ns.AddMethodMapping(ctx.Add, + []string{"add"}, + [][2]string{ + {"{{add 1 2}}", "3"}, + }, + ) + + ns.AddMethodMapping(ctx.Div, + []string{"div"}, + [][2]string{ + {"{{div 6 3}}", "2"}, + }, + ) + + ns.AddMethodMapping(ctx.Mod, + []string{"mod"}, + [][2]string{ + {"{{mod 15 3}}", "0"}, + }, + ) + + ns.AddMethodMapping(ctx.ModBool, + []string{"modBool"}, + [][2]string{ + {"{{modBool 15 3}}", "true"}, + }, + ) + + ns.AddMethodMapping(ctx.Mul, + []string{"mul"}, + [][2]string{ + {"{{mul 2 3}}", "6"}, + }, + ) + + ns.AddMethodMapping(ctx.Sub, + []string{"sub"}, + [][2]string{ + {"{{sub 3 2}}", "1"}, + }, + ) + + return ns + } internal.AddTemplateFuncsNamespace(f) diff --git a/tpl/os/init.go b/tpl/os/init.go index d03ad5f03..264afd43a 100644 --- a/tpl/os/init.go +++ b/tpl/os/init.go @@ -24,22 +24,32 @@ func init() { f := func(d *deps.Deps) *internal.TemplateFuncsNamespace { ctx := New(d) - examples := [][2]string{ - {`{{ range (readDir ".") }}{{ .Name }}{{ end }}`, "README.txt"}, - {`{{ readFile "README.txt" }}`, `Hugo Rocks!`}, - } - - return &internal.TemplateFuncsNamespace{ + ns := &internal.TemplateFuncsNamespace{ Name: name, Context: func() interface{} { return ctx }, - Aliases: map[string]interface{}{ - "getenv": ctx.Getenv, - "readDir": ctx.ReadDir, - "readFile": ctx.ReadFile, - }, - Examples: examples, } + ns.AddMethodMapping(ctx.Getenv, + []string{"getenv"}, + [][2]string{}, + ) + + ns.AddMethodMapping(ctx.ReadDir, + []string{"readDir"}, + [][2]string{ + {`{{ range (readDir ".") }}{{ .Name }}{{ end }}`, "README.txt"}, + }, + ) + + ns.AddMethodMapping(ctx.ReadFile, + []string{"readFile"}, + [][2]string{ + {`{{ readFile "README.txt" }}`, `Hugo Rocks!`}, + }, + ) + + return ns + } internal.AddTemplateFuncsNamespace(f) diff --git a/tpl/partials/init.go b/tpl/partials/init.go index a9d1f82fe..4ee3faf19 100644 --- a/tpl/partials/init.go +++ b/tpl/partials/init.go @@ -24,20 +24,25 @@ func init() { f := func(d *deps.Deps) *internal.TemplateFuncsNamespace { ctx := New(d) - examples := [][2]string{ - {`{{ partial "header.html" . }}`, `Hugo Rocks!`}, - } - - return &internal.TemplateFuncsNamespace{ + ns := &internal.TemplateFuncsNamespace{ Name: name, Context: func() interface{} { return ctx }, - Aliases: map[string]interface{}{ - "partial": ctx.Include, - "partialCached": ctx.getCached, - }, - Examples: examples, } + ns.AddMethodMapping(ctx.Include, + []string{"partial"}, + [][2]string{ + {`{{ partial "header.html" . }}`, `Hugo Rocks!`}, + }, + ) + + ns.AddMethodMapping(ctx.getCached, + []string{"partialCached"}, + [][2]string{}, + ) + + return ns + } internal.AddTemplateFuncsNamespace(f) diff --git a/tpl/safe/init.go b/tpl/safe/init.go index fc47c66a7..94012a6ca 100644 --- a/tpl/safe/init.go +++ b/tpl/safe/init.go @@ -24,30 +24,57 @@ func init() { f := func(d *deps.Deps) *internal.TemplateFuncsNamespace { ctx := New() - examples := [][2]string{ - {`{{ "Bat&Man" | safeCSS | safeCSS }}`, `Bat&Man`}, - {`{{ "Bat&Man" | safeHTML | safeHTML }}`, `Bat&Man`}, - {`{{ "Bat&Man" | safeHTML }}`, `Bat&Man`}, - {`{{ "(1*2)" | safeJS | safeJS }}`, `(1*2)`}, - {`{{ "http://gohugo.io" | safeURL | safeURL }}`, `http://gohugo.io`}, - } - - return &internal.TemplateFuncsNamespace{ + ns := &internal.TemplateFuncsNamespace{ Name: name, Context: func() interface{} { return ctx }, - Aliases: map[string]interface{}{ - "safeCSS": ctx.CSS, - "safeHTML": ctx.HTML, - "safeHTMLAttr": ctx.HTMLAttr, - "safeJS": ctx.JS, - "safeJSStr": ctx.JSStr, - "safeURL": ctx.URL, - "sanitizeURL": ctx.SanitizeURL, - "sanitizeurl": ctx.SanitizeURL, - }, - Examples: examples, } + ns.AddMethodMapping(ctx.CSS, + []string{"safeCSS"}, + [][2]string{ + {`{{ "Bat&Man" | safeCSS | safeCSS }}`, `Bat&Man`}, + }, + ) + + ns.AddMethodMapping(ctx.HTML, + []string{"safeHTML"}, + [][2]string{ + {`{{ "Bat&Man" | safeHTML | safeHTML }}`, `Bat&Man`}, + {`{{ "Bat&Man" | safeHTML }}`, `Bat&Man`}, + }, + ) + + ns.AddMethodMapping(ctx.HTMLAttr, + []string{"safeHTMLAttr"}, + [][2]string{}, + ) + + ns.AddMethodMapping(ctx.JS, + []string{"safeJS"}, + [][2]string{ + {`{{ "(1*2)" | safeJS | safeJS }}`, `(1*2)`}, + }, + ) + + ns.AddMethodMapping(ctx.JSStr, + []string{"safeJSStr"}, + [][2]string{}, + ) + + ns.AddMethodMapping(ctx.URL, + []string{"safeURL"}, + [][2]string{ + {`{{ "http://gohugo.io" | safeURL | safeURL }}`, `http://gohugo.io`}, + }, + ) + + ns.AddMethodMapping(ctx.SanitizeURL, + []string{"sanitizeURL", "sanitizeurl"}, + [][2]string{}, + ) + + return ns + } internal.AddTemplateFuncsNamespace(f) diff --git a/tpl/strings/init.go b/tpl/strings/init.go index 8e3dfdef2..9f33bd176 100644 --- a/tpl/strings/init.go +++ b/tpl/strings/init.go @@ -24,54 +24,118 @@ func init() { f := func(d *deps.Deps) *internal.TemplateFuncsNamespace { ctx := New(d) - examples := [][2]string{ - {`{{chomp "

Blockhead

\n" }}`, `

Blockhead

`}, - { - `{{ findRE "[G|g]o" "Hugo is a static side generator written in Go." "1" }}`, - `[go]`}, - {`{{ hasPrefix "Hugo" "Hu" }}`, `true`}, - {`{{ hasPrefix "Hugo" "Fu" }}`, `false`}, - {`{{lower "BatMan"}}`, `batman`}, - { - `{{ replace "Batman and Robin" "Robin" "Catwoman" }}`, - `Batman and Catwoman`}, - { - `{{ "http://gohugo.io/docs" | replaceRE "^https?://([^/]+).*" "$1" }}`, - `gohugo.io`}, - {`{{slicestr "BatMan" 0 3}}`, `Bat`}, - {`{{slicestr "BatMan" 3}}`, `Man`}, - {`{{substr "BatMan" 0 -3}}`, `Bat`}, - {`{{substr "BatMan" 3 3}}`, `Man`}, - {`{{title "Bat man"}}`, `Bat Man`}, - {`{{ trim "++Batman--" "+-" }}`, `Batman`}, - {`{{ "this is a very long text" | truncate 10 " ..." }}`, `this is a ...`}, - {`{{ "With [Markdown](/markdown) inside." | markdownify | truncate 14 }}`, `With Markdown …`}, - {`{{upper "BatMan"}}`, `BATMAN`}, - } - - return &internal.TemplateFuncsNamespace{ + ns := &internal.TemplateFuncsNamespace{ Name: name, Context: func() interface{} { return ctx }, - Aliases: map[string]interface{}{ - "chomp": ctx.Chomp, - "countrunes": ctx.CountRunes, - "countwords": ctx.CountWords, - "findRE": ctx.FindRE, - "hasPrefix": ctx.HasPrefix, - "lower": ctx.ToLower, - "replace": ctx.Replace, - "replaceRE": ctx.ReplaceRE, - "slicestr": ctx.SliceString, - "split": ctx.Split, - "substr": ctx.Substr, - "title": ctx.Title, - "trim": ctx.Trim, - "truncate": ctx.Truncate, - "upper": ctx.ToUpper, - }, - Examples: examples, } + ns.AddMethodMapping(ctx.Chomp, + []string{"chomp"}, + [][2]string{ + {`{{chomp "

Blockhead

\n" }}`, `

Blockhead

`}, + }, + ) + + ns.AddMethodMapping(ctx.CountRunes, + []string{"countrunes"}, + [][2]string{}, + ) + + ns.AddMethodMapping(ctx.CountWords, + []string{"countwords"}, + [][2]string{}, + ) + + ns.AddMethodMapping(ctx.FindRE, + []string{"findRE"}, + [][2]string{ + { + `{{ findRE "[G|g]o" "Hugo is a static side generator written in Go." "1" }}`, + `[go]`}, + }, + ) + + ns.AddMethodMapping(ctx.HasPrefix, + []string{"hasPrefix"}, + [][2]string{ + {`{{ hasPrefix "Hugo" "Hu" }}`, `true`}, + {`{{ hasPrefix "Hugo" "Fu" }}`, `false`}, + }, + ) + + ns.AddMethodMapping(ctx.ToLower, + []string{"lower"}, + [][2]string{ + {`{{lower "BatMan"}}`, `batman`}, + }, + ) + + ns.AddMethodMapping(ctx.Replace, + []string{"replace"}, + [][2]string{ + { + `{{ replace "Batman and Robin" "Robin" "Catwoman" }}`, + `Batman and Catwoman`}, + }, + ) + + ns.AddMethodMapping(ctx.ReplaceRE, + []string{"replaceRE"}, + [][2]string{}, + ) + + ns.AddMethodMapping(ctx.SliceString, + []string{"slicestr"}, + [][2]string{ + {`{{slicestr "BatMan" 0 3}}`, `Bat`}, + {`{{slicestr "BatMan" 3}}`, `Man`}, + }, + ) + + ns.AddMethodMapping(ctx.Split, + []string{"split"}, + [][2]string{}, + ) + + ns.AddMethodMapping(ctx.Substr, + []string{"substr"}, + [][2]string{ + {`{{substr "BatMan" 0 -3}}`, `Bat`}, + {`{{substr "BatMan" 3 3}}`, `Man`}, + }, + ) + + ns.AddMethodMapping(ctx.Trim, + []string{"trim"}, + [][2]string{ + {`{{ trim "++Batman--" "+-" }}`, `Batman`}, + }, + ) + + ns.AddMethodMapping(ctx.Title, + []string{"title"}, + [][2]string{ + {`{{title "Bat man"}}`, `Bat Man`}, + }, + ) + + ns.AddMethodMapping(ctx.Truncate, + []string{"truncate"}, + [][2]string{ + {`{{ "this is a very long text" | truncate 10 " ..." }}`, `this is a ...`}, + {`{{ "With [Markdown](/markdown) inside." | markdownify | truncate 14 }}`, `With Markdown …`}, + }, + ) + + ns.AddMethodMapping(ctx.ToUpper, + []string{"upper"}, + [][2]string{ + {`{{upper "BatMan"}}`, `BATMAN`}, + }, + ) + + return ns + } internal.AddTemplateFuncsNamespace(f) diff --git a/tpl/time/init.go b/tpl/time/init.go index d91c1376f..33f8a7dbb 100644 --- a/tpl/time/init.go +++ b/tpl/time/init.go @@ -24,22 +24,32 @@ func init() { f := func(d *deps.Deps) *internal.TemplateFuncsNamespace { ctx := New() - examples := [][2]string{ - {`{{ (time "2015-01-21").Year }}`, `2015`}, - {`dateFormat: {{ dateFormat "Monday, Jan 2, 2006" "2015-01-21" }}`, `dateFormat: Wednesday, Jan 21, 2015`}, - } - - return &internal.TemplateFuncsNamespace{ + ns := &internal.TemplateFuncsNamespace{ Name: name, Context: func() interface{} { return ctx }, - Aliases: map[string]interface{}{ - "dateFormat": ctx.Format, - "now": ctx.Now, - "time": ctx.AsTime, - }, - Examples: examples, } + ns.AddMethodMapping(ctx.Format, + []string{"dateFormat"}, + [][2]string{ + {`dateFormat: {{ dateFormat "Monday, Jan 2, 2006" "2015-01-21" }}`, `dateFormat: Wednesday, Jan 21, 2015`}, + }, + ) + + ns.AddMethodMapping(ctx.Now, + []string{"now"}, + [][2]string{}, + ) + + ns.AddMethodMapping(ctx.AsTime, + []string{"asTime"}, // TODO(bep) handle duplicate + [][2]string{ + {`{{ (asTime "2015-01-21").Year }}`, `2015`}, + }, + ) + + return ns + } internal.AddTemplateFuncsNamespace(f) diff --git a/tpl/tplimpl/template_funcs.go b/tpl/tplimpl/template_funcs.go index 089463a1e..938f4531c 100644 --- a/tpl/tplimpl/template_funcs.go +++ b/tpl/tplimpl/template_funcs.go @@ -51,8 +51,14 @@ func (t *templateFuncster) initFuncMap() { panic(ns.Name + " is a duplicate template func") } funcMap[ns.Name] = ns.Context - for k, v := range ns.Aliases { - funcMap[k] = v + for _, mm := range ns.MethodMappings { + for _, alias := range mm.Aliases { + if _, exists := funcMap[alias]; exists { + panic(alias + " is a duplicate template func") + } + funcMap[alias] = mm.Method + } + } } diff --git a/tpl/tplimpl/template_funcs_test.go b/tpl/tplimpl/template_funcs_test.go index b0a6e87b7..97685f96e 100644 --- a/tpl/tplimpl/template_funcs_test.go +++ b/tpl/tplimpl/template_funcs_test.go @@ -87,19 +87,21 @@ func TestTemplateFuncsExamples(t *testing.T) { for _, nsf := range internal.TemplateFuncsNamespaceRegistry { ns := nsf(d) - for i, example := range ns.Examples { - in, expected := example[0], example[1] - d.WithTemplate = func(templ tpl.TemplateHandler) error { - require.NoError(t, templ.AddTemplate("test", in)) - require.NoError(t, templ.AddTemplate("partials/header.html", "Hugo Rocks!")) - return nil - } - require.NoError(t, d.LoadResources()) + for _, mm := range ns.MethodMappings { + for i, example := range mm.Examples { + in, expected := example[0], example[1] + d.WithTemplate = func(templ tpl.TemplateHandler) error { + require.NoError(t, templ.AddTemplate("test", in)) + require.NoError(t, templ.AddTemplate("partials/header.html", "Hugo Rocks!")) + return nil + } + require.NoError(t, d.LoadResources()) - var b bytes.Buffer - require.NoError(t, d.Tmpl.Lookup("test").Execute(&b, &data)) - if b.String() != expected { - t.Fatalf("%s[%d]: got %q expected %q", ns.Name, i, b.String(), expected) + var b bytes.Buffer + require.NoError(t, d.Tmpl.Lookup("test").Execute(&b, &data)) + if b.String() != expected { + t.Fatalf("%s[%d]: got %q expected %q", ns.Name, i, b.String(), expected) + } } } } diff --git a/tpl/transform/init.go b/tpl/transform/init.go index 98994c0e6..3483d1306 100644 --- a/tpl/transform/init.go +++ b/tpl/transform/init.go @@ -24,47 +24,72 @@ func init() { f := func(d *deps.Deps) *internal.TemplateFuncsNamespace { ctx := New(d) - examples := [][2]string{ - {`{{ "I :heart: Hugo" | emojify }}`, `I ❤️ Hugo`}, - {`{{ .Title | markdownify}}`, `BatMan`}, - {`{{ plainify "Hello world, gophers!" }}`, `Hello world, gophers!`}, - { - `htmlEscape 1: {{ htmlEscape "Cathal Garvey & The Sunshine Band " | safeHTML}}`, - `htmlEscape 1: Cathal Garvey & The Sunshine Band <cathal@foo.bar>`}, - { - `htmlEscape 2: {{ htmlEscape "Cathal Garvey & The Sunshine Band "}}`, - `htmlEscape 2: Cathal Garvey &amp; The Sunshine Band &lt;cathal@foo.bar&gt;`}, - { - `htmlUnescape 1: {{htmlUnescape "Cathal Garvey & The Sunshine Band <cathal@foo.bar>" | safeHTML}}`, - `htmlUnescape 1: Cathal Garvey & The Sunshine Band `}, - { - `htmlUnescape 2: {{"Cathal Garvey &amp; The Sunshine Band &lt;cathal@foo.bar&gt;" | htmlUnescape | htmlUnescape | safeHTML}}`, - `htmlUnescape 2: Cathal Garvey & The Sunshine Band `}, - { - `htmlUnescape 3: {{"Cathal Garvey &amp; The Sunshine Band &lt;cathal@foo.bar&gt;" | htmlUnescape | htmlUnescape }}`, - `htmlUnescape 3: Cathal Garvey & The Sunshine Band <cathal@foo.bar>`}, - { - `htmlUnescape 4: {{ htmlEscape "Cathal Garvey & The Sunshine Band " | htmlUnescape | safeHTML }}`, - `htmlUnescape 4: Cathal Garvey & The Sunshine Band `}, - { - `htmlUnescape 5: {{ htmlUnescape "Cathal Garvey & The Sunshine Band <cathal@foo.bar>" | htmlEscape | safeHTML }}`, - `htmlUnescape 5: Cathal Garvey & The Sunshine Band <cathal@foo.bar>`}, - } - - return &internal.TemplateFuncsNamespace{ + ns := &internal.TemplateFuncsNamespace{ Name: name, Context: func() interface{} { return ctx }, - Aliases: map[string]interface{}{ - "emojify": ctx.Emojify, - "highlight": ctx.Highlight, - "htmlEscape": ctx.HTMLEscape, - "htmlUnescape": ctx.HTMLUnescape, - "markdownify": ctx.Markdownify, - "plainify": ctx.Plainify, - }, - Examples: examples, } + ns.AddMethodMapping(ctx.Emojify, + []string{"emojify"}, + [][2]string{ + {`{{ "I :heart: Hugo" | emojify }}`, `I ❤️ Hugo`}, + }, + ) + + ns.AddMethodMapping(ctx.Highlight, + []string{"highlight"}, + [][2]string{}, + ) + + ns.AddMethodMapping(ctx.HTMLEscape, + []string{"htmlEscape"}, + [][2]string{ + { + `{{ htmlEscape "Cathal Garvey & The Sunshine Band " | safeHTML}}`, + `Cathal Garvey & The Sunshine Band <cathal@foo.bar>`}, + { + `{{ htmlEscape "Cathal Garvey & The Sunshine Band "}}`, + `Cathal Garvey &amp; The Sunshine Band &lt;cathal@foo.bar&gt;`}, + { + `{{ htmlEscape "Cathal Garvey & The Sunshine Band " | htmlUnescape | safeHTML }}`, + `Cathal Garvey & The Sunshine Band `}, + }, + ) + + ns.AddMethodMapping(ctx.HTMLUnescape, + []string{"htmlUnescape"}, + [][2]string{ + { + `{{ htmlUnescape "Cathal Garvey & The Sunshine Band <cathal@foo.bar>" | safeHTML}}`, + `Cathal Garvey & The Sunshine Band `}, + { + `{{"Cathal Garvey &amp; The Sunshine Band &lt;cathal@foo.bar&gt;" | htmlUnescape | htmlUnescape | safeHTML}}`, + `Cathal Garvey & The Sunshine Band `}, + { + `{{"Cathal Garvey &amp; The Sunshine Band &lt;cathal@foo.bar&gt;" | htmlUnescape | htmlUnescape }}`, + `Cathal Garvey & The Sunshine Band <cathal@foo.bar>`}, + { + `{{ htmlUnescape "Cathal Garvey & The Sunshine Band <cathal@foo.bar>" | htmlEscape | safeHTML }}`, + `Cathal Garvey & The Sunshine Band <cathal@foo.bar>`}, + }, + ) + + ns.AddMethodMapping(ctx.Markdownify, + []string{"markdownify"}, + [][2]string{ + {`{{ .Title | markdownify}}`, `BatMan`}, + }, + ) + + ns.AddMethodMapping(ctx.Plainify, + []string{"plainify"}, + [][2]string{ + {`{{ plainify "Hello world, gophers!" }}`, `Hello world, gophers!`}, + }, + ) + + return ns + } internal.AddTemplateFuncsNamespace(f) diff --git a/tpl/urls/init.go b/tpl/urls/init.go index fb9b00a27..e32807f32 100644 --- a/tpl/urls/init.go +++ b/tpl/urls/init.go @@ -24,33 +24,43 @@ func init() { f := func(d *deps.Deps) *internal.TemplateFuncsNamespace { ctx := New(d) - examples := [][2]string{ - {`{{ "index.html" | absLangURL }}`, `http://mysite.com/hugo/en/index.html`}, - {`{{ "http://gohugo.io/" | absURL }}`, `http://gohugo.io/`}, - {`{{ "mystyle.css" | absURL }}`, `http://mysite.com/hugo/mystyle.css`}, - {`{{ 42 | absURL }}`, `http://mysite.com/hugo/42`}, - {`{{ "index.html" | relLangURL }}`, `/hugo/en/index.html`}, - {`{{ "http://gohugo.io/" | relURL }}`, `http://gohugo.io/`}, - {`{{ "mystyle.css" | relURL }}`, `/hugo/mystyle.css`}, - {`{{ mul 2 21 | relURL }}`, `/hugo/42`}, - {`{{ "Bat Man" | urlize }}`, `bat-man`}, - } - - return &internal.TemplateFuncsNamespace{ + ns := &internal.TemplateFuncsNamespace{ Name: name, Context: func() interface{} { return ctx }, - Aliases: map[string]interface{}{ - "absURL": ctx.AbsURL, - "absLangURL": ctx.AbsLangURL, - "ref": ctx.Ref, - "relURL": ctx.RelURL, - "relLangURL": ctx.RelLangURL, - "relref": ctx.RelRef, - "urlize": ctx.URLize, - }, - Examples: examples, } + ns.AddMethodMapping(ctx.AbsURL, + []string{"absURL"}, + [][2]string{}, + ) + + ns.AddMethodMapping(ctx.AbsLangURL, + []string{"absLangURL"}, + [][2]string{}, + ) + ns.AddMethodMapping(ctx.Ref, + []string{"ref"}, + [][2]string{}, + ) + ns.AddMethodMapping(ctx.RelURL, + []string{"relURL"}, + [][2]string{}, + ) + ns.AddMethodMapping(ctx.RelLangURL, + []string{"relLangURL"}, + [][2]string{}, + ) + ns.AddMethodMapping(ctx.RelRef, + []string{"relref"}, + [][2]string{}, + ) + ns.AddMethodMapping(ctx.URLize, + []string{"urlize"}, + [][2]string{}, + ) + + return ns + } internal.AddTemplateFuncsNamespace(f)