[go: up one dir, main page]

Skip to content

said-m/priprava

Repository files navigation

Priprava

JSON template engine that supports template strings with variables (configurable string interpolation), conditions and loops.

Шаблонизатор, позволяющий описывать JSON-ы с динамическим данными, подставляемыми в рантайме, путём описания специальных параметров.

npm version Build Status Coverage Status jest npm type definitions npm license

EN documentation or en

Навигация

Геттинг Стартед

Установка

yarn add priprava

Пример использования

template.json - Описание объекта // (раскрыть/закрыть)
{
  "content": [
    {
      "component": "title",
      "content": {
        "$temp": true,
        "template": "${ title }",
      },
    },
    {
      "$temp": true,
      "if": "promo.has",
      "template": {
        "component": "promo",
        "content": {
          "$temp": true,
          "template": "Осторожно, продакт-плейсмент: ${ promo.text }",
        },
      },
    },
    "В объектах ниже меняется только строка. Вывод, шаблонизируем его.",
    {
      "$temp": true,
      "for": {
        "item": "thisText",
        "mode": "of",
        "data": "text",
      },
      "template": {
        "component": "text",
        "content": {
          "$temp": true,
          "template": "${ thisText }",
        },
      },
    },
    "Сформировать массив - мало, хочется организовать условия на его содержимое!",
    {
      "component": "list",
      "content": {
        "$temp": true,
        "for": {
          "item": "thisItem",
          "mode": "of",
          "data": "list",
        },
        "template": {
          "$temp": true,
          "if": "thisItem.length <= 70",
          "template": "${ thisItem }",
        },
      },
    },
    {
      "component": "input",
      "content": {
        "title": "Заголовок поля",
        "text": {
          "$temp": true,
          "template": "\"${ input }\" - интерполяция в строках в деле!",
        },
        "description": "Дополнительное описание",
      },
    },
  ],
}
data.json - Динамические данные // (раскрыть/закрыть)
{
  "title": "Пример объекта, который должен сформировать класс.",
  "promo": {
    "has": true,
    "text": "Сомневаетесь? Даже повара согласны, что priprava - это клёво!",
  },
  "text": [
    "Это описание компоненты текста.",
    "Полагаю, таких параграфов может быть несколько, поэтому необходимо это всё описывать циклом.",
    "А в объекте ниже - списке, необходимо массив формировать прям во внутрь описания."
  ],
  "list": [
    "Элемент списка 1",
    "Элемент списка 2",
    "...",
    "Тут ещё может быть куча элементов",
    "А эта строка не будет выведена, так как длина текста превышает описанные в шаблоне условия."
  ],
  "input": "Текст поля"
}
settings.ts - Параметры парсера // (раскрыть/закрыть)
export const settings: PripravaInputSettingsInterface = {
  mode: {
    default: PripravaModesEnum.ignore,
    if: PripravaModesEnum.inherit,
    for: PripravaModesEnum.break,
    template: PripravaModesEnum.inherit,
  },
  interpolation: /\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g,
  showStatus: false,
};

import Priprava from 'priprava';
import template from './template.json';
import data from './data.json';
import { settings } from './settings';

const priprava = new Priprava({
  data,
  settings,
});

const parsed = priprava.parse({
  data: template,
});

if (!parsed) {  // undefined
  console.log('Результат парсинга - ничего');
}

console.log(parsed.data);  // см. в пример результата
parsed - Результат парсинга // (раскрыть/закрыть)
{
  content: [
    {
      component: 'title',
      content: 'Пример объекта, который должен сформировать класс.',
    },
    {
      component: 'promo',
      content: "Осторожно, продакт-плейсмент: Сомневаетесь? Даже повара согласны, что priprava - это клёво!",
    },
    'В объектах ниже меняется только строка. Вывод, шаблонизируем его.',
    {
      component: 'text',
      content: 'Это описание компоненты текста.',
    },
    {
      component: 'text',
      content: 'Полагаю, таких параграфов может быть несколько, поэтому необходимо это всё описывать циклом.',
    },
    {
      component: 'text',
      content: 'А в объекте ниже - списке, необходимо массив формировать прям во внутрь описания.',
    },
    {
      component: 'list',
      content: [
        'Элемент списка 1',
        'Элемент списка 2',
        '...',
        'Тут ещё может быть куча элементов',
      ],
    },
    {
      component: 'input',
      content: {
        title: 'Заголовок поля',
        text: '"Текст поля" - интерполяция в строках в деле!',
        description: 'Дополнительное описание',
      },
    },
  ],
}

Параметры

Инициализация

// ... - объявление всех данные.

const priprava = new Priprava({
  data,
  settings,
});

// ...

У Priprava имеется публичный метод parse, которому нужно передать шаблон, требующий обработки:

// продолжение листинга выше:

const parsed = priprava.parse({
  data: template
});

// ... - дальнейшие действия с результатом.

Шаблонами могут являться данные любого типа. Либо парсер сможет найти в них шаблонизацию, либо не сможет. Конечно же, логичнее всего отправлять туда что-то интерфейса [key: string]: any, чтобы в парсинге был смысл.

В случае безрезультатной обработки - вернётся undefined. Если не получится найти шаблонизацию в данных, то они будут возращены в том виде, в котором были отосланы, как успешная обработка.

Про интерфейс данных, с которым работают все методы Priprava.

Распарсенный шаблон следует искать в data результата. Ссылочка выше - не просто так.

Операторы

Объявление

$temp = boolean

Флаг (true, false - не важно), указывающий парсеру на то, что данный объект является объектом шаблонизации.

Интерфейс: PripravaDescriptionInterface

Шаблонизация без шаблона - глупость, поэтому для объявления обязателен оператор template.

Пример
{
  "$temp": true,
  "template": "# ${ title }"
}
Условия

if = string | boolean

Интерфейс: PripravaIfOperatorInterface

Строка интерполируется по умолчанию, т.е. исполняется целиком.

Пример
{
  "$temp": true,
  "if": "isPromo",
  "template": "Реклама: ${ ad }"
}
Циклы

for = { item, mode, data }

Интерфейс: PripravaForOperatorInterface

  • item = string;
  • mode = "in" | "of";
  • data = string;

item - название переменной итерируемого значения,
mode - режим итерации, типа for...in или for...of,
data - ключ итерируемого объекта в сторе.

Пример
{
  "$temp": true,
  "for": {
    "item": "thisArticle",
    "mode": "of",
    "data": "articles"
  },
  "template": "## ${ thisArticle.title }",
}
Шаблоны

template = string | object

Интерфейс: PripravaTemplateInterface

По умолчанию, интерполяция строки осуществляется по формату шаблонных строк ES6 - ${...}. Однако, если вам хочется что-то другое, например Angular-овская интерполяция - {{...}}, то в настройках парсера можно задать кастомное регулярное выражение, пример: /{{([\s\S]+?)}}/g.

Пример
{
  "$temp": true,
  "template": "ES${ 3 + 3 } template string"
}

Настройки парсера

  • mode - Режимы работы парсера*
    • default - общий режим
      по умолчанию: ignore
    • if - для оператора условий
      по умолчанию: inherit
    • for - для оператора цикла
      по умолчанию: break
    • template - для оператора шаблона
      по умолчанию: inherit
  • interpolation - RegExp интерполяции в строке шаблона
    по умолчанию: /\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g - эт тип так будет: 'ES${ 3 + 3 } template strings' -> 'ES6 template strings'
  • showStatus - Вывод логов (их почти нет, а те что есть - бесполезные)
    по умолчанию: false

* значения режимов задаются enum-ом PripravaModesEnum:

  • ignore - игнорирование исключительных ситуаций, "делаем, как можем";
  • break - переход к следующему объекту, "кладём на текущий";
  • inherit - наследование режима от mode.default.

Инпут-Аутпут Дата-Сеттингс (IO-DS)

Решил замутить свой паттерн, на основе RORO. Суть в том, что методы принимают и возвращают объект следующего вида:

type InputInterface<
  Data = unknown,
  Settings = undefined
> = {
  /** Входные данные */
  data: Data;
  /** Опции */
  settings?: Settings;
};

Обязательность settings определяется передачей дженерику интерфейса Settings (2-ой).

Исключения
  • Правило не распространяется на статичные методы;
  • Методы могут возвращать undefined, вместо описанного объекта.

Кейс применения

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

А может случиться так, что на каком-то серваке нужен первый сервис старой версии, тогда при маппинге в .js/.ts потребуется внесение обновлений в старую версию, так и в свежую.

Версионирование

Соблюдение SemVer не гарантируется, однако постараюсь. Но лучше фиксируйте версию.

Сорс, так-то, открытый, а вот полную гит-историю никто не обещал. Сорри. На GitHub-е с версии 0.4.1. История до того - в GitLab-е.

Автор

  • Саид Магомедов - Said-M // vk

Если у кого-то возникнет желание поконтрибьютить немного - welcome.

Лицензия

Данный проект распространяется по MIT License - текст лицензии можно найти в файле LICENSE.


ENglish

Template engine for describing JSON with dynamic data.

Getting Started

Installation

yarn add priprava

Usage

Defining

Receive-Return

My own pattern is used - IO-DS, based on RORO. Methods receives and returns object.
The rule does not apply to:

  • static methods;
  • undefined can be returned.

Author

License

This project is licensed under the MIT License.