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

от автора

Файл 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] == None: # сразу после символа `{` идёт новый произвольный отступ, который действует вплоть до парного символа `}` 

И наконец в результате моего знакомства с 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В] спорный/непривычный элемент данного формата — это использование парных кавычек ‘’ для сырых строк [без управляющих последовательностей \ escape sequences].
Но мой выбор оправдывает тот факт, что Консорциум Юникода утвердил этот год — кодом открывающей одиночной парной кавычки.

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

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

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

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

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

pip install thindf 

Для JavaScript:

npm install thindf node const thindf = require('thindf'); 

И используем:

  • thindf.to_thindf(object, indent = 4) для получения строки в формате thindf соответствующей переданному объекту (аналог json.dumps и JSON.stringify).
  • thindf.parse(str) для получения объекта из строки в формате thindf (аналог 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 = {}  


ссылка на оригинал статьи https://habr.com/post/419361/


Комментарии

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

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