Комментарии 13
Выглядит симпатично.
Один парсер, один источник правды, и тест-сьют, который доказывает, что все биндинги согласны.
А что, если, вместо биндингов через FFI сделать транслятор в JSON. А там пусть родные библиотеки парсят. Тестами покрыть все краевые случаи, что бы убедиться, что транслятор правильно работает. В итоге получаем не прям новый 15ый стандарт, а просто упрощённый способ описания json.
Спасибо, приятно слышать!
По сути это уже встроено - только как метод тестирования, не как продукт. Тест-спека - это пары “вход на Ktav → ожидаемый JSON”: https://github.com/ktav-lang/spec/tree/main/versions/0.6/tests/valid Берёте любой .ktav - рядом его .json-эквивалент. Транслятор, покрытый тестами на все краевые случаи, - это и есть наш эталонный Rust-парсер.
Почему всё же выбрал FFI в 7 языков, а не “отдай JSON нативному парсеру” - довод один, про скорость. Цель была - парсинг вровень с serde_json; по факту медленнее раза в два, но это наносекунды, я считаю результат хорошим. А вот лишний проход через JSON-текст (сериализовать → распарсить второй раз) - уже отдельная стоимость поверх, которая на горячем пути (частые перечитывания, много мелких конфигов) будет заметна. На конфиге, который читается раз при старте, - без разницы.
И обычно: “транслятор + родной парсер” - ровно то, что делают на старте нового формата, и это хорошая стратегия при ограниченности ресурсов. Я сделал наоборот - вложился в 7 FFI-биндингов за полтора месяца, до первого внешнего пользователя. Риск, чего уж там.
Лёгкий транслятор в чистый JSON отдельно от биндингов - хорошая идея, завёл issue: https://github.com/ktav-lang/spec/issues/2. В следующих версиях рассмотрю подробнее как с сней быть.
И да: без всего, что Ktav даёт сверх модели JSON, формулировка была бы именно такой - не 15-й стандарт, а просто приятный способ печатать JSON руками)
Вообще непонятно в чем отличие от JSON, точнее JSONC. Было бы неплохо увидеть таблицу сравнения хотя бы с JSON и проблем разного рода. Например надо вставить в конфиг дефолтный приватный ключ для тестового окружения, в JSON он выглядит так, в ktav он выглядит так.
Или например, ktav для решения этой проблемы использует include с файлом с приватным ключом. Или проблема с установкой log_level при ручном запуске, и что-то типа такого в конфиге ENV('LOG_LEVEL', 'info'), тоесть при ручном запуске просто устанавливаем LOG_LEVEL в переменных окружения, если ничего не выставлено то дефолт info.
Спасибо, что заглянули! Видимо в статье разница с JSONC не бросилась в глаза. Есть сравнительаня таблица на сайте - ktav-lang.github.io (в комментарий пока не разобрался как вставлять таблицы), опишу словами:
JSONC - это JSON плюс комментарии, и всё, остальное как в обычном JSON: кавычки на ключах и строках обязательны, запятые обязательны.
В Ktav иначе: кавычки на ключах и строках не нужны вообще (кроме ::, когда явно хочешь застолбить строку), запятые нужны только если пишешь массив или объект в одну строку, комментарии свои - ##, а для многострочного текста есть отдельный блок в скобках вместо \n внутри одной строки в кавычках.
На примере с приватным ключом. В JSON/JSONC он склеен в одну строку через \n:
{
// тестовый ключ, не для прода
"private_key": "-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKC...\n-----END RSA PRIVATE KEY-----"
}
В Ktav - отдельный блок, построчно как есть:
## тестовый ключ, не для прода
private_key: (
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKC...
-----END RSA PRIVATE KEY-----
)
Include с файлом секрета и ENV(‘LOG_LEVEL’, ‘info’) внутри конфига - сознательно не входит в формат. Ktav - только слой данных, без include/expressions/interpolation, как и JSON с TOML: добавишь интерполяцию - формат превращается в мини-язык со своей семантикой выполнения.
Стремился к минимализму - “Ktav простой и хороший, он не совершенный, но лучший для конфигов”. Получилось сделать его таким или нет - покажет время.
Возможно параллельно будет какой-нибудь ktav_plus - со ссылками, переменными и прочим, но основной формат останется в минимализме, как json
Попробуйте как-то создать конфигурацию эдак на 3-4 тыс ключей (а лучше больше), и для 3-х окружений: dev, prod, test, причем для dev/test должны быть какие-то дефолтные креды для внешних сервисов и настройки (dev и test должны тоже отличаться, dev это локальная разработка, test это какой-то поднятый сервер для qa), а для prod такие креды которые не должны попасть в git. И сразу будет понятно все недостатки и ограничения Вашего решения. Пока из приятного только многострочные значения, кавычки и запятые вкусовщин. Да можно сказать что этого всего нету и в JSON/YAML/TOML и include/expressions/interpolation и логику merge надо будет городить руками, но при этом всем среди простых форматов у JSON явное преимущество из-за наличия схем.
Спасибо за подробный контр-аргумент - это полезнее общей критики, потому что чётко очерчивает границу применимости.
И да, вы правы: на 3-4 тысячах ключей с тремя окружениями и разделением секретов Ktav вам ничего не даст сверх того, что дают JSON/YAML/TOML. Формат этого не решает и не пытается. Он про другое - про конфиг, который человек пишет и читает руками: сервис, CLI, небольшое приложение. Там, где конфигурация становится системой со своим наследованием, оверлеями и секрет-менеджментом, нужен не формат, а инструмент: Helm/Kustomize, CUE, Consul, Vault. Выбор формата на таком масштабе - вопрос десятый.
Про include / interpolation / merge - это сознательно вне формата, а не недоделка. Как только внутрь синтаксиса заходит include или подстановка переменных, файл перестаёт читаться сам по себе, и появляется порядок вычисления, который надо описывать и реализовывать одинаково во всех семи биндингах. Мы решили, что этот слой живёт в приложении: конфиг из файла, env поверх, секреты инжектятся отдельно. Скучно, зато предсказуемо.
Про схемы вы попали в реальный пробел - своего языка описания схем у Ktav нет, и это честное ограничение.
Но одно уточнение: модель данных у Ktav ровно JSON-овская - те же скаляры, массивы, объекты, null, bool, без тегов и якорей. Поэтому JSON Schema применима к результату разбора: распарсили .ktav в структуру и валидируйте тем же валидатором, что и для JSON. Отдельная схема под формат не нужна - преимущество JSON здесь наследуется, а не теряется.
Про вкусовщину спорить не буду. Кавычки и запятые действительно вопрос привычки, и если они вам не мешают - формат вам не нужен, это нормальный исход. Он вырос из личного раздражения, а не из уверенности, что так лучше всем, и 20 лет за клавитурой накопили разражение в бесконечных танцах пальцев с шифтами)
Крутой мини продукт. Выглядит очень приятно. И тут, наверное, главный плюс. Вы не пытаетесь сказать, что это удобно и полезно везде и всегда.
Конкретная боль - кросс-языковые команды. В этом случае все рассуждения о том, что для того же Go решение наверное не самое идиоматичное, надо доставлять .so рядом, думать про musl/alpine в докере, про комбинации OS/arch и.т.д. это осознанная цена за консистентность.
Единственное, чего мне не хватило бы, доп. синтаксиса для явного указания типа. У вас уже есть :: для форс-строки, и напрашивается то же самое для остальных типов, условные ::int и ::float. Защита от дурака для критичных переменных, где типизация по форме может поехать, плюс ревью конфигов становится проще: тип виден глазами, а не выводится в голове.
Пробежался по репо и заметил одну деталь, тихую канонизацию числовых скаляров при выводе типов (1.10 -> 1.1, 01234 -> 1234). Я с подобным сталкивался в работе, поэтому решил помочь и подготовил issue и PR. Ознакомьтесь, может будет полезным.
Issue: https://github.com/ktav-lang/rust/issues/1
PR: https://github.com/ktav-lang/rust/pull/2
Спасибо - и отдельное спасибо за ПР, это редкий жанр комментария.
Про кросс-языковые команды верно, цена именно такая, как вы описали: .so рядом и прочее. Платим её сознательно, ради одинакового поведения везде и производительности Rust.
PR влит, релиз для Rust опубликован - 0.6.2: https://crates.io/crates/ktav
Теперь про ::int / ::float. Идея понятна и ложится на существующий синтаксис, но в 0.5.0 мы убрали ровно такие маркеры - были :i и :f.
Главная причина - типизированные языки всё равно сами решают, какого типа получать значение. Целевой тип задаёт структура, в которую десериализуют, а не документ: serde в Rust, struct в Go, класс в C# приведут port к u16 независимо от того, как парсер классифицировал скаляр. Маркер в конфиге дублировал то, что уже объявлено в коде, и добавлял вопрос, что делать, когда он противоречит форме.
Плюс ощущение, что для конфигов небольшого ПО текущих возможностей хватает - формат задумывался именно для удобных конфигураций, а не для передачи данных.
Отчасти ту же боль закрывает как раз ваш parse_strict, только с другой стороны: он не даёт форме молча поехать.
Благодраю за участие! Очень рад первому ПР.
У вас на сайте в INI ошибка - так и задумано?

Посмотрите plist из Common Lisp или Clojure, где ключи - keywords.
Не совсем то, что у вас, но семантически близко.
Спасибо, мысль хорошая - keywords действительно про то же самое: ключ самоозначающий, кавычки ему не нужны.
Если продолжить её и поставить форматы рядом, то ближайшим соседом по внешнему виду окажется, наверное, всё-таки JSON5 - там тот же порядок “ключ двоеточие значение” и ключи тоже без кавычек:
{ host: 'a.example', port: 1080 }
В plist и EDN двоеточие переезжает влево и превращает сам ключ в keyword, а пары разделяются пробелами внутри s-выражения:
(:host "a.example" :port 1080)
Но любопытно, что plist и JSON5 при этом роднит ровно то же самое, что вы заметили: ключ не нуждается в кавычках. Похоже, до этой мысли рано или поздно доходит каждый, кто делает формат для чтения человеком, - просто доводят её по-разному.
Ktav дотянул её на шаг дальше и снял кавычки и со строковых значений тоже, а структуру оставил построчной, без объемлющих скобок:
host: a.example
port: 1080
Так что, да - “не совсем то, но семантически близко”)
Заинтересовало и возник вопрос
У вас на сайте есть демо пример. Я немного изменил его
{
"log_level": "info",
"log_level1": "true",
"log_level2": true,
"debug": true
}
на выходе
log_level: info
log_level1:: true
log_level2: true
debug: true
log_level2 и debug оба boolean, но записались по разному
PS Все понял :: это наоброт переводить в строку

Психанул: как я за полтора месяца сделал формат конфигов и парсеры для семи языков