9 апреля 2020 г. Templates Html Text Sources
Пакеты 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}}
Можно итерировать массивы, срезы, карты или каналы.
В следующем коде инструкция T1 будет выполняться на каждой итерации:
{{range pipeline}} T1 {{end}}
{{range pipeline}} T1 {{else}} T2 {{end}}
Переменные ключ/значение для каждой итерации также можно получить:
{{range $key, $value := pipeline}}
{{ $key }}: {{ $value }}
{{end}}
{{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 arg2 — arg1 == arg2ne arg1 arg2 — arg1 != arg2lt arg1 arg2 — arg1 < arg2le arg1 arg2 — arg1 <= arg2gt arg1 arg2 — arg1 > arg2ge arg1 arg2 — arg1 >= 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). На основе этой идентификации контента существуют различные решения экранирования.
В блоге Go описывается, как использовать срезы. Давайте посмотрим на внутреннее устройство срезов.
Read More → Slice Allocation SourcesВ Go у нас есть функциональность горутин из коробки. Мы можем запускать код параллельно. Однако в нашем параллельно выполняющемся коде мы можем работать с общими переменными, и не совсем понятно, как именно Go обрабатывает такие ситуации.
Read More → Map SourcesПрограммный интерфейс map в Go описан в блоге Go. Нам просто нужно вспомнить, что map — это хранилище ключ-значение, и оно должно извлекать значения по ключу как можно быстрее.
Read More → Map SourcesWhy PHP- and JavaScript-like regular expressions work with dot (".") work differently in GO.
Read More → Regular Expressions SourcesПочему регулярные выражения с точкой (".") работают по-другому в Go по сравнению с PHP и JavaScript.
Read More → Regular Expressions Sources