Текстовые шаблоны в Go

06.10.2024 - Alexey Nurgaliev - Программирование - Go

Для шаблонов генерации текста есть два пакета: text/template и html/template. У них одинаковый интерфейс, разница только в том, что последний дополнительно экранирует подстановки для вставки в html-документ (чтобы избежать XSS-инъекций).

У каждого шаблона есть имя. Инициализировать шаблоны можно разными способами:

Выполнить шаблон можно при помощи Execute или ExecuteTemplate. Первый метод выполняет шаблон по умолчанию, второй - ищет нужный по имени. Результат выводится в io.Writer.

Шаблоны вычисляются относительно полей некоторой структуры (или ассоциативного массива). . - точка - текущее значение этой структуры (или массива) внутри шаблона.

Удалить пробелы слева или справа от подстановки: добавить - к скобкам:

{{- x }} удалить пробелы слева
{{ x -}} удалить пробелы справа
{{- x -}} удалить пробелы слева и справа

Стандартные действия:

{{/* комментарий */}}

{{ x }} - подстановка значения х

Условный оператор:
{{ if условие }}
{{ else if другое_условие }}
{{ else }}
{{ end }}

Цикл:
{{ range x }}
    Перебор значений х (массив, срез, ассоциативный массив, канал).
    Текущее значение становится . (точкой)
    {{ break }} - прервать выполнение ближайшего цикла
    {{ continue }} - перейти к следующей итерации ближайшего цикла
{{ else }}
    Выполнится, если в х нет элементов.
{{ end }}

Вставка других шаблонов:
{{ define "name" }}
    Определение шаблона с именем name
{{ end }}

{{ template "name" }} - подстановка шаблона name с пустой точкой
{{ template "name" x }} - подстановка шаблона name с точкой, равной х

{{ block "name" x }}
    Определение шаблона с именем name и вывод его сразу же с точкой, равной х
{{ end }}

Переопределение точки:
{{ with x }}
    Здесь точка равна x. Если х пустое значение, блок не выполняется
{{ else with y }}
    Выполнится, если y - не пустое значение, точка станет y.
{{ else }}
    Выполнится, если х и у - пустое значение
{{ end }}

Вызов функции:
{{ printf "%s" "hello, world" }} - параметры перечисляются через пробел

Передача результата по цепочке:
{{ "hello, world" | printf "%s" }} - результат выполнения предыдущей коменды подставится последним аргументом.

Переменные:

{{ $x := "abacaba" }} - присвоить значение переменной в первый раз
{{ $x = "abacaba" }} - повторно присвоить значение переменной

{{ range $index, $element := x }}
    Присвоить ключ (индекс) текущего элемента x в $index, а значение в $element.
    В отличие от go, если передать только одно имя переменной, в нее присвоится значение, а не индекс.
{{ end }}

{{ with $element := x }}
    Вместе с точкой присвоить значение х и переменной $element.
{{ end }}

Переменная действует до конца текущего блока.

Встроенные функции

Полный список здесь. Из интересного, можно выделить:

HTML

html/template не требует явного экранирования выводимых значений. Более того, он умеет распознавать, где выводится значение (HTML, CSS, URL) и применять соответствующее экранирование. Более подробно описано в документации.

https://masterminds.github.io/sprig/ - проект по расширению функциональности шаблонов. Добавляет ряд полезных функций.

Рекурсивный поиск шаблонов

func searchTemplates(rootPath, pattern string) ([]string, error) {
    var foundTemplates []string

    err := filepath.Walk(rootPath, func (path string, info fs.FileInfo, err error) error {
        if err != nil {
            return err
        }
        if info.IsDir() {
            return nil
        }
        base := filepath.Base(path)
        ok, err := filepath.Match(pattern, base)
        if err != nil {
            return err
        }
        if ok {
            foundTemplates = append(foundTemplates, path)
        }
        return nil
    })

    if err != nil {
        return nil, err
    }

    return foundTemplates, nil
}

Здесь нужно обратить внимание, что имя шаблона (для выполнения либо для включения) - это имя файла, без пути. Т.е. файл templates/includes/part.tmpl надо использовать как part.tmpl.

Использование функции:

templates, err := searchTemplates("templates", "*.tmpl")
if err != nil {
    return err
}

t, err := template.ParseFiles(templates...)
if err != nil {
    return err
}

if err := t.ExecuteTemplate(w, "name.tmpl", ctx); err != nil {
    return err
}
Лицензия Creative Commons
Code More Team - GitHub