По поводу и без Do about and for no reason

Использование шаблонизатора Liquid в Jekyll

Продолжаем разбираться с Jekyll. Основным компонентом для создания полезного функционала сайта является некий Liquid. Его же надо знать для модификации сайта и добавления "рюшечек" и "свистелок".

Liquid - это язык генерации страниц из шаблонов, используемый в Jekyll. В нём есть переменные, циклы и некоторые базовые операции, а потому формально он может считаться языком программирования (впрочем, очень ограниченным).

У Liquid есть собственная документация. Она подходит для справки, но в контексте сайта/блога её не хватает, т.к. Jekyll использует расширенную вариацию Liquid, с тремя дополнительными тегами, а также множеством фильтров и некоторыми Jekyll-специфическими переменными. В заметке рассмотрен именно этот вариант Liquid.

Компанией Cloudcannon составлен детальный справочник по переменным, тегам, фильтрам и прочим элементам Jekyll/Liquid.

Терминология: в Liquid есть теги и фильтры. "Теги" Liquid никак не связаны с тегами (метками) в обычном смысле (как в "облаке тегов"), а являются аналогами "тегов HTML", т.е. выполняют роль разметки. Фильтры используются внутри тегов и выполняют преобразования над переменными: от замены текста до сортировки.

Обработка Liquid тегов и фильтров происходит в первую очередь, до преобразования Markdown-разметки в HTML. Можно посмотреть исходный текст этой записи как источник примеров.

Для отладки ошибок Liquid см. раздел ниже.

Далее по порядку разобраны компоненты Liquid:

Ещё раз подчеркну, что некоторые переменные и теги существуют только в "Liquid для Jekyll", а не "чистого Liquid".

Переменные Liquid

Переменные - ядро Liquid. В большинстве случаев их можно только читать, использовать, но не изменять. В документации Liquid они отделены от "объектов", но для простоты удобно считать "объекты" просто переменными для чтения.

Переменные1 "появляются" несколькими способами:

  • берутся из YAML-разметки, а именно:

    • из заголовков страниц/записей блога (Front Matter),
    • из конфигурационного файла Jekyll _config.yml.

    Они включают пользовательские переменные, внутренние переменные Jekyll, также переменные со значениями по умолчанию2.

  • из файлов данных Jekyll,

  • создаются тегами assign и capture, см. ниже.

Переменными просто пользоваться, но некоторые вещи банально не документированы. Они возникают на стыке YAML (данные), Liquid (обработка данных) и Ruby (как языка, на котором написан Jekyll и обработчик Liquid).

Некоторые факты:

  • Liquid лояльно относится3 к несуществующим переменным, полям, индексам массивов и возвращает специальное пустое значение Nil (когда результат Liquid-кода "пуст"). При выводе это значение соответствует пустой строке:

    "{{ not_a_var }}", "{{ some_var.no_such_attribute }}" и "{{ list[-3] }}" выведут "", "" и "".

  • Кавычки (и двойные, и одинарные) определяют строку, а не переменную. В примере текст специально взят в квадратные скобки. Кавычки не входят в состав строки (переменная some_var не определена):

    [{{ some_var }}], [{{ "some_var" }}] и [{{ 'some_var' }}] выведут [], [some_var] и [some_var].

  • Есть ключевые слова, например, true, false (чувствительны к регистру)

    "{{ true }}", "{{ false }}" и "{{ False }}" выведут "true", "false" и ""

  • Есть типы, сравнение между которыми выдаст ошибку (например, строка и число). С другой стороны, может происходить автоматическое преобразование типов, например, в фильтрах.
  • При выводе тегом {{ ... }} содержимое преображается в строку.

В YAML можно задать скаляры, массивы и словари (см. заметку по YAML), а Liquid формально может использовать только скаляры и массивы.

Это не мешает использовать доступ к полям словаря с помощью синтаксиса dict_name.field_name или равноправного dict_name[field_name], см. примеры YAML. Эти значения можно использовать, но не изменять.

"{{ site.url }}", "{{ site["url"] }}" и "{{ page.title }}" выведут "https://atremba.github.io", "https://atremba.github.io" и "Использование шаблонизатора Liquid в Jekyll"

Здесь использованы предопределённые переменные, связанные с сайтом и конкретно с этой страницей.

Доступ к элементам массива осуществляется по числовому индексу (начинается с 0): array_name[0], array_name[1], ...

Наконец, в Jekyll определены два фильтра: sort и where, имеющие доступ к полям (properties). А именно, они могут сортировать и фильтровать массивы словарей, исходя из значений конкретных полей.

Создание переменных в Liquid

Иметь дело с переменными в Liquid не так просто, в первую очередь из-за их постоянства. Как уже говорилось, изменять некоторые сложные переменные динамически нельзя (в частности, словари и их поля).

Зато можно создавать новые переменные. Можно определить и создать переменную прямо внутри тега assign:

{% assign my_variable = ... %}

А парные теги {% capture my_variable %} ... {% endcapture %} "захватывают" в переменную my_variable всё содержимое между ними.

Списки (массивы) можно породить либо тегом split (из строк, см. пример в разделе про фильтры), либо применением фильтров к существующим массивам/словарям.

Модификация переменных

В Liquid предусмотрены особые теги итерации {% increment var_name %} и {% decrement var_name %}, создающие и модифицирующие целочисленные переменные (начиная с нуля). Некоторый юмор в том, что созданная таким образом переменная var_name отличается от переменной var_name с тем же именем, но созданной с помощью {% assign var_name = ... %} Как с этим работать, я так и не понял. Но для себя сделал вывод, что Liquid немного странный.

Теги Liquid

Теги бывают двух видов4. Они могут:

  1. Подставить текст переменной. Они состоят из двойных открывающих и закрывающих фигурных скобок, например {{ page.some_var }} выведет переменную some_var, заданную (без префикса!5) в заголовке этой страницы: Текст в переменной some_var.. Перед выводом переменная может быть преобразована фильтром, например, заменяющим часть строки "Текст в" на "Data in": Data in переменной some_var..

  2. Выполнить команду в формате {% command ... %} . Эти теги, в свою очередь, делятся на

    {% for counter in page.example_list %}{{ counter }}{% endfor %}

    Выведет:

    1234567

    У тегов есть нетривиальные особенности работы с окружающими пробелами, особенно с вложенными многокомпонентными конструкциями (типа if - elseif - else - endif), поэтому удобнее всего их использовать в HTML-шаблонах, а не markdown-разметке4.

Комментарии, ограниченные тегами {% comment %} ... {% endcomment %} , будет исключены из текста.

Чтобы отменить интерпретацию тегов ({{ ... }} и {% ... %} ), их надо обрамлять {% raw %} и {% endraw %}.

Специфические теги Jekyll

Помимо стандартных Liquid-тегов, в Jekyll добавлено ещё три:

  • для подсветки синтаксиса (средствами HTML!). Мне кажется, что этот функционал дублирует блочные записи c ```lang ... ``` в Markdown.
  • для ссылок на произвольные страницы (link) и записи блога (post_url). Они весьма полезны, т.к. выполняют валидацию ссылок, и если соответствующей страницы/записи нет, сборка Jekyll прервётся, а в режиме запущенного веб-сервера (jekyll serve) будет выдано сообщение об ошибке, как в примерах выше.

К сожалению, если сайт располагается не в корне, то надо добавлять префикс baseurl (задаваемый в _config.yml):

[Name of Link]({{ site.baseurl }}{% post_url 2010-07-21-name-of-post %})

Фильтры Liquid

Фильтры применяются для форматирования вывода и преобразования выражений. Для вывода используется тег {{ ... }}, а преобразования полезны для формирования переменных, например, это способ создать массив:

{% assign my_array = "ants, bugs, bees, bugs, ants" | split: ", " %}

создаст список my_array, который будет выведен как

antsbugsbeesbugsants

  • Фильтры бывают без параметров, например,

    {{ 123.456 | ceil }} выведет

    124

  • Фильтры с параметрами: {{ 7 | divided_by: 2.0 }} выведет

    3.5

  • Фильтры можно применять последовательно:
    {{ my_array | uniq | join: ", " }} выведет

    ants, bugs, bees

Кроме полного списка фильтров Jekyll и Liquid, удобно использовать справочник с группировкой по типам объектам (целые числа, массивы, строке), к которым применяются фильтры. Заодно в этом справочнике приведены встроенные переменные и список управляющих тегов (условия, циклы и пр.)

Отладка Liquid

Удобнее всего экспериментировать с Liquid, запустив Jekyll локально, а потом редактировать файл, периодически его сохраняя. Исключение - главный конфигурационный файл _config.yml; для учёта изменений в нём надо перезапустить сервер.

Интересно, что запуск Jekyll в режиме сервера (jekyll serve и bundle exec jekyll serve) прервётся, если будет обнаружена ошибка в Liquid. Однако если запустить его на "хорошем" коде, а потом внести ошибку в код, то работа сервера не прервётся.

При обнаружении ошибок или предупреждений Jekyll/Liquid выдаёт в консоль, откуда был запущен, содержательную информацию, включающую номер строки. Правда, иногда он не соответствует строкам markdown-кода из-за заголовка6. Примеры:

Liquid Warning: Liquid syntax error (line 47): Expected end_of_string but found pipe in ""now" | date: "%d" <= 10" in <path_to_blog>/_posts/2019-05-08-jekyll-and-liquid.md

Liquid Exception: Could not find post "2019-04-29-local-jekyll" in tag 'post_url'. Make sure the post exists and the name is correct. in <path_to_blog>/_posts/2019-05-08-jekyll-and-liquid.md

Error: Could not find post "2019-04-28-local-jekyll" in tag 'post_url'. Make sure the post exists and the name is correct.

После исправления ошибки выдача станет нормальной:

Regenerating: 1 file(s) changed at 2019-05-12 10:53:49
_posts/2019-05-08-jekyll-and-liquid.md
...done in 3.0911087 seconds.

Для подробного вывода можно запустить jekyll build --trace, точнее,

>bundle exec jekyll build --trace

Заключение

Язык Liquid чаще всего используется для создания шаблонов или тем сайтов/блогов.

Однако с его помощью можно и формировать страницы динамически, и даже организовывать подобие базы данных с помощью файлов данных (в режиме чтение).

В обычных записях можно использовать ссылки на другие записи. Например, некоторые записи этого блога начинаются со ссылок вида [текст]({% post_url 2019-04-09-blog-on-github-pages.md %}).


Ссылки


  1. Формально переменные являются "объектами", и их значения выводятся с помощью конструкции {{ ... }} . Для простоты я называю эти двойные фигурные скобки тоже "тегами".

  2. Переменные по умолчанию есть и в заголовках, и в конфигурационном файле. Github Pages добавляет к ним метаданные репозитория. Кроме того, в Jekyll можно добавить свои переменные по умолчанию для нужных страниц или записей, сделав раздел defaults: в _config.yml.

  3. Согласно документации, технически это поведение (обработку неопределённых переменных и фильтров) можно изменить при запуске обработчика Liquid-кода. Но как включить этот режим из Jekyll - не знаю.

  4. В спецификации Liquid есть вариации тегов, включающие дефисы: {{- ... -}}, и {%- ... -%} . Они "отключают" пробелы вокруг результата выполнения тега, что важно в контексте Markdown-разметки. Похоже, что они удаляют пробельные символы до/после тега, "приклеивая" его содержимое к предыдущему/последующему тексту. Эти дефисы-модификаторы могут быть не парными. Так, {{ ... -}} отключит пробелы после вывода. По моему мнению, поведение этих тегов в сложных и вложенных конструкциях типа for ... if-then-else-endif ... endfor документировано недостаточно.

  5. Все заданные в заголовке (Front Matter) переменные добавляются как поля в словаре page. Аналогично, все переменные, заданные в _config.yml, являются полями словаря site, и доступны в тексте любой страницы.

  6. YAML-заголовок (Front Matter) считывается и убирается из текста до обработчика Liquid. Это логично, ведь собственно из него и берётся часть переменных. Поэтому номер строки ошибки, предупреждения или исключения Liquid надо увеличивать на число строк Front Matter - включая начальные и закрывающие строки с "---".