Использование YAML в Jekyll/Liquid
TL;DR; Надо быть очень аккуратным, YAML очень требователен к синтаксису!
YAML - это "простой" текстовый язык разметки, предназначенный для "человеко-читаемого" представления данных и конкурирующий с json, но на деле весьма нетривиален1.
Однако в простых случаях данные в формате YAML действительно проще понимать и редактировать в текстовом виде, чем в том же json.
В Jekyll YAML используется и для конфигурации (_config.yml
), и для хранения данных, а посему активно используется в "скриптах" Liquid для динамического формирования страниц2.
Сразу предупрежу, что если Jekyll обнаружит ошибку в формате YAML, страница или сайт целиком не будут обновлены. Поэтому крайне рекомендуется поставить Jekyll локально и проверять корректность YAML-элементов в валидаторе (см. Ссылки).
Минута теории. В YAML есть три вида объектов: последовательности (списки, массивы), словари (структуры, отображения) и скаляры. Последовательности и словари можно записывать двумя способами, построчно и "в скобках" (аналогично Python или формату json).
Примеры:
# Front Matter является словарём, т.е. все элементы
# верхнего уровня должны быть вида <имя>: <значение>
# Пустые строки между элементами не несут нагрузки.
# Комментарии обозначены знаком #
# Имена могут содержать пробелы
scalar element: 42 # комментарии могут быть в конце любой строки
# Кавычки (двойные, одинарные) не включаются в состав строки
sequence_element: [zero, 1, 2, 3, 'inf'] # последовательность
# (список, массив) в виде строки
# Для логических переменных можно использовать true/false
boolean_element: true
# причём независимо от регистра
another_boolean_element: FalSE
# Отображение (словарь) с несколькими элементами, построчно
dictionary_element:
an_integer_number: 12
a_float: 0.123
some_string: This is a string with---
AnotherString: "В кавычках можно спрятать: двоеточие"
some_timestamp: 2019-04-20 # это дата (точнее, timestamp)
# В словаре может быть последовательность
# (последовательность может содержать строки)
a_sequence: [1, 2, three, 'some string, and more']
another_sequence_element:
# Содержит всего один элемент - последовательность (массив)
# из 3 элементов, заданную "построчно"
- 3.14
# Cловарь можно задать и "строчно".
# Пробелы после двоеточий важны!
- {a: 1, b: 0.4, c: "2019-04-30 12:51 +0400"}
- Это строка
- !!str 2019-03-14 # эта дата сохранена как строка!
Этот "зоопарк" добавлен в заголовок (Front Matter) markdown-исходника этой страницы. Отмечу, что пример содержит словарь, элементами которого являются: число, пара логичских переменных, последовательность (массив), вложенный словарь и ещё одна последовательность.
YAML в файле конфигурации _config.yml
(Jekyll)
Данный вопрос заслуживает [отдельного рассмотрения]({ post_url 2019-04-28-jekyll-main-configuration }).
YAML как источник данных на страницах (Liquid)
В Jekyll каждая страница может иметь заголовок в YAML разметке (Front Matter), ограниченный строками c тремя дефисами.
---
# YAML-блок, содержащий словарь
---
Данный блок содержит словарь, который добавляется к переменной page, то есть все элементы должны иметь вид
name: content
причём двоеточие "прилипает" к имени, а после двоеточия есть пробел. Содержимое может быть списком, словарём или скалярной переменной (строкой, числом и т.п.), но об этом чуть ниже.
При этом формируется поле page.name
с указанным содержимым.
Доступ к полям структуры/словаря осуществляется либо с помощью точки, либо с помощью индексации квадратными скобками: page['name']
эквивалентно page.name
. Индексация в скобках применяется для ключей с пробелам.
Когда Jekyll обрабатывает страницы, шаблонизатор Liquid с помощью тегов3 {{ ... }}
или {% ... %}
подставляет известные переменные и/или преобразует их.
Подробнее об различиях этих видов тегов см. запись о Liquid, но для демонстрации понадобится только первый тип: {{...}}
, выводящий значения переменных.
Отображение переменных из примера выше
Текст примера был добавлен в заголовок исходного md-файла этой страницы. Результаты зависят не только от того, как YAML преобразовал текст в данные, но и от того, как Liquid отобразил эти данные обратно в текст.
-
Если ключ (id) в словаре содержит пробел, необходимо использовать доступ по индексации
{{ page['scalar element'] }}
выведет:42
-
Значения логических переменных (флаги) не зависят от регистра:
{{ page.boolean_element }}
и{{ page.another_boolean_element }}
отображаются как:true false
-
Последовательность
{{ page.sequence_element }}
при выводе "слипается":zero123inf
-
Для последовательностей доступна индексация, начинающаяся с нуля:
{{ page.sequence_element[0] }}
,{{ page.sequence_element[4] }}
выведутzero inf
Кстати, выход за границы или некорректное значение индекса безопасен, результатом будет пустой элемент:
{{ page.sequence_element[10] }}
и{{ page.sequence_element[abc] }}
выведут (внутри кавычек):""
и""
. Хотя это больше относится к интерпретации переменных в Liquid (точнее, Ruby). -
Строка {{ page.dictionary_element.AnotherString }}
и элемент времени{{ page.dictionary_element.some_timestamp }}
выведут:В кавычках можно спрятать: двоеточие 2019-04-20
Обратите внимание, что из полного описания момента времени отобразилась только дата. Это - вывод по умолчанию. Для более полной информации надо использовать [фильтр Liquid]({ post_url 2019-05-12-jekyll-and-liquid }):
{{ page.dictionary_element.some_timestamp | date: "Year %Y %H:%M" }}
Year 2019 00:00
-
По умолчанию YAML распознаёт тип скаляра по его текстовой записи. Но можно указать его явно, указав префикс. Например, указание
!!str
в последнем элементе последовательностиpage.another_sequence_element[3]
выведет строку.2019-03-14
С одной стороны, тип данных важен в YAML, а с другой, прикладной стороны, Liquid автоматически приводит их к нужному типу. По всей видимости, это связано с тем, что Liquid написан на Ruby. К строке можно применить фильтр данных для времени: (
{{page.another_sequence_element[3] | date: "%d/%m/%Y"}}
выведет14/03/2019
).Аналогично и к строке
page.dictionary_element.some_string
, и к моменту времениpage.dictionary_element.some_timestamp
можно применить операцию замены (фильтр Liquid| replace: "-", "=>"
):{{page.dictionary_element.some_string | replace: "-", "=>"}}
и{{page.dictionary_element.some_timestamp | replace: "-", "=>"}}
выведутThis is a string with=>=>=> 2019=>04=>20
Как видно, для даты было использовано её текстовое значение по умолчанию.
Кроме чисел, строк и элементов времени, скалярами в YAML могут быть бинарные данные (например, записанные в текстовом формате Base64), и вообще скалярам может быть присвоен произвольный тип4, лишь бы он корректно обрабатывался приложением, "распознающим" этот тип.
Вышенаписанного достаточно, чтобы использовать YAML. Некоторые источники можно почитать в разделе Ссылок.
YAML как источник данных
Вместо базы данных Jekyll имеет достатуп к данным из папки _data
, которая может содежать и YAML-файлы. (в плане)
Типичные ошибки
В принципе, в сообщения об ошибках в YAML Jekyll указывает строку, позицию и описание.
-
В имени (заголовке) записи/страницы есть двоеточие. В этом случае надо заключить его в кавычки (двойные или одинарные), т.е. вместо
title: Блог: выбор размещения
писать
title: "Блог: выбор размещения"
-
Символ табуляции в префиксах (и не только). Jekyll прямо запрещает использование табуляции в конфигурационном файле. Если это и не приводит к ошибкам, вместо заданных Jekyll использует настройки по умолчанию. Проверить конфигурационный файл можно командой
bundle exec jekyll doctor
(или простоjekyll doctor
), а параметр--strict_front_matter
(или полеstrict_front_matter: true
в_config.yml
) заставляет Jekyll не пропускать ошибки в заголовках Front Matter. -
Запись полей словаря в неаккуратном формате:
key:value
вызовет ошибку сборки, необходим пробел. Правильно:
key: value
Вариант с
key : value
тоже работает, но лучше придерживаться общепринятого стандарта.
Понимание YAML
YAML-данные имеют три ипостаси:
- Текстовую (presentation), описывающую представление данных (presentation) - собственно, именно в таком виде данные хранятся в текстовых файлах, обычно в формате UTF-8.
- В виде графа (representation), в простом случае без ссылок - просто дерево, каждый узел которого (кроме корневого) - список или отображение, а висячая вершина/лист - скаляр. Особенность тут в том, что каждый тег имеет свой тип (число, строка и т.д.), который в в YAML называется... "тегом"4.
- В виде данных на соответствующем языке программирования (native).
Они образуют цепочку преобразований, включающую сериализацию, см. пример из спецификации:
По умолчанию данные записаны в текстовых файлах в формате UTF-8.
Скаляры представляются в тексте несколькими символами UTF-8 или их отсутствием.
Ссылки
-
https://yaml.org - сайт YAML со спецификацией. Спецификация кажется переусложнённой, но примеры вполне читаемы, особенно если знать, что:
- во втором разделе примеры и в левых, и в правых табличках,
- в пятом разделе (более детальном) в левых таблицах - исходный текст, а в правых - "каноническое" представление YAML этих данных (результат разбора и интерпретации),
- существует одностраничное описание разметки (reference card).
-
http://lzone.de/cheat-sheet/YAML - краткая памятка по YAML (cheat sheet), ссылка на несколько других валидаторов, включая онлайн-конвертер в json/python.
-
https://codebeautify.org/yaml-validator - онлайн-проверка синтаксиса YAML на корректность.
-
https://yamlvalidator.com - ещё один валидатор.
-
https://www.opennet.ru/base/dev/yaml.txt.html - знакомство с YAML (на русском языке).
-
https://ru.wikipedia.org/wiki/YAML - волшебная Википедия содержит несколько ссылок, а английская Википедия вдобавок и пояснения по формату
-
Например, представление данных является связным направленным графом с корнем, допускающим циклы. ↩
-
Напомню, что в результате получаются обычные HTML-файлы. Jekyll/Liquid всего лишь динамически собирают эти страницы на специальном "языке программирования" Liquid. ↩
-
Не путать с "тегами" в смысле "облака тегов" и "тегами" YAML. Теги Liquid по смыслу ближе всего к HTML-тегам. ↩
-
Не путать с "тегами" в обычном понимании меток (в смысле "облака тегов") и с "тегами" Liquid. Тег YAML близок типу данных, отягощённуму дополнительной информацией (схемой), которая управляет дальнейшей обработкой этого куска данных в конкретном приложении. ↩