Go: введение в Jet

Сегодня я хотел бы рассказать о шаблонизаторе CloudyKit Jet, разработанным для использования в языке Go. При разработке к дизайну Jet были предъявлены следующие требования: использование шаблонизатора должно быть максимально простым, но при этом он должен обеспечивать высокую производительность. Чего и удалось допиться разработчикам.

В результате Jet имеет:

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

Для начала работы с Jet нужно его установить, что сделать достаточно просто


go get -u github.com/CloudyKit/jet

После чего импортируем библиотеку в проект


import "github.com/CloudyKit/jet"

Для начала создадим объект jet.Set, в который будут загружены все шаблоны из директории views


View := jet.NewHTMLSet("./views"))

Можно загрузить шаблоны более чем из одной директории


View := jet.NewHTMLSet( "./view1", "./view2" )

также можно просто создать объект, не загружая шаблоны, вызвав NewHTMLSet без параметров. Шаблон можно загрузить из переменной типа string, указав ему явно имя


View.LoadTemplate(name, content)

Вернемся к нашему исходному каталогу views, пусть в нем есть два шаблона.


<!-- file: "views/message.jet" -->
<!DOCTYPE html>
<html>
  <head></head>
  <body>
    <h1>Hello, {{ login }}.>/h2>
    {{yield message()}}
  </body>
</html>

<!-- file: "views/new_message.jet" -->
{{extends "message.jet"}}
{{block body()}}
  <p>You have a message from {{ user }}:</p>
  <p>{{ message }}</p>
{{end}}

В Jet управляющие инструкции и выражения шаблона записываются в двойных фигурных скобках {{ ...}} В плане синтаксиса Jet напоминает Twig в PHP.

В данном примере шаблон new_message.jet является производным от шаблона message.jet и определяет блок block (инструкция extends определяет базовый шаблон). А инструкции {{login}}, {{user}}, {{message}} определяют подстановку переменных в шаблон.

Шаблоны позволяют в свое тело вставлять другие шаблоны без наследования при помощи include


{{ include mytemplate.jet  }}

В тексте шаблонов разрешены комментарии, которые определяются так


{* this is a comment *}

Для того чтобы отрендерить шаблон new_mesage.jet нам потребуется передать в него переменные user, login, message. Для этого нужно создать объект jet.VarMap производный от map.


vars := make(jet.VarMap)
vars.Set( "user", "Mike")
vars.Set("login", "John")
vars.Set("message", "Hello my friend")

Если одна из переменных не будет определена в jet.VarMap, то при рендеринге шаблона возникнет ошибка. Для рендеринга используем код


templateName := "new_message.jet"

t, err := View.GetTemplate(templateName)
if err != nil {
    panic(err) // template could not be loaded
}

var w bytes.Buffer // needs to conform to io.Writer interface 

if err = t.Execute(&w, vars, nil); err != nil {
    panic(err) // error when executing template
}

fmt.Println( w.String() )

Шаблонизатор по умолчанию выполняет эскейпинг всех переменных для корректного отображения в HTML. Но если мы хотим в message передать уже сформированный HTML, нам потребуется определить пользовательский фильтр. Это делается так


View.AddGlobal("noescape", 
  jet.SafeWriter(func(w io.Writer, b []byte) {
    w.Write(b)
}))

A шаблоне message запишем так


{{ message | noescape }}

Если мы хотим после этого провести еще несколько преобразований, то можно использовать следующий синтаксис


{{ message | filter1 | filter2 | ... | filterN }}

В качестве переменной шаблона может выступать переменная любого типа. Например, если у нас есть тип


type User struct {
  Login string
  Url string
}

func (u *User) Href() string {
  return fmt.Sprintf( "<a href=\"%s\">%s</a>", u.Url, u.Login )
}

Тогда, передав переменную user типа User в объект jet.VarMap мы сможем написать в шаблоне так


{{ user.Login }} {{ user.Url }} {{ user.Href() | noescape }}

Для подстановки значения map или slice можно воспользоваться синтаксисом


{{ mapName["field"] }}
{{ sliceName[1] }}

Jet поддерживает итерации по переменным типа map и slice. Это осуществляется при помощи инструкции range, которая имеет несколько вариантов синтаксиса


{{range varname}}
{{end}}

{* итерация по значениям *}
{{range value := varname}}
{{end}}

{* итерация по паре ключ/значение*}
{{range keyOrIndex, value := varname}}
{{end}}

Кроме range синтаксис шаблонов поддерживает условные оператор if, синтаксис которого ниже


{{if expression}}
{{end}}

{* assignment is possible as well *}
{{if ok := expression; ok }}

{{else if expression}}

{{else}}

{{end}}

Для дальнейшего изучения можно воспользоваться официальной документацией на сайте проекта.

 

08.07.2018









 
архив

подписка