doublebrace

package module
v0.0.0-...-2b0922f Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 30, 2026 License: MIT Imports: 16 Imported by: 0

README

doublebrace - additional functions for Go templates

The default functions in text/template and html/template are minimal. This extends them.

Go Reference Build Status

Quick Start

Requires Go >= 1.23.

import (
	"github.com/client9/doublebrace"
)

t := template.New("foo").Funcs(doublebrace.FuncMap())

What's Included?

  • strings — case, trim, search, replace, split/join, truncate, rune-aware length
  • math — arithmetic, rounding, min/max, clamp, pow
  • cast — toInt, toFloat for frontmatter values that arrive as strings
  • encoding — jsonify for embedding data in <script> blocks
  • date and time — now, parseTime; use time.Time methods for formatting
  • url / safe types — urlEncode, urlPathEscape, safeHTML, safeCSS, etc.
  • path — pathBase, pathDir, pathExt, pathJoin, pathClean
  • lists (slices) — list, seq, take, drop, sort, sortNum, reverse, concat, …
  • dicts (maps) — dict, keys, values, merge

Goals

  • Independent and exportable — serves as a base, or for use in different templating systems
  • Stdlib only — keep it simple; functions requiring external deps go in a different module
  • Not pipeline-based — pipeline order looks elegant for single-argument functions, then gets confusing. Argument order follows Go stdlib (subject first).
  • Prefer separate functions over extra argumentssort and sortNum instead of a mode flag
  • Immutable data structures — all functions return new values, never modify inputs

Alternatives

Masterminds/sprig — appears semi-abandoned, pipeline-based, has a number of unusual functions and dependencies.

Hugo — the static site generator has many functions, but inconsistent design and argument order optimized for pipelines. Implementation is tightly coupled to Hugo internals.

Not Included

  • Internationalization / titlecase — requires golang.org/x/text; good for a separate module. Note: all string operations here are rune-aware.
  • Regular expressions — defer until use cases in templates are better understood
  • Base64 encoding — two competing encodings (standard vs URL-safe); add when use case is clear
  • Random / shuffle — non-deterministic output is problematic for static site generators.
  • Checksum and hashes — limited uses, many variations; good for a separate module
  • Cryptography — limited use, many variations,
  • OS and environment — pass these as data to the template instead
  • Math trig — limited utility in HTML templates

Documentation

Overview

Package doublebrace provides a stdlib-only template.FuncMap for use with Go's text/template and html/template packages.

All functions follow Go stdlib argument order: the primary value is the first argument. This matches direct Go calls and avoids pipeline-optimized argument order confusion. Single-argument functions work naturally in pipelines regardless.

Usage

import "github.com/client9/doublebrace"

t := template.New("page").Funcs(doublebrace.FuncMap())

To combine with your own functions:

fns := doublebrace.Merge(doublebrace.FuncMap(), template.FuncMap{
    "myFunc": myFunc,
})
t := template.New("page").Funcs(fns)

Strings

  • lower(s) string — convert to lowercase
  • upper(s) string — convert to uppercase
  • trim(s) string — remove leading and trailing whitespace
  • trimPrefix(s, prefix) string — remove prefix if present
  • trimSuffix(s, suffix) string — remove suffix if present
  • trimLeft(s, cutset) string — remove leading characters contained in cutset
  • trimRight(s, cutset) string — remove trailing characters contained in cutset
  • contains(s, substr) bool — report whether substr is within s
  • hasPrefix(s, prefix) bool — report whether s begins with prefix
  • hasSuffix(s, suffix) bool — report whether s ends with suffix
  • count(s, substr) int — count non-overlapping instances of substr in s; "" counts runes+1
  • replace(s, old, new [, n]) string — replace first occurrence of old with new; optional n sets limit (-1 replaces all)
  • replaceAll(s, old, new) string — replace all occurrences of old with new
  • repeat(s, n) string — return n copies of s concatenated
  • split(s, sep) []string — split s into substrings separated by sep
  • join(elems, sep) string — join elements with sep; elems must be []string
  • fields(s) []string — split s on whitespace, discarding empty strings
  • lenRunes(s) int — number of runes in s; unlike built-in len which counts bytes
  • truncate(s, n) string — shorten to at most n runes; appends "…" if truncated
  • firstUpper(s) string — uppercase first rune only; all other characters unchanged

Math

All math functions accept any numeric type or numeric string as input. Results are float64; use printf for integer formatting.

  • add(a, b) float64 — a + b
  • sub(a, b) float64 — a - b
  • mul(a, b) float64 — a * b
  • div(a, b) float64 — a / b; error on zero divisor
  • mod(a, b) float64 — floating-point remainder of a/b; error on zero divisor
  • modBool(a, b) bool — report whether a is evenly divisible by b; useful for alternating rows
  • abs(a) float64 — absolute value
  • ceil(a) float64 — least integer value ≥ a
  • floor(a) float64 — greatest integer value ≤ a
  • round(a) float64 — nearest integer, rounding half away from zero
  • pow(base, exp) float64 — base raised to exp
  • min(args...) float64 — minimum value; accepts scalars, slices, or a mix
  • max(args...) float64 — maximum value; accepts scalars, slices, or a mix
  • clamp(val, min, max) float64 — constrain val to [min, max]

Encoding

  • jsonify(v) string — marshal v to JSON; useful for <script> data blocks

Cast

Cast functions convert values between types. Useful when frontmatter values arrive as strings and numeric operations are needed.

  • toInt(v) int — convert to int; floats are truncated toward zero
  • toFloat(v) float64 — convert to float64

Time

Time functions return time.Time values. Use Go's time.Time methods directly in templates for formatting and field access: {{.Date.Format "2006-01-02"}}, {{.Date.Year}}, {{.Date.Weekday}}, etc.

  • now() time.Time — current local time; use {{now.UTC}} for UTC
  • parseTime(layout, s) time.Time — parse s using Go reference-time layout

Path

Path functions use forward slashes regardless of OS (suitable for URLs). They wrap the stdlib path package, not filepath.

  • pathBase(p) string — last element of p; "foo/bar.html" → "bar.html"
  • pathDir(p) string — all but last element; "foo/bar.html" → "foo"
  • pathExt(p) string — file extension including dot; "bar.html" → ".html"
  • pathJoin(elems...) string — join elements and clean the result
  • pathClean(p) string — normalize: resolve . and .., remove double slashes

Safe Types

These functions wrap string values in html/template typed aliases, preventing the template engine from escaping content that has already been sanitized. All accept string, []byte, any html/template typed value, or any type via fmt.Sprint. nil is an error.

  • safeCSS(s) template.CSS — mark s safe for style attributes and <style> blocks
  • safeHTML(s) template.HTML — mark s safe to render as raw HTML without escaping
  • safeHTMLAttr(s) template.HTMLAttr — mark s safe as an HTML attribute name/value pair
  • safeJS(s) template.JS — mark s safe for use inside <script> blocks
  • safeJSStr(s) template.JSStr — mark s safe for interpolation inside JS string literals
  • safeURL(s) template.URL — mark s safe for use in href/src/action attributes

URL Encoding

  • urlEncode(s) string — percent-encode for query strings; spaces become +
  • urlPathEscape(s) string — percent-encode a single path segment; / is encoded too

Collections — Constructors

  • list(elems...) []any — create a slice from values: list "a" "b" "c"
  • dict(k, v, ...) map[string]any — create a map from key-value pairs: dict "name" "Alice"
  • seq(n) []int — integers 1..n (1-based)
  • seq(start, end) []int — integers start..end inclusive
  • seq(start, end, step) []int — with step; negative step counts down

Collections — Sequence Access

These functions operate on any slice type or string. String operations are rune-aware: multi-byte characters are never split.

  • first(v) any — first element of a slice, or first rune of a string
  • last(v) any — last element of a slice, or last rune of a string
  • take(v, n) any — first n elements of a slice, or first n runes of a string; negative n takes from the end
  • drop(v, n) any — skip first n elements of a slice, or first n runes of a string; negative n removes from the end

Collections — Sequence Transformation

  • reverse(v) []any — new slice in reverse order
  • compact(v) []any — remove consecutive duplicate elements; for full dedup: compact (sort $list)
  • concat(slices...) []any — concatenate multiple slices into one
  • sort(v [, key]) []any — type-aware sort: numeric types sort numerically, time.Time sorts chronologically, everything else sorts lexicographically; for []any the first non-nil element determines mode; key names a field for slice-of-maps (always lexicographic)
  • sortNum(v [, key]) []any — numeric sort via float64 conversion; key names a field for slice-of-maps
  • where(v, key, val) []any — filter slice of map[string]any where element[key] == val

For descending order compose with reverse: reverse (sort $pages "Title")

ISO 8601 date strings ("2006-01-02") sort correctly with lexicographic order.

Collections — Map Operations

  • keys(m) []string — sorted keys of a map[string]any
  • values(m) []any — values of a map[string]any ordered by sorted keys
  • merge(maps...) map[string]any — shallow merge; later maps win on key collision

Collections — General

  • in(v, val) bool — membership test: slice (element), map (key existence), string (substring)
  • default(def, val) any — return val if non-zero, else def; zero: nil, false, 0, "", empty slice/map
  • cond(ctrl, a, b) any — ternary: return a if ctrl is truthy, else b

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func Abs

func Abs(a any) (float64, error)

Abs returns the absolute value of a.

abs -7 → 7
abs  3 → 3
Example
package main

import (
	"fmt"

	"github.com/client9/doublebrace"
)

func main() {
	v, _ := doublebrace.Abs(-7)
	fmt.Println(v)
}
Output:
7

func Add

func Add(a, b any) (float64, error)

Add returns a + b. Both arguments accept any numeric type or numeric string.

add 3 4   → 7
add 1.5 2 → 3.5
Example
package main

import (
	"fmt"

	"github.com/client9/doublebrace"
)

func main() {
	v, _ := doublebrace.Add(3, 4)
	fmt.Println(v)
}
Output:
7

func Ceil

func Ceil(a any) (float64, error)

Ceil returns the least integer value greater than or equal to a.

ceil 1.2 → 2
ceil 2.0 → 2
Example
package main

import (
	"fmt"

	"github.com/client9/doublebrace"
)

func main() {
	v, _ := doublebrace.Ceil(1.2)
	fmt.Println(v)
}
Output:
2

func Clamp

func Clamp(val, minVal, maxVal any) (float64, error)

Clamp constrains val to the range [min, max]. If val < min, min is returned; if val > max, max is returned; otherwise val is returned unchanged. All arguments accept any numeric type or numeric string.

clamp 5 1 10  → 5
clamp 0 1 10  → 1
clamp 15 1 10 → 10
Example
package main

import (
	"fmt"

	"github.com/client9/doublebrace"
)

func main() {
	within, _ := doublebrace.Clamp(5, 1, 10)
	below, _ := doublebrace.Clamp(0, 1, 10)
	above, _ := doublebrace.Clamp(15, 1, 10)
	fmt.Println(within, below, above)
}
Output:
5 1 10

func Compact

func Compact(v any) (any, error)

Compact removes consecutive duplicate elements, identical to slices.Compact semantics. For full deduplication use: compact (sort $list)

compact []int{1, 1, 2, 3, 3, 1} → []any{1, 2, 3, 1}
Example
package main

import (
	"fmt"

	"github.com/client9/doublebrace"
)

func main() {
	v, _ := doublebrace.Compact([]any{1, 1, 2, 3, 3, 1})
	fmt.Println(v)
}
Output:
[1 2 3 1]

func Concat

func Concat(ins ...any) ([]any, error)

Concat concatenates multiple slices into a single []any.

concat (list 1 2) (list 3 4) → []any{1, 2, 3, 4}
Example
package main

import (
	"fmt"

	"github.com/client9/doublebrace"
)

func main() {
	v, _ := doublebrace.Concat([]int{1, 2}, []int{3, 4})
	fmt.Println(v)
}
Output:
[1 2 3 4]

func Cond

func Cond(ctrl, a, b any) any

Cond returns a if ctrl is truthy (non-zero), otherwise b.

cond true  "yes" "no" → "yes"
cond false "yes" "no" → "no"
cond ""    "yes" "no" → "no"
cond 1     "yes" "no" → "yes"
Example
package main

import (
	"fmt"

	"github.com/client9/doublebrace"
)

func main() {
	fmt.Println(doublebrace.Cond(true, "yes", "no"))
	fmt.Println(doublebrace.Cond(false, "yes", "no"))
	fmt.Println(doublebrace.Cond(0, "yes", "no"))
}
Output:
yes
no
no

func Default

func Default(def, val any) any

Default returns val if it is non-zero, otherwise def. Zero values: nil, false, 0, "", and empty slices/maps.

default "anon" ""      → "anon"
default "anon" "Alice" → "Alice"
default 0 42           → 42
Example
package main

import (
	"fmt"

	"github.com/client9/doublebrace"
)

func main() {
	fmt.Println(doublebrace.Default("anon", ""))
	fmt.Println(doublebrace.Default("anon", "Alice"))
}
Output:
anon
Alice

func Dict

func Dict(kvs ...any) (map[string]any, error)

Dict creates a map[string]any from alternating key-value arguments. Returns an error if the argument count is odd or a key is not a string.

dict "name" "Alice" "age" 30 → map[string]any{"name": "Alice", "age": 30}
Example
package main

import (
	"fmt"

	"github.com/client9/doublebrace"
)

func main() {
	m, _ := doublebrace.Dict("name", "Alice", "age", 30)
	fmt.Println(m["name"], m["age"])
}
Output:
Alice 30

func Div

func Div(a, b any) (float64, error)

Div returns a / b. Returns an error on division by zero. Both arguments accept any numeric type or numeric string.

div 10 4 → 2.5
Example
package main

import (
	"fmt"

	"github.com/client9/doublebrace"
)

func main() {
	v, _ := doublebrace.Div(10, 4)
	fmt.Println(v)
}
Output:
2.5

func Drop

func Drop(v any, n int) (any, error)

Drop skips the first n elements of a slice, or the first n runes of a string. If n is negative, it removes the last |n| elements or runes. If |n| exceeds the length an empty result is returned. Rune-aware for strings: multi-byte characters are not split.

drop []int{1, 2, 3, 4, 5} 2  → []any{3, 4, 5}
drop []int{1, 2, 3, 4, 5} -2 → []any{1, 2, 3}
drop "日本語" 1                → "本語"
drop "日本語" -1               → "日本"
Example
package main

import (
	"fmt"

	"github.com/client9/doublebrace"
)

func main() {
	v, _ := doublebrace.Drop([]int{1, 2, 3, 4, 5}, 2)
	fmt.Println(v)
}
Output:
[3 4 5]
Example (Negative)
package main

import (
	"fmt"

	"github.com/client9/doublebrace"
)

func main() {
	v, _ := doublebrace.Drop([]int{1, 2, 3, 4, 5}, -2)
	fmt.Println(v)
}
Output:
[1 2 3]
Example (String)
package main

import (
	"fmt"

	"github.com/client9/doublebrace"
)

func main() {
	v, _ := doublebrace.Drop("hello", 2)
	fmt.Println(v)
}
Output:
llo

func First

func First(v any) (any, error)

First returns the first element of a slice, or the first rune of a string.

first []int{1, 2, 3} → 1
first "café"         → "c"
Example
package main

import (
	"fmt"

	"github.com/client9/doublebrace"
)

func main() {
	v, _ := doublebrace.First([]string{"a", "b", "c"})
	fmt.Println(v)
}
Output:
a
Example (String)
package main

import (
	"fmt"

	"github.com/client9/doublebrace"
)

func main() {
	v, _ := doublebrace.First("café")
	fmt.Println(v)
}
Output:
c

func FirstUpper

func FirstUpper(s string) string

FirstUpper returns s with the first rune converted to its Unicode title case. All other characters are left unchanged. It is rune-safe: multi-byte leading characters such as "é" are handled correctly.

firstUpper "go"              → "Go"
firstUpper "hello world"    → "Hello world"
firstUpper "élan"           → "Élan"
Example
package main

import (
	"fmt"

	"github.com/client9/doublebrace"
)

func main() {
	fmt.Println(doublebrace.FirstUpper("go"))
	fmt.Println(doublebrace.FirstUpper("hello world"))
	fmt.Println(doublebrace.FirstUpper("élan"))
}
Output:
Go
Hello world
Élan

func Floor

func Floor(a any) (float64, error)

Floor returns the greatest integer value less than or equal to a.

floor 1.9 → 1
floor 2.0 → 2
Example
package main

import (
	"fmt"

	"github.com/client9/doublebrace"
)

func main() {
	v, _ := doublebrace.Floor(1.9)
	fmt.Println(v)
}
Output:
1

func FuncMap

func FuncMap() template.FuncMap

FuncMap returns a template.FuncMap containing all standard functions. See the package documentation for the full list.

Example
package main

import (
	"html/template"

	"github.com/client9/doublebrace"
)

func main() {
	fm := doublebrace.FuncMap()
	t := template.Must(template.New("").Funcs(fm).Parse(`{{upper "hello"}}`))
	_ = t.Execute(nil, nil)
	// (FuncMap registers all template functions; use with template.New().Funcs())
}

func In

func In(v, val any) (bool, error)

In reports whether val is present in v.

  • slice: element membership via reflect.DeepEqual

  • map[string]any: key existence (val must be string)

  • string: substring search (val must be string)

    in (list "a" "b" "c") "b" → true in (dict "x" 1) "x" → true in "hello world" "world" → true

Example (Map)
package main

import (
	"fmt"

	"github.com/client9/doublebrace"
)

func main() {
	ok, _ := doublebrace.In(map[string]any{"x": 1}, "x")
	fmt.Println(ok)
}
Output:
true
Example (Slice)
package main

import (
	"fmt"

	"github.com/client9/doublebrace"
)

func main() {
	ok, _ := doublebrace.In([]string{"a", "b", "c"}, "b")
	fmt.Println(ok)
}
Output:
true
Example (String)
package main

import (
	"fmt"

	"github.com/client9/doublebrace"
)

func main() {
	ok, _ := doublebrace.In("hello world", "world")
	fmt.Println(ok)
}
Output:
true

func Jsonify

func Jsonify(v any) (string, error)

Jsonify marshals v to a JSON string. Useful for embedding data in <script> blocks or data attributes.

jsonify (dict "name" "Alice" "age" 30) → {"age":30,"name":"Alice"}
Example
package main

import (
	"fmt"

	"github.com/client9/doublebrace"
)

func main() {
	v, _ := doublebrace.Jsonify(map[string]any{"name": "Alice", "age": 30})
	fmt.Println(v)
}
Output:
{"age":30,"name":"Alice"}
Example (Slice)
package main

import (
	"fmt"

	"github.com/client9/doublebrace"
)

func main() {
	v, _ := doublebrace.Jsonify([]string{"a", "b", "c"})
	fmt.Println(v)
}
Output:
["a","b","c"]

func Keys

func Keys(v any) ([]string, error)

Keys returns the keys of a map[string]any in sorted order.

keys map[string]any{"b": 2, "a": 1} → ["a" "b"]
Example
package main

import (
	"fmt"

	"github.com/client9/doublebrace"
)

func main() {
	m := map[string]any{"b": 2, "a": 1, "c": 3}
	k, _ := doublebrace.Keys(m)
	fmt.Println(k)
}
Output:
[a b c]

func Last

func Last(v any) (any, error)

Last returns the last element of a slice, or the last rune of a string.

last []int{1, 2, 3} → 3
last "café"         → "é"
Example
package main

import (
	"fmt"

	"github.com/client9/doublebrace"
)

func main() {
	v, _ := doublebrace.Last([]string{"a", "b", "c"})
	fmt.Println(v)
}
Output:
c
Example (String)
package main

import (
	"fmt"

	"github.com/client9/doublebrace"
)

func main() {
	v, _ := doublebrace.Last("café")
	fmt.Println(v)
}
Output:
é

func LenRunes

func LenRunes(s string) int

LenRunes returns the number of runes in s. Unlike the built-in len, which counts bytes, LenRunes counts characters — so multi-byte characters such as "é" or "日" each count as one.

lenRunes "café" → 4
lenRunes "日本語" → 3
Example
package main

import (
	"fmt"

	"github.com/client9/doublebrace"
)

func main() {
	fmt.Println(doublebrace.LenRunes("café"))
	fmt.Println(doublebrace.LenRunes("日本語"))
}
Output:
4
3

func List

func List(elems ...any) []any

List creates a []any from the given values.

list "a" "b" "c" → []any{"a", "b", "c"}
Example
package main

import (
	"fmt"

	"github.com/client9/doublebrace"
)

func main() {
	s := doublebrace.List("a", "b", "c")
	fmt.Println(s)
}
Output:
[a b c]

func Max

func Max(args ...any) (float64, error)

Max returns the largest value among the given numbers. Accepts one or more scalars, slices, or a mix; slices are flattened recursively.

Max(3, 1, 2)              → 3
Max([]int{5, 2, 8})       → 8
Max([]int{5, 2}, 9, 1)    → 9
Example
package main

import (
	"fmt"

	"github.com/client9/doublebrace"
)

func main() {
	v, _ := doublebrace.Max(3, 1, 4, 1, 5, 9)
	fmt.Println(v)
}
Output:
9

func Merge

func Merge(fms ...template.FuncMap) template.FuncMap

Merge combines multiple template.FuncMaps into one new map. Later maps win on key collision, so user-defined functions override defaults:

fns := funcs.Merge(funcs.FuncMap(), template.FuncMap{"myFunc": myFunc})
Example
package main

import (
	"html/template"

	"github.com/client9/doublebrace"
)

func main() {
	custom := template.FuncMap{
		"greet": func(name string) string { return "Hello, " + name + "!" },
	}
	fm := doublebrace.Merge(doublebrace.FuncMap(), custom)
	t := template.Must(template.New("").Funcs(fm).Parse(`{{greet "World"}}`))
	_ = t.Execute(nil, nil)
	// (Merge combines FuncMaps; later maps win on key collision)
}

func MergeMaps

func MergeMaps(mapsIn ...any) (map[string]any, error)

MergeMaps combines map[string]any maps into a new map. Later maps win on key collision. Registered as "merge" in the template FuncMap.

merge (dict "a" 1 "b" 2) (dict "b" 99 "c" 3) → {"a":1, "b":99, "c":3}
Example
package main

import (
	"fmt"

	"github.com/client9/doublebrace"
)

func main() {
	a := map[string]any{"x": 1, "y": 2}
	b := map[string]any{"y": 99, "z": 3}
	m, _ := doublebrace.MergeMaps(a, b)
	fmt.Println(m["x"], m["y"], m["z"])
}
Output:
1 99 3

func Min

func Min(args ...any) (float64, error)

Min returns the smallest value among the given numbers. Accepts one or more scalars, slices, or a mix; slices are flattened recursively.

Min(3, 1, 2)              → 1
Min([]int{5, 2, 8})       → 2
Min([]int{5, 2}, 1, 9)    → 1
Example
package main

import (
	"fmt"

	"github.com/client9/doublebrace"
)

func main() {
	v, _ := doublebrace.Min(3, 1, 4, 1, 5, 9)
	fmt.Println(v)
}
Output:
1
Example (Slice)
package main

import (
	"fmt"

	"github.com/client9/doublebrace"
)

func main() {
	v, _ := doublebrace.Min([]int{7, 2, 8})
	fmt.Println(v)
}
Output:
2

func Mod

func Mod(a, b any) (float64, error)

Mod returns the floating-point remainder of a / b (math.Mod). Returns an error on division by zero. Both arguments accept any numeric type or numeric string.

mod 10 3 → 1
Example
package main

import (
	"fmt"

	"github.com/client9/doublebrace"
)

func main() {
	v, _ := doublebrace.Mod(10, 3)
	fmt.Println(v)
}
Output:
1

func ModBool

func ModBool(a, b any) (bool, error)

ModBool reports whether a is evenly divisible by b (a mod b == 0). Useful for alternating row styles: {{if modBool $i 2}}even{{end}}

ModBool(4, 2) → true
ModBool(5, 2) → false
Example
package main

import (
	"fmt"

	"github.com/client9/doublebrace"
)

func main() {
	even, _ := doublebrace.ModBool(4, 2)
	odd, _ := doublebrace.ModBool(5, 2)
	fmt.Println(even, odd)
}
Output:
true false

func Mul

func Mul(a, b any) (float64, error)

Mul returns a * b. Both arguments accept any numeric type or numeric string.

mul 3 4 → 12
Example
package main

import (
	"fmt"

	"github.com/client9/doublebrace"
)

func main() {
	v, _ := doublebrace.Mul(3, 4)
	fmt.Println(v)
}
Output:
12

func Now

func Now() time.Time

Now returns the current local time. The returned time.Time value supports method calls in templates: {{now.Year}}, {{now.Format "2006-01-02"}}, etc.

Example
package main

import (
	"github.com/client9/doublebrace"
)

func main() {
	t := doublebrace.Now()
	_ = t
	// Returns current local time as time.Time.
	// In templates: {{now.Year}}, {{now.Format "2006-01-02"}}
}

func ParseTime

func ParseTime(layout, s string) (time.Time, error)

ParseTime parses a formatted string and returns the time.Time value it represents. The layout defines the format using Go's reference time: Mon Jan 2 15:04:05 MST 2006.

parseTime "2006-01-02" "2024-03-15" → 2024-03-15 00:00:00 +0000 UTC
Example
package main

import (
	"fmt"

	"github.com/client9/doublebrace"
)

func main() {
	t, _ := doublebrace.ParseTime("2006-01-02", "2024-03-15")
	fmt.Println(t.Format("January 2, 2006"))
}
Output:
March 15, 2024

func PathBase

func PathBase(p string) string

PathBase returns the last element of a slash-separated path. Trailing slashes are removed before extracting the last element.

pathBase "/a/b/c.html" → "c.html"
pathBase "/a/b/"       → "b"
Example
package main

import (
	"fmt"

	"github.com/client9/doublebrace"
)

func main() {
	fmt.Println(doublebrace.PathBase("/a/b/c.html"))
	fmt.Println(doublebrace.PathBase("/a/b/"))
}
Output:
c.html
b

func PathClean

func PathClean(p string) string

PathClean returns the shortest equivalent path by applying lexical rules.

pathClean "/a/b/../c" → "/a/c"
pathClean "a//b"      → "a/b"
Example
package main

import (
	"fmt"

	"github.com/client9/doublebrace"
)

func main() {
	fmt.Println(doublebrace.PathClean("/a/b/../c"))
	fmt.Println(doublebrace.PathClean("a//b"))
}
Output:
/a/c
a/b

func PathDir

func PathDir(p string) string

PathDir returns all but the last element of a slash-separated path.

pathDir "/a/b/c.html" → "/a/b"
pathDir "/a/b/"       → "/a/b"
Example
package main

import (
	"fmt"

	"github.com/client9/doublebrace"
)

func main() {
	fmt.Println(doublebrace.PathDir("/a/b/c.html"))
}
Output:
/a/b

func PathExt

func PathExt(p string) string

PathExt returns the file extension of the last element of a path, including the leading dot. Returns "" if there is no extension.

pathExt "index.html" → ".html"
pathExt "Makefile"   → ""
Example
package main

import (
	"fmt"

	"github.com/client9/doublebrace"
)

func main() {
	fmt.Println(doublebrace.PathExt("index.html"))
	fmt.Println(doublebrace.PathExt("Makefile"))
}
Output:
.html

func PathJoin

func PathJoin(elems ...string) string

PathJoin joins path elements with slashes and cleans the result.

pathJoin "/a" "b" "c.html" → "/a/b/c.html"
Example
package main

import (
	"fmt"

	"github.com/client9/doublebrace"
)

func main() {
	fmt.Println(doublebrace.PathJoin("/a", "b", "c.html"))
}
Output:
/a/b/c.html

func Pow

func Pow(base, exp any) (float64, error)

Pow returns base raised to the power of exp.

Pow(2, 10) → 1024
Pow(9, 0.5) → 3  (square root)
Example
package main

import (
	"fmt"

	"github.com/client9/doublebrace"
)

func main() {
	v, _ := doublebrace.Pow(2, 10)
	fmt.Println(v)
}
Output:
1024
Example (Sqrt)
package main

import (
	"fmt"

	"github.com/client9/doublebrace"
)

func main() {
	v, _ := doublebrace.Pow(9, 0.5)
	fmt.Println(v)
}
Output:
3

func Replace

func Replace(s, old, new string, n ...int) string

Replace returns a copy of s with occurrences of old replaced by new. The optional n argument limits the number of replacements; if omitted, only the first occurrence is replaced. Use replaceAll to replace all.

replace "aabbaa" "a" "x"     → "xabbaa"
replace "aabbaa" "a" "x" 3  → "xxbbxa"
replace "aabbaa" "a" "x" -1 → "xxbbxx"
Example
package main

import (
	"fmt"

	"github.com/client9/doublebrace"
)

func main() {
	fmt.Println(doublebrace.Replace("aabbaa", "a", "x"))
}
Output:
xabbaa
Example (Count)
package main

import (
	"fmt"

	"github.com/client9/doublebrace"
)

func main() {
	fmt.Println(doublebrace.Replace("aabbaa", "a", "x", -1))
}
Output:
xxbbxx

func Reverse

func Reverse(v any) (any, error)

Reverse returns a new slice with the elements in reverse order.

reverse []int{1, 2, 3} → []any{3, 2, 1}
Example
package main

import (
	"fmt"

	"github.com/client9/doublebrace"
)

func main() {
	v, _ := doublebrace.Reverse([]int{1, 2, 3})
	fmt.Println(v)
}
Output:
[3 2 1]

func Round

func Round(a any) (float64, error)

Round returns the nearest integer, rounding half away from zero.

round 1.4 → 1
round 1.5 → 2
Example
package main

import (
	"fmt"

	"github.com/client9/doublebrace"
)

func main() {
	lo, _ := doublebrace.Round(1.4)
	hi, _ := doublebrace.Round(1.5)
	fmt.Println(lo, hi)
}
Output:
1 2

func SafeCSS

func SafeCSS(s any) (template.CSS, error)

SafeCSS converts s to template.CSS, marking it safe for use in style attributes and <style> blocks without escaping.

Example
package main

import (
	"fmt"

	"github.com/client9/doublebrace"
)

func main() {
	v, _ := doublebrace.SafeCSS("color: red")
	fmt.Println(v)
}
Output:
color: red

func SafeHTML

func SafeHTML(s any) (template.HTML, error)

SafeHTML converts s to template.HTML, marking it safe to render as raw HTML without escaping. Use only with trusted content.

Example
package main

import (
	"fmt"

	"github.com/client9/doublebrace"
)

func main() {
	v, _ := doublebrace.SafeHTML("<b>bold</b>")
	fmt.Println(v)
}
Output:
<b>bold</b>

func SafeHTMLAttr

func SafeHTMLAttr(s any) (template.HTMLAttr, error)

SafeHTMLAttr converts s to template.HTMLAttr, marking it safe for use as an HTML attribute (name and value pair) without escaping.

Example
package main

import (
	"fmt"

	"github.com/client9/doublebrace"
)

func main() {
	v, _ := doublebrace.SafeHTMLAttr(`class="hero"`)
	fmt.Println(v)
}
Output:
class="hero"

func SafeJS

func SafeJS(s any) (template.JS, error)

SafeJS converts s to template.JS, marking it safe for use inside <script> blocks without escaping.

Example
package main

import (
	"fmt"

	"github.com/client9/doublebrace"
)

func main() {
	v, _ := doublebrace.SafeJS("alert('hi')")
	fmt.Println(v)
}
Output:
alert('hi')

func SafeJSStr

func SafeJSStr(s any) (template.JSStr, error)

SafeJSStr converts s to template.JSStr, marking it safe for interpolation inside a JavaScript string literal without escaping.

Example
package main

import (
	"fmt"

	"github.com/client9/doublebrace"
)

func main() {
	v, _ := doublebrace.SafeJSStr(`hello\nworld`)
	fmt.Println(v)
}
Output:
hello\nworld

func SafeURL

func SafeURL(s any) (template.URL, error)

SafeURL converts s to template.URL, marking it safe for use in URL attributes (href, src, action, etc.) without escaping.

Example
package main

import (
	"fmt"

	"github.com/client9/doublebrace"
)

func main() {
	v, _ := doublebrace.SafeURL("https://example.com/path?q=1")
	fmt.Println(v)
}
Output:
https://example.com/path?q=1

func Seq

func Seq(args ...int) ([]int, error)

Seq returns a slice of integers. Counting is 1-based by default.

seq 5        → [1 2 3 4 5]
seq 3 7      → [3 4 5 6 7]
seq 1 10 2   → [1 3 5 7 9]
seq 5 1 -1   → [5 4 3 2 1]
Example
package main

import (
	"fmt"

	"github.com/client9/doublebrace"
)

func main() {
	s, _ := doublebrace.Seq(5)
	fmt.Println(s)
}
Output:
[1 2 3 4 5]
Example (Range)
package main

import (
	"fmt"

	"github.com/client9/doublebrace"
)

func main() {
	s, _ := doublebrace.Seq(3, 7)
	fmt.Println(s)
}
Output:
[3 4 5 6 7]
Example (Step)
package main

import (
	"fmt"

	"github.com/client9/doublebrace"
)

func main() {
	s, _ := doublebrace.Seq(1, 9, 2)
	fmt.Println(s)
}
Output:
[1 3 5 7 9]

func Sort

func Sort(v any, key ...string) (any, error)

Sort returns a new slice sorted by type:

  • numeric types (int, float64, etc.) sort numerically
  • time.Time values sort chronologically
  • everything else sorts lexicographically (string comparison)

For []any, the first non-nil element determines the sort mode. An optional key names a field for slice-of-maps sorting (always lexicographic). For descending order, compose with reverse. ISO 8601 date strings ("2006-01-02") sort correctly lexicographically.

sort (list "banana" "apple" "cherry") → ["apple" "banana" "cherry"]
sort (list 10 2 30)                   → [2 10 30]
sort $pages "Title"                   → pages A→Z by Title field
Example
package main

import (
	"fmt"

	"github.com/client9/doublebrace"
)

func main() {
	v, _ := doublebrace.Sort([]string{"banana", "apple", "cherry"})
	fmt.Println(v)
}
Output:
[apple banana cherry]
Example (Numeric)
package main

import (
	"fmt"

	"github.com/client9/doublebrace"
)

func main() {
	v, _ := doublebrace.Sort([]int{10, 2, 30, 5})
	fmt.Println(v)
}
Output:
[2 5 10 30]
Example (Time)
package main

import (
	"fmt"
	"time"

	"github.com/client9/doublebrace"
)

func main() {
	t1 := time.Date(2024, 3, 1, 0, 0, 0, 0, time.UTC)
	t2 := time.Date(2023, 12, 31, 0, 0, 0, 0, time.UTC)
	v, _ := doublebrace.Sort([]time.Time{t1, t2})
	fmt.Println(v.([]any)[0].(time.Time).Format("2006-01-02"))
}
Output:
2023-12-31

func SortNum

func SortNum(v any, key ...string) (any, error)

SortNum returns a new slice sorted numerically using toFloat64 conversion. An optional key names a field for slice-of-maps sorting. For descending order, compose with reverse.

sortNum (list "10" "9" "2") → ["2" "9" "10"]
sortNum $pages "Year"       → pages sorted by Year field, ascending
Example
package main

import (
	"fmt"

	"github.com/client9/doublebrace"
)

func main() {
	v, _ := doublebrace.SortNum([]string{"10", "9", "2"})
	fmt.Println(v)
}
Output:
[2 9 10]

func Sub

func Sub(a, b any) (float64, error)

Sub returns a - b. Both arguments accept any numeric type or numeric string.

sub 10 3 → 7
Example
package main

import (
	"fmt"

	"github.com/client9/doublebrace"
)

func main() {
	v, _ := doublebrace.Sub(10, 3)
	fmt.Println(v)
}
Output:
7

func Take

func Take(v any, n int) (any, error)

Take returns the first n elements of a slice, or the first n runes of a string. If n is negative, it returns the last |n| elements or runes. If |n| exceeds the length the full input is returned. Rune-aware for strings: multi-byte characters are not split.

take []int{1, 2, 3, 4, 5} 3  → []any{1, 2, 3}
take []int{1, 2, 3, 4, 5} -2 → []any{4, 5}
take "日本語" 2                → "日本"
take "日本語" -1               → "語"
Example
package main

import (
	"fmt"

	"github.com/client9/doublebrace"
)

func main() {
	v, _ := doublebrace.Take([]int{1, 2, 3, 4, 5}, 3)
	fmt.Println(v)
}
Output:
[1 2 3]
Example (Negative)
package main

import (
	"fmt"

	"github.com/client9/doublebrace"
)

func main() {
	v, _ := doublebrace.Take([]int{1, 2, 3, 4, 5}, -2)
	fmt.Println(v)
}
Output:
[4 5]
Example (String)
package main

import (
	"fmt"

	"github.com/client9/doublebrace"
)

func main() {
	v, _ := doublebrace.Take("日本語", 2)
	fmt.Println(v)
}
Output:
日本

func ToFloat

func ToFloat(v any) (float64, error)

ToFloat converts v to float64. Numeric types are converted directly. Numeric strings are parsed with strconv.ParseFloat.

toFloat 42      → 42
toFloat "3.14"  → 3.14
Example
package main

import (
	"fmt"

	"github.com/client9/doublebrace"
)

func main() {
	v, _ := doublebrace.ToFloat("3.14")
	fmt.Println(v)
}
Output:
3.14

func ToInt

func ToInt(v any) (int, error)

ToInt converts v to int. Numeric types are converted directly (floats are truncated toward zero). Numeric strings are parsed with strconv.Atoi.

toInt 42        → 42
toInt 3.9       → 3
toInt "17"      → 17
Example
package main

import (
	"fmt"

	"github.com/client9/doublebrace"
)

func main() {
	v, _ := doublebrace.ToInt("42")
	fmt.Println(v)
}
Output:
42
Example (Float)
package main

import (
	"fmt"

	"github.com/client9/doublebrace"
)

func main() {
	v, _ := doublebrace.ToInt(3.9)
	fmt.Println(v)
}
Output:
3

func Truncate

func Truncate(s string, n int) string

Truncate shortens s to at most n runes. If s is longer it is cut and an ellipsis ("…") is appended. n includes the ellipsis, so the result is always at most n runes long.

truncate "hello world" 8 → "hello w…"
truncate "hi" 8          → "hi"
Example
package main

import (
	"fmt"

	"github.com/client9/doublebrace"
)

func main() {
	fmt.Println(doublebrace.Truncate("hello world", 8))
	fmt.Println(doublebrace.Truncate("hi", 8))
}
Output:
hello w…
hi

func URLEncode

func URLEncode(s string) string

URLEncode escapes a string so it can be safely placed in a URL query parameter, encoding spaces as "+" and special characters as "%XX".

urlEncode "hello world" → "hello+world"
urlEncode "a=1&b=2"    → "a%3D1%26b%3D2"
Example
package main

import (
	"fmt"

	"github.com/client9/doublebrace"
)

func main() {
	fmt.Println(doublebrace.URLEncode("hello world"))
	fmt.Println(doublebrace.URLEncode("a=1&b=2"))
}
Output:
hello+world
a%3D1%26b%3D2

func URLPathEscape

func URLPathEscape(s string) string

URLPathEscape escapes a string so it can be safely placed in a URL path segment, encoding spaces and reserved characters as "%XX" (spaces become "%20", not "+").

urlPathEscape "hello world" → "hello%20world"
urlPathEscape "a/b"         → "a%2Fb"
Example
package main

import (
	"fmt"

	"github.com/client9/doublebrace"
)

func main() {
	fmt.Println(doublebrace.URLPathEscape("hello world"))
	fmt.Println(doublebrace.URLPathEscape("a/b"))
}
Output:
hello%20world
a%2Fb

func Values

func Values(v any) ([]any, error)

Values returns the values of a map[string]any in key-sorted order.

values map[string]any{"b": 2, "a": 1} → [1 2]
Example
package main

import (
	"fmt"

	"github.com/client9/doublebrace"
)

func main() {
	m := map[string]any{"b": 2, "a": 1}
	v, _ := doublebrace.Values(m)
	fmt.Println(v)
}
Output:
[1 2]

func Where

func Where(v any, key string, val any) (any, error)

Where filters a slice of map[string]any by field equality. Only elements where element[key] == val are included in the result.

where $pages "Draft" false    → pages where Draft == false
where $pages "Section" "blog" → pages in the blog section
Example
package main

import (
	"fmt"

	"github.com/client9/doublebrace"
)

func main() {
	pages := []any{
		map[string]any{"Title": "Post A", "Draft": false},
		map[string]any{"Title": "Post B", "Draft": true},
		map[string]any{"Title": "Post C", "Draft": false},
	}
	v, _ := doublebrace.Where(pages, "Draft", false)
	fmt.Println(len(v.([]any)))
}
Output:
2

Types

This section is empty.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL