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

ELDF — новый текстовый формат данных (альтернатива JSON)

Время на прочтение6 мин
Количество просмотров7.5K
Файл AppData\Local\Dropbox\info.json:
{
    "personal": {
        "host": 5060852864,
        "is_team": false,
        "subscription_type": "Basic",
        "path": "C:\\Users\\DNS\\Dropbox"
    }
}
В новом формате выглядит так:
 
personal
    host = 5060852864
    is_team = 0B
    subscription_type = Basic
    path = C:\Users\DNS\Dropbox


Конфигурационный файл редактора Sublime Text:
{
    "added_words":
    [
        "plugin",
        "habrahabr",
        "закомментированным"
    ],
    "default_line_ending": "unix",
    //"font_face": "Fira Code",
    "font_size": 11,
    "ignored_packages":
    [
        "Sublimerge Pro",
        "Vintage"
    ],
    "ignored_words":
    [
        "utf"
    ],
    "line_numbers": false,
    "show_encoding": true,
    "show_line_endings": true,
    "tab_size": 4,
    "theme": "Default.sublime-theme"
}
В новом формате выглядит так:
 
added_words = [
    plugin
    habrahabr
    закомментированным
]

default_line_ending = unix
//font_face = Fira Code
font_size = 11
ignored_packages = [
    Sublimerge Pro
    Vintage
]

ignored_words = [
    utf
]

line_numbers = 0B
show_encoding = 1B
show_line_endings = 1B
tab_size = 4
theme = Default.sublime-theme


Немного истории


Данный формат обязан своим появлением другому формату со странным названием blockpar.
Blockpar разработал Алексей Дубовой (один из основателей Elemental Games) в процессе работы над игрой Космические рейнджеры. Впоследствии Александр Зеберг (бывший ведущий программист Katauri Interactive [после "распада" компании на Katauri Interactive и Elemental Games он ушёл в Katauri]) решил использовать данный формат для игры King's Bounty: Легенда о рыцаре.

Определение каждого игрового объекта хранилось в формате blockpar в отдельном файле с расширением .atom, например вот вырезка из файла data/data.kfs/spider.atom:
main {
  class=chesspiece
  model=spider.bma
  cullcat=0
}
arena_params {
  race=neutral
  cost=24
  level=1
  leadership=14
  attack=4
  defense=4
  ...
  resistances {
    physical=20
    poison=0
    magic=0
    fire=-10
  }
  ...
}
...

Впоследствии [во время работы над проектом Royal Quest] я немного расширил этот формат, чтобы можно было вместо:
button {
    name=close
    pos=400,600
    size=200,40
    image=button_close.png
    anchors=0,0,100,0
}
писать так:
button=close,400,600,200,40 {
    image=button_close.png
    anchors=0,0,100,0
}

Также я добавил поддержку многострочных строковых значений посредством обратного апострофа (backtick — `):
button=... {
    onmouseover=`
    ...
    `
}
Почему я отказался от `строк, заключённых в обратные апострофы`
В значении параметров допустимо непосредственно вставлять код скриптов, а в комментариях в коде я использую обратные апострофы в том же значении, как они применяются в Markdown и пк-разметке. Например:
if len(indentation_levels) and indentation_levels[-1][0] == -1: # сразу после символа `{` идёт новый произвольный отступ, который действует вплоть до парного символа `}`

И наконец в результате моего знакомства с Python-ом идея отказа от фигурных скобок настолько захватила меня, что я решил, что формат blockpar можно ещё больше упростить [отказавшись от обязательных фигурных скобок].

Также влияние на меня оказали:
  • Текстовый формат хранения в Yet Another Serialization Library, использующейся в клиенте Royal Quest.
  • Формат файла конфигурации nginx (впрочем, я отбросил идею отказаться от разделителя (символа = или :) между именем параметра и его значением (почему)).
  • YAML (а именно идея использовать . перед именем элемента массива [в YAML используется -]).

Почему 0В и 1В?
  • Зачастую true/false используются в значении yes/no (YES/NO используется в Objective-C), on/off или enable/disable (например: you can enable show line endings; turn logging on/off; is digit? yes), а в булевой алгебре используются 0 и 1, так что использование ключевых слов true и false в большинстве случае довольно спорно.
  • Мне не нравится истина/ложь в русской версии формата, а 0В и 1В (здесь В — русская заглавная в) можно связать с 0Выключено и 1Включено. [Вопрос о целесообразности русской версии прошу не поднимать.]
  • 0В и 1В используются в языке программирования 11l по причинам обозначенным в документации.

Строки в одиночных парных кавычках


Ещё один [помимо 0В и 1В] спорный/непривычный элемент данного формата — это использование парных кавычек ‘’ для сырых строк [без управляющих последовательностей \ escape sequences].
Но мой выбор оправдывает тот факт, что Консорциум Юникода утвердил этот год — кодом открывающей одиночной парной кавычки.

Как такие кавычки набирать на клавиатуре — смотрите здесь.

Если в строке присутствуют непарные кавычки, тогда нужно выполнить "балансировку строки" аналогично тому, как это делается в пк-разметке для вставки HTML-кода.
Например есть строка don’t.
Так как в ней присутствует несбалансированная закрывающая кавычка, добавим балансирующую открывающую кавычку в самое начало строки: don’t.
Сбалансированную строку заключаем в парные кавычки: ‘don’t.
Теперь необходимо как-то показать парсеру, что добавленную слева кавычку не следует включать в строку, так как она нужна только для восстановления баланса. Для этого используется символ машинописного апострофа ', который нужно поставить по одной штуке на каждую балансирующую кавычку [таким образом, один машинописный апостроф "съедает" одну парную кавычку], в данном случае его необходимо поставить в начало строки: '‘‘don’t’.
Сбалансированную строку можно как есть вставлять в другие строки в парных кавычках:
‘text = '‘‘don’t’’.

Использование


В данный момент есть реализация на Python и на JavaScript (можно попробовать cконвертировать JSON в новый формат прямо в браузере на веб-странице проекта).

Для Python — устанавливаем как обычно:
pip install eldf

Для JavaScript:
npm install eldf
node
const eldf = require('eldf');

И используем:
  • eldf.to_eldf(object, indent = 4) для получения строки в формате ELDF соответствующей переданному объекту (аналог json.dumps и JSON.stringify).
  • eldf.parse(str) для получения объекта из строки в формате ELDF (аналог json.loads и JSON.parse).

В заключение приведу ещё несколько примеров:
Несколько строчек из моего Default (Windows).sublime-keymap:
[
{ "keys": ["f4"], "command": "f4" },
{ "keys": ["shift+f4"], "command": "f4", "args": {"shift_key_pressed": true} },
{ "keys": ["alt+shift+`"], "command": "insert", "args": {"characters": "`"} }, // (
{ "keys": [":",      ")"], "command": "insert_snippet", "args": {"contents": ":)(:"} },

{ "keys": ["alt+9"], "context": [{"key": "selector", "operator": "equal", "operand": "text.pq"}], "command": "insert_pq" }, // ‘ (for balance)
{ "keys": ["alt+0"], "context": [{"key": "selector", "operator": "equal", "operand": "text.pq"}], "command": "insert", "args": {"characters": "’"} },
]
С использованием нового формата я бы записал так:
f4 = on_f4()
shift+f4 = on_f4(shift_key_pressed' 1B)
alt+shift+` = insert(characters' ‘`’) // (
:,) = insert_snippet(contents' ‘:)(:’)

alt+9 = if selector == ‘text.pq’ {insert_pq()} else 0B // ‘ (for balance)
alt+0 = if selector == ‘text.pq’ {insert(characters' "’")} else 0B

Кусочек из файла d.json [из репозитория менеджера плагинов для Sublime Text]:
{
    "schema_version": "3.0.0",
    "packages": [
        {
            "name": "Django Lookup Snippets",
            "details": "https://github.com/icycandle/sublime-django-lookup",
            "releases": [
                {
                    "sublime_text": "*",
                    "tags": true
                }
            ]
        },
        {
            "name": "Django Manage Commands",
            "details": "https://github.com/vladimirnani/DjangoCommands",
            "labels": ["Django", "python", "web", "management"],
            "releases": [
                {
                    "sublime_text": "<3000",
                    "tags": "st2-"
                },
                {
                    "sublime_text": ">=3000",
                    "tags": "st3-"
                }
            ]
        }
    ]
}
В новом формате выглядит так:
schema_version = ‘3.0.0’
packages = [
.   name = Django Lookup Snippets
    details = https://github.com/icycandle/sublime-django-lookup
    releases = [
    .   sublime_text = *
        tags = 1B
    ]

.   name = Django Manage Commands
    details = https://github.com/vladimirnani/DjangoCommands
    labels = [
        Django
        python
        web
        management
    ]
    releases = [
    .   sublime_text = <3000
        tags = st2-
    .   sublime_text = >=3000
        tags = st3-
    ]
]

Some corner cases:
{
    "a": "‘...’",
    "b": "string which ends with a space ",
    "c d": "\n",
    "e ": "3",
    "dirs": [
        ["Doc,Scans", ".t’xt"]
    ],
    "node": null,
    "n" : "N",
    "files": [],
    "f": "[]",
    "ff": [
        []
    ],
    "products": {}
}
 
a = ‘‘...’’
b = ‘string which ends with a space ’
c d = "\n"
‘e ’ = ‘3’
dirs = [
    [‘Doc,Scans’, '‘‘.t’xt’]
]
node = N
n = ‘N’
files = []
f = ‘[]’
ff = [
    []
]
products {}

Теги:
Хабы:
Всего голосов 46: ↑15 и ↓31-16
Комментарии76

Публикации

Истории

Работа

Data Scientist
85 вакансий
React разработчик
55 вакансий
Python разработчик
133 вакансии

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

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