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

от автора

На Хабре, было несколько статей о 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», будьте осторожны.


ссылка на оригинал статьи https://habr.com/ru/articles/834270/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *