GO templates

09.04.2020 шаблоны html text исходники


GO templates

Пакеты text/template, html/template являются частью стандартной библиотеки GO. Шаблоны GO используются во многих решениях на базе языка, таких как docker, kubernetes, helm. С ними интегрированы также многие web-библиотеки, например echo. Понимание синтаксиса встроенного шаблонизатора GO полезно в очень многих задачах.

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

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

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

Вызовы шаблонизатора

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

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

Для запуска шаблона со стороны GO-кода у нас есть функции Execute и ExecuteTemplate. В них обоих есть параметр data interface{}.

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

Здесь данные, переданные через параметр data — те, с которыми шаблон работает по умолчанию. Из шаблона они доступны как .. Для вывода текущих данных достаточно следующего:

{{ . }}

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

Далее рассмотрим синтаксические компоненты шаблонизатора GO.

Комментарии

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

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

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

Если condition будет равен 0, "", nil, или пустым массивом, срезом, это будет воспринято как false. В остальных случаях — инструкция T1 выполнится.

Допустимы вариации с else, else if:

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

Цикл range

{{range somelist}} T1 {{end}}

Итерировать в цикле можно массивы, срезы, карты и каналы. На каждую итерацию будет вызвана инструкция T1, при этом контекст шаблона будет переключен на элемент конкретной итерации.

Также возможна вариация с else, который выполнится если somelist пуст:

{{range somelist}} T1 {{else}} T2 {{end}}

Можно получить ключи и значения каждого элемента в переменные:

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

Конструкция with

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

В данном случае к pipeline применяется проверка, будто бы мы используем if. Если значение pipeline будет эквивалентно true (cм описание if), инструкция T1 выполнится. При этом текущий контекст этой инструкции изменится на pipeline.

Шаблоны

Шаблон задать можно следующими двумя конструкциями:

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

Запустить шаблон c данными context:

{{template "name" context}}

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

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

.Field1.Field2.key1
.key1.key2

Можно использовать вызовы методов. Функция или метод для использования в шаблоне должны удовлетворять одному из условий:

  • возвращаемое значение функции или метода должно быть одно;
  • возвращаемых значений два, но второе — error;

В исходниках пакета text/template присуствует код проверки вызываемых функций/методов на соответствие этим условиям.

Следующий код на GO подготавливает метод для вызова в шаблоне:

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

Код шаблона вызывает метод:

.Method()

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

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

Вызов функции любого типа выглядит так:

funcname arg1 arg2 arg3

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

  • call funcLocation arg1 arg2 — встроенная функция call позволяет вызывать другие функции, например функции, хранящиеся в переменных или чье имя находится в переменной;
  • 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).Num()-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, например так работает получение поля структуры.

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

Другие статьи