Шаблоны GO: принципы и использование

9 апреля 2020 г. Templates Html Text Sources


Шаблоны GO: принципы и использование

Пакеты text/template и html/template являются частью стандартной библиотеки Go. Шаблоны Go используются во многих программах, написанных на Go — Docker, Kubernetes, Helm. Многие сторонние библиотеки интегрированы с шаблонами Go, например Echo. Знание синтаксиса шаблонов Go очень полезно.

Эта статья состоит из документации пакета text/template и нескольких решений автора. После описания синтаксиса шаблонов Go мы погрузимся в исходники text/template и html/template.

Шаблоны Go являются активными, что означает, что здесь доступны инструкции управления потоком, такие как if, else и циклы range.

Go — строго типизированный язык, но шаблоны работают со всеми типами данных благодаря разработчикам пакета reflect.

Давайте кратко рассмотрим основы синтаксиса шаблонов Go.

Вызовы шаблонов

Все инструкции шаблона задаются между символами {{ и }}. Любой другой текст — это обычный текст, который просто выводится без изменений.

Контекст шаблона

У нас есть функции Go Execute и ExecuteTemplate для запуска шаблонов. Обе они имеют параметр data interface{}.

Execute(wr io.Writer, data interface{}) error
ExecuteTemplate(wr io.Writer, name string, data interface{}) error

Параметр data здесь — это данные шаблона по умолчанию. К ним можно получить доступ из шаблонов как .. Следующий код выводит данные по умолчанию:

{{ . }}

Назовём данные по умолчанию текущим контекстом шаблона. Некоторые инструкции шаблона могут изменять контекст шаблона.

Комментарии

{{/* comment */}}

Условный оператор

{{if condition}} T1 {{end}}

Если условие равно 0, "", nil или пустому массиву/срезу, условие обрабатывается как false, и T1 не выполнится. В противном случае T1 выполнится.

Вариации с else, else if:

{{if condition}} T1 {{else}} T0 {{end}}
{{if condition1}} T1 {{else if condition2}} T0 {{end}}

Циклы Range

Можно итерировать массивы, срезы, карты или каналы.

В следующем коде инструкция T1 будет выполняться на каждой итерации:

{{range pipeline}} T1 {{end}}
{{range pipeline}} T1 {{else}} T2 {{end}}

Переменные ключ/значение для каждой итерации также можно получить:

{{range $key, $value := pipeline}}
{{ $key }}: {{ $value }}
{{end}}

With

{{with pipeline}} T1 {{end}}

Если pipeline эквивалентен true (как описано в объяснении if), T1 выполняется, и текущий контекст шаблона устанавливается в pipeline.

Шаблоны

Можно создать шаблон, используя одну из следующих инструкций:

{{block "name" pipeline}} T1 {{end}}
{{define "name" pipeline}} T1 {{end}}

Для запуска шаблона:

{{template "name" pipeline}}

Элементы карты, поля структуры, вызовы методов

Шаблоны Go могут выводить данные. Например, можно вывести поле структуры или значение карты. Поля структуры, используемые в шаблонах, должны быть экспортированными (начинаться с заглавной буквы). Ключи карты могут начинаться со строчной буквы.

Все они могут быть объединены в цепочку:

.Field1.Field2.key1
.key1.key2

Можно использовать вызовы методов в шаблонах. Методы шаблона должны возвращать одно значение или два значения, и последнее значение должно быть ошибкой. В исходниках есть такая проверка метода.

Код Go:

type myType struct{}

func(m *myType) Method() string {
    return "123"
}

Код шаблона:

.Method()

Стандартные функции

В шаблонах Go есть два типа функций — встроенные и пользовательские.

Синтаксис вызова функции для каждой функции следующий:

funcname arg1 arg2 arg3

Список стандартных функций:

  • call funcLocation arg1 arg2 — функция для вызова функции с аргументами;
  • index x 1 2 3 — получение элемента среза/массива/карты;
  • slice x 1 2 — срез среза/массива — s[1:2];
  • len x — получение длины среза/массива/карты;
  • print, printf, println — явный вывод данных;

Булевы операторы также работают как функции:

  • eq arg1 arg2arg1 == arg2
  • ne arg1 arg2arg1 != arg2
  • lt arg1 arg2arg1 < arg2
  • le arg1 arg2arg1 <= arg2
  • gt arg1 arg2arg1 > arg2
  • ge arg1 arg2arg1 >= arg2

Значения можно объединять в цепочку с функцией с помощью оператора |. Такие значения становятся последним аргументом функции:

{{"output" | printf "%q"}}

Пользовательские функции

Можно определить любую функцию в Go для использования её позже в шаблонах.

Код Go функции last, которая помогает проверить, является ли итерируемый элемент последним в списке:

tempTemplate := template.New("main").Funcs(
    template.FuncMap{
        "last": func(x int, a interface{}) bool {
            return x == reflect.ValueOf(a).Len()-1
        },
    },
)

Использование last в шаблоне:

{{ $allKeywords := .Data.Keywords }}
{{ range $k,$v := .Data.Keywords}}
{{ $v }}{{ if ne (last $i $allKeywords) }},{{ end }}
{{ end }}

Внутреннее устройство

Шаблоны Go используют пакет reflect для работы с любыми типами данных. Например, исходники range в text/template:

func (s *state) walkRange(dot reflect.Value, r *parse.RangeNode) {
    // ...
    switch val.Kind() {
    case reflect.Array, reflect.Slice:
        // ...
    case reflect.Map:
        // ...
    case reflect.Chan:
        // ...
    }
}

Для каждого итерируемого типа есть логическая ветвь. Тот же подход с reflect используется в исходниках инструкции evalField.

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

Tags:

Похожие статьи

4 Apr 2020

Принципы работы типа slice в GO

Принципы работы типа slice в GO

В блоге Go описывается, как использовать срезы. Давайте посмотрим на внутреннее устройство срезов.

Read More → Slice Allocation Sources
2 Apr 2020

Обработка данных в конкурентных программах

Обработка данных в конкурентных программах

В Go у нас есть функциональность горутин из коробки. Мы можем запускать код параллельно. Однако в нашем параллельно выполняющемся коде мы можем работать с общими переменными, и не совсем понятно, как именно Go обрабатывает такие ситуации.

Read More → Map Sources
2 Apr 2020

Принципы работы типа map в GO

Принципы работы типа map в GO

Программный интерфейс map в Go описан в блоге Go. Нам просто нужно вспомнить, что map — это хранилище ключ-значение, и оно должно извлекать значения по ключу как можно быстрее.

Read More → Map Sources
30 Mar 2020

Golang regexp: matching newline

Why PHP- and JavaScript-like regular expressions work with dot (".") work differently in GO.

Read More → Regular Expressions Sources
30 Mar 2020

Golang regexp: сопоставление символа новой строки

Golang regexp: сопоставление символа новой строки

Почему регулярные выражения с точкой (".") работают по-другому в Go по сравнению с PHP и JavaScript.

Read More → Regular Expressions Sources