Как стать автором
Обновить

Yaml — король мета-описаний

Уровень сложностиСредний
Время на прочтение11 мин
Количество просмотров1.2K

На Хабре, было несколько статей о Yaml, но мне кажется все они однобоки и не раскрывают его истинную природу. Я попробую это исправить и рассказать о Yaml в положительном контексте. Не буду вновь описывать детали синтаксиса стандартного Yaml, в Интернете есть много материалов на эту тему. Их можно найти и на Хабре, в том числе, по ссылкам из этой статьи. Материал ориентирован на тех, кто знаком с Yaml, но возможно чувствует неприязнь к формату.

За последние 30 лет активно развивающего IT, устоялись три основных формата для представления иерархических данных, т.е. деревьев:

  1. XML/HTML - особенность формата в том, что узлами являются объекты, а это архи необходимо, в первую очередь, для кодирования богатых "человеческих визуализаций". Отсюда и постфикс "ML" - язык разметки. Этот формат используется для веб-сайтов или, например, для хранения данных MS Excel. Узлы представляются объектами из-за наличия атрибутов тегов, а также необходимостью прохода по дереву во всех направлениях. В начале 2000-х годов, XML более широко использовался для передачи данных между удаленными хостами, но часто был вытеснен JSON, так как последний намного более прост, а мощь XML (как и сложность) оказались не востребованы.

  2. JSON - также представляет иерархические данные, но узловыми ключами не могут быть объекты, а только скаляры: строки или целые числа. Особенность формата в том, в том, что он имеет минимальное количество правил, необходимых для практической реализации парсеров. Отсюда его популярность, в том числе, для обмена сообщениями между удаленными хостами. JSON часто не читают люди, а кодирование и декодирование происходит автоматически.

  3. YAML - также не может содержать в узловых ключах объекты, а только скаляры. Этот формат включает, как подмножество, JSON, часто используется для конфигураций, и ориентирован в большей степени для чтения-записи человеком, чем JSON. В этой статье, сравниваются два последних формата, но имхо, нужно акцентировать, что Yaml предназначен для человека, а JSON не только. И ставить вопрос "кто круче" не очень корректно.

Ситуация с Yaml, наверно самая сложная и запутанная, например в этом посте-нытье рассказывается как всё плохо. Но негативные обстоятельства можно и нужно обратить вспять. Для этого следует всё расставить по своим местам. С момента появления Yaml прошло 23 года, но он активно используется в различных языках программирования для конфигураций, в том числе, флагманском PHP-framework Symfony. По-моему этого факта достаточно, чтобы утверждать, что Yaml имеет свой "рай", просто его нужно понять. Наверно до Yaml нужно дорасти, я например, написал свой парсер Yaml и многое понял из того о чём пишу, только в прошлом году, хотя занимаюсь программированием уже давно.

Я считаю, в стандартной архитектуре Yaml, действительно присутствует удивительный факап. Первичная идея - гениальная, но конечные детали - неудовлетворительны. Как говорится "начали за здравие, закончили за упокой"... Тем не менее, за 23 года жизни Yaml устоялся факт его локального применения. Жизнестойкость и локальность объясняется так: он действительно красив, минималистичен и выразителен, но его парсер всегда будет более сложен, объёмен и менее эффективен чем у JSON. Производительность - не проблема, так как компилированные данные всегда можно поместить в кэш. Я назвал Yaml королем мета-описаний, потому что, считаю синтаксис Yaml таким, который вобрал в себя все самые мощные способы представления иерархических структур, которые будут выглядеть красиво и выразительно для человека. По сути, Yaml удобно использовать не только для конфигураций, но и для программирования. Но об этом позже.

Я не нашел в Wikipedia четкого термина для "мета-описаний", поэтому опишу, что я под этим подразумеваю. Иногда, в программировании, бывает эффективно придумать какой-то собственный формат представления данных, который будет отражать алгоритмические особенности парсера, работающего с таким форматом. Данные в таком произвольном формате, я и называю мета-описаниями. Например, среди моих прошлых работ, имеется PHP имплементация CSS framework Tailwind. Для генерации всех(!) утилит Tailwind используется до 200 мета-описаний. Это пример одного мета-описания для генерации 42-х утилит "Grid Column Start / End":

- MetaName: col #grid
  ForMenu: 2col span-start-end
  Body: |
    auto|span-full|start-auto|end-auto|@num
    grid-column: auto
    .auto
    grid-column: 1 / -1
    .span-full
    grid-column-start: auto
    .start-auto
    grid-column-end: auto
    .end-auto
    span-~num!=: span {0} / span {0}|start-~num!=-start: {0}|end-~num!=-end: {0}
    grid-column{@}
    .@num

В консоли можно протестировать как Vesper (это моя имплементация Tailwind) генерирует утилиты, вот три примера:

>php vendor/bin/sky venus tw col-auto
.col-auto {
  grid-column: auto;
}

>php vendor/bin/sky venus tw col-span-2
.col-span-2 {
  grid-column: span 2 / span 2;
}

>php vendor/bin/sky venus tw col-start-2
.col-start-2 {
  grid-column-start: 2;
}

Кстати, эта статья включает работу программиста на PHP. Для тех, кто хочет подробнее ознакомиться с моей работой, доступно к установке пустое демо-приложение "Hole". Предостережение: Coresky - это экспериментальный фреймворк, подробнее в спойлере:

Hidden text

Это значит, что я использую фреймворк только для разработки новых архитектурных решений и не рекомендую его использовать для публичной части Интернета. Проделана огромная работа благодаря только лишь эндорфинам и удовольствию творчества. Много кропотливой, не творческой работы не сделано и вы легко найдете ошибки. Отчасти они присутствуют из-за того, что я всё еще продолжаю делать обратно несовместимые изменения новых версий к старым. Тем не менее то, о чём я пишу в этой статье - отлично работает. Большая часть кода имеет статус release-candidate.

>composer create-project coresky/hole
  # и можно сразу запустить PHP-DEV-WEB сервер:
>cd hole
>php vendor/bin/sky s

Исправим стандартный Yaml

Смотрите, для передачи данных между хостами существуют JSON и XML, а Yaml мы будем использовать только локально. Поэтому нет проблем, чтобы написать свой собственный парсер. Давайте исправим архитектурный факап, чтобы минимизировать досадные "подводные камни" и с другой стороны, раскроем всю потенциальную мощь Yaml. Конечно, необходимо стараться максимально сохранить стандартный Yaml, это упростит изучение Coresky модификации. Пакет Composer symfony/yaml как и многие другие популярные имплементации также не поддерживают стандарт полностью. Например, обычно, не поддерживается множественный Yaml документ. В Coresky, парсер Yaml это один файл, содержащий 577 строк. Я использую технику разбивки на токены, тогда как в Symfony используются регулярные выражения.

Неявная типизация. С проблемой Норвегии - это ерунда какая-то.. Давайте оставим рабочими только три литерала, которые являются стандартными в JSON и соответствуют по типу в PHP: true, false, null. Целые числа оставим только десятичные, а также с плавающей точкой float, в полном соответствии с синтаксисом PHP. Двоичные, шестнадцатеричные INT и всю остальную неявную типизацию убираем. Для этого дела лучше применить функции трансформации или операторы. Об этом ниже.

Явная типизация. Здесь, нужно расширить стратегию и использовать соответствующие термины "оператор" или "функция преобразования". В Coresky имеется собственный компилятор представлений - Jet, который является потомком Laravel/Blade, в нём используются операторы, начинающиеся с символа амперсанд. Используем такой же подход и в Yaml. Для имплементации большинства функций трансформации требуется буквально пару строк кода на PHP. То как трансформируются значения записано в нотации стандартного JSON:

# @base64 тоже что и !!binary в стандартном Yaml
img: @base64 |
  R0lGODdhDQAIAIAAAAAAANnZ2SwAAAAADQAIAAACF4SDGQ
  ar3xxbJ9p0qa7R0YxwzaFME1IAADs=
array: @csv(:) a:b:c # трансформируется в массив: ["a", "b", "c"]
hi: @hex2bin > # трансформируется в строку: "Hello word!"
  48 65 6C 6C 6F 20 77 6F 72 64
  21
# десятичные числа в спец-нотации:
card: @dec 1234_5678_9012_3456 # >>> 1234567890123456
big-number: @dec 1 000 000 000 # >>> 1000000000
phone: @dec 2-777-222-22-22    # >>> 27772222222
# двоичное число:
five: @bin 101 # >>> 5
# Search-And-Replace, используется функция PHP preg_replace(..)
sar: @sar(|=+| is ) a========1, b==2 # >>> "a is 1, b is 2"
# @left(string), @right(string) - дописать "string" слева или справа
key: @left(left-) value # >>> "left-value"
# и так далее ..

Операторы можно указывать только вначале значений Yaml или JSON нотаций. Для одного значения, можно указать несколько операторов и они могут применяться каскадно для всех значений под-массива. Порядок выполнения - справа налево и снизу вверх. Для частной отмены каскадного выполнения, можно использовать @deny:

one: @right( RR)
  two: @bin
    - 0b11
    - 101 # можно и без префикса 0b
    - @deny @left(LL ) 1010
  three: four
color: @left(#) @each @bang(. ) @sar(| +| ) > # not in cascade
  aliceblue.f0f8ff     antiquewhite.faebd7
  beige.f5f5dc         bisque.ffe4c4
# для кодирования цветов мы указали только информационную "соль"
# вот почему Yaml - король метаопределений
{
  "one": {
    "two": ["3 RR", "5 RR", "LL 1010"],
    "three": "four RR"
  },
  "color": {
    "aliceblue": "#f0f8ff",
    "antiquewhite": "#faebd7",
    "beige": "#f5f5dc",
    "bisque": "#ffe4c4"
  }
}

Якоря и ссылки. Не будем расширять синтаксис, можно использовать функцию трансформации @path:

one:
  two: [1, 3]
three: @path(one.two.1) # this value will eq. to 3

Множественный Yaml. Должен иметь именованные части! Тогда можно будет свободно компоновать Yaml-данные и применять принцип DRY. Компилятор Jet поддерживает маркеры частей файлов. Используем эту же стратегию и в Coresky Yaml:

#.run =========================
- @inc(.test) # @inc это include
- 2
#.run

#.test =========================
+ 123
#.test
<?php

# функция yml(..) выполняет inline-yaml, может кешировать компилированный PHP
# и передавать переменные, подобно как это делается в Jet (Blade)
print_r(yml('+ @inc(run) filename.yml'));

# stdout: array(0 => 123, 1 => 2)

Гибридные массивы PHP. В стандартной Yaml нотации есть одна неразбериха: для того чтобы указать значение со строковым ключом на нижнем уровне иерархии - обязательно нужен отступ, а для перечислений отступ необязателен:

aaa:
  bbb: # { "aaa": { "bbb": null } }    отступ ==> глубина

aaa:
bbb: #  { "aaa": null, "bbb": null }   <--- здесь глубина не изменилась

aaa:
  - bbb # { "aaa": [ "bbb" ] }      отступ ==> глубина

aaa:
- bbb # { "aaa": [ "bbb" ] }    <--- здесь глубина изменилась хотя нет отступа!!!

# Исправим синтаксис для предыдущего примера в Coresky вот так:
aaa:
- bbb # { "aaa": null, "0": "bbb" }

Yaml компилируется в PHP массив, а массивы в PHP могут быть гибридными. Разрешим совмещать в Yaml нотации строковые ключи и ключи перечислений (дефис). Это расширит потенциал использования Yaml. Ниже представлен действующий код для генерации HTML формы системной конфигурации Coresky для Root-Admin-Section:

#.system
- <fieldset><legend>Primary settings</legend>
- ['', [[<b><u>Production</u></b>, li]]]
trace_root: [Debug mode on production for `root` profile, chk]
trace_cli: [Use X-tracing for CLI, chk]
error_403: [Use 403 code for `die`, chk]
empty_die: [Empty response for `die`, chk]
gate_404: [Gate errors as 0.404 (soft), chk]
log_error: [Log ERROR, radio, [Off, On]]
log_crash: [Log CRASH, radio, [Off, On]]
- [Hard cache, {
    cache_act: ['', radio, [Off, On]],
    cache_sec: ['Default TTL, seconds', number, style="width:100px", 300]
  }]
- </fieldset>
- <fieldset><legend>"Visitor's & users settings"</legend>
- [Cookie name, {
    c_name: ['', '', '', sky],
    c_upd: ['Cookie updates, minutes', number, style="width:100px", 60]
  }]
visit: ['One visit break after, off minutes', number, '', 5]
reg_req: [Users required for registrations, radio, [Both, Login, E-mail]]
- </fieldset>
#.system

Coresky Yaml - младший брат Jet

В Coresky имеется функционал для генерации HTML форм из массивов PHP. Теперь формы можно определять с помощью Yaml. Добавим операторы @php и @preflight, ниже пример кода Yaml и соответствующий компилированный PHP кэш-файл:

#.test =======================================
+ @preflight($v_1, &$v_2) |
  return SomeClass::method_1($v_1, $v_2);
- string
- @php OtherClass::method_2($__return)
- {a: b, c: @php(OtherClass::method_3([1, $v_3])), x: y} # json notation
#.test
<?php

# preflight code
$__return = call_user_func(function() use ($v_1, &$v_2) {
  return SomeClass::method_1($v_1, $v_2);
});

# other yaml after compile
return array(
  0 => 'string',
  1 => OtherClass::method_2($__return),
  2 => array(
    'a' => 'b',
    'c' => OtherClass::method_3([1, $v_3]),
    'x' => 'y'
  ),
);

В операторе @php, код PHP можно указать или в параметре (в скобках) или в значении (после оператора). В первом примере выше, в последней строке, подсветка синтаксиса Yaml неверно сработала: всё что указано в скобках оператора @php, не является данными JSON нотации, а является кодом PHP. Если @preflight имеет скобки, то код будет выполняться в изолированной области видимости, которая организуется с помощью Closure и call_user_func, а если скобок нет - то без изоляции.

Вызов такого кода, и передачу переменных, можно выполнить с помощью функции Coresky yml(..):

<?php

$array = yml('cache_filename', '+ @inc(test) filename.yaml', [
    'v_1' => $var_1,
    'v_2' => $var_2,
    'v_3' => $var_3,
]);

В примерах выше, область применения Coresky Yaml, определенно коррелирует с компилятором представлений Jet. Представьте бухгалтерское приложение с огромным количеством форм. Теперь, рядом с папкой mvc, можно сделать папку forms и разместить в ней все формы приложения. Имена файлов содержащих yaml-формы, можно назвать в соответствии с именами контроллеров и разместить по несколько штук в одном файле разделив маркерами, в соответствии с именами действий, где используются эти формы. Представляете как это разгрузит код моделей, контроллеров и представлений? Как сказал автор статьи "Некоторые приемы YAML", да пребудет с нами KISS и DRY, а еще актуально: "разделяй и властвуй".

Формы на Yaml это не панацея, посмотрите, например этот файл. Довольно часто бывает нужно "прожонглировать" кусками кода и данных, чтобы сгенерировать финальный код. Раньше в подобных случаях, я использовал возможности Jet. Теперь это удобнее делать в Yaml. В приложении "Hole", которое вы можете установить с помощью Composer, есть несколько примеров форм на Yaml, в том числе, с передачей переменных в компилированный кэш.

Вы вероятно заметили "плюс" в Yaml нотации, в примерах выше, в местах где обычно бывает дефис. Это еще одно новшество в синтаксисе Coresky Yaml. Есть и другие нововведения, читайте подробнее в документации. Я старался показать хорошую сторону и скрытый потенциал Yaml, хотя, действительно в стандарте есть недостатки. Часто написать веб-приложение можно и без Yaml, но, например, в этом проекте без него было бы туго. Yaml в Coresky - это еще один мощный механизм для программирования и упрощения кода. Если у вас в проекте имеется более-менее значительный массив данных, который поместить в БД не рационально, у вас есть возможность использовать Yaml.

Другие интересные идеи в Coresky

Я напишу пару слов о каждой и помещу текст в спойлеры, так как это немного не по теме статьи. Если не интересно - вы можете пропустить эту часть.

Планы и продукты:

Hidden text

Нет велосипедам! Система продуктов в Coresky - это попытка сделать так, чтобы повторное использование функционального кода было максимально простым. Продукты бывают трёх типов: prod(функциональный код), dev(это по сути просто плагины для инструментов разработчика), view(дополнительные наборы шаблонов Jet и стилей CSS). Имя продукта основного приложения всегда main. Продукты похожи на модули Zend/Laminas, но в последнем нет никаких специальных средств для их интеграции в приложение, а также специальных системных средств для их поддержки. Продукты устанавливаются с помощью веб-интерфейса инструментов разработчика, в это время может произойти специальная инициализация продукта: создание таблиц в БД и т.д.

Планы - это надстройка над сущностями приложений. Стандартные планы, которые имеются в любом Sky-приложении: app, cache, mem, gate, jet. Управляет планами, по сути, движок абстрактного кэша.

Sky Gate - небесные врата

Hidden text

В Coresky нет привычной системы роутинга. SkyGate - это утилита с веб-интерфейсом, в которой настраиваются входные внешние данные для контроллеров. Первая часть адреса запроса определяет контроллер, а вторая действие в нем. Если нужна нестандартная адресация, необходимо использовать Coresky Rewrites.

Компилятор преставлений Jet

Hidden text

За основу была взята идея Laravel/Blade. В Jet много новшеств: препроцессор, бинарная идея блоков, включающая операторы @block, @use, #use, маркеры частей файла, три типа генерации визуализаций: top, sub, block. Имеется ввиду, что каждая визуализация запускает действие в контроллере, подготавливает переменные и генерирует визуализацию с помощью компилированного шаблона Jet.

Заключение

  1. Если вы работаете со значительным объемом, особенно иерархических данных и не сильно воспринимаете Yaml: потрудитесь его изучить получше. Ему уже 23 года и он прекрасно себя чувствует, несмотря на плохие слухи. Если это так, значит он кому-то нужен, может нужен и вам? Когда вы, как Джек Салли скажете "Yaml, я тебя вижу..", вас от него уже будет не оторвать.

  2. У стандартного Yaml есть скрытый потенциал, и я в этой статье старался показать как его раскрыть.

  3. Не забывайте про подводные камни, если вы "не видите Yaml", будьте осторожны.

Теги:
Хабы:
+2
Комментарии0

Публикации

Истории

Работа

React разработчик
57 вакансий
PHP программист
114 вакансий

Ближайшие события

19 сентября
CDI Conf 2024
Москва
24 сентября
Конференция Fin.Bot 2024
МоскваОнлайн
30 сентября – 1 октября
Конференция фронтенд-разработчиков FrontendConf 2024
МоскваОнлайн