# Ktav (כְּתָב)
**Languages:** [English](README.md) · **Русский** · [简体中文](README.zh.md)
> Простой формат конфигурации. Форма JSON — скаляры, массивы, объекты,
> `null`, `true`, `false` — без пунктуации JSON. Никаких кавычек
> вокруг строк, никаких запятых, никакой таблицы escape-последовательностей.
> Точечные ключи для вложенности, видимые явные маркеры для литеральных
> и многострочных строк.
Этот репозиторий — **каноническая спецификация** формата Ktav.
Реализации на любом языке программирования должны соответствовать
той версии, на которую они ориентированы.
## На вкус
Один пример, задействующий все основные формы формата — обычный `:`
(String), ключевое слово Bool, типовые маркеры `:i` (Integer) и `:f`
(Float), сырой `::` (литеральная String), точечные ключи, вложенные
составные значения, многострочная строка.
```text
# A config for a SOCKS5 rotator.
port:i 20082
log_level: info
debug: true
banned_patterns: [
.*\.onion:\d+
.*\.local
]
upstreams: [
{
host: a.example
port:i 1080
weight:f 0.7
timeouts: {
read:i 30
write:i 10
}
}
{
host: b.example
port:i 1080
weight:f 0.3
}
]
# Dotted keys — flat alternative to nesting.
node.host: a.example
node.port:i 1080
# `::` делает строку литеральной — двоеточие внутри пароля сохраняется.
node.auth:: p@ss:word
motd: (
Welcome to the node.
Please behave.
)
```
Разбирается в следующее значение (показано в виде JSON5 — с
комментариями и без кавычек в ключах для читаемости). Обратите
внимание на соответствие маркеров:
- `:` — обычная String, на уровне Value остаётся строкой даже для
цифрового содержимого (`log_level: "info"`,
`banned_patterns[0]: "…"`).
- `: true` / `: false` / `: null` — ключевые Bool / Null.
- `:i` — Integer как нативное JSON-число.
- `:f` — Float как нативное JSON-число (с десятичной точкой).
- `::` — сырая String, классификация не применяется.
```json5
{
port: 20082,
log_level: "info",
debug: true,
banned_patterns: [
".*\\.onion:\\d+",
".*\\.local",
],
upstreams: [
{
host: "a.example",
port: 1080,
weight: 0.7,
timeouts: { read: 30, write: 10 },
},
{
host: "b.example",
port: 1080,
weight: 0.3,
},
],
node: {
host: "a.example",
port: 1080,
auth: "p@ss:word",
},
motd: "Welcome to the node.\nPlease behave.",
}
```
### Без `:i` / `:f` — числа остаются строками
Формат никогда не угадывает типы по виду содержимого; типизация
делается явно, по желанию. Без маркера каждый скаляр на уровне Value
— это String. Потребители, которым нужны нативные числа, либо
помечают значение `:i` / `:f`, либо приводят типы на своей границе
(Rust + serde делает это автоматически через `FromStr`).
```text
retries: 3
version: 1.2
ratio:f 0.5
count:i 42
```
```json5
{
retries: "3", // обычный `:` — String
version: "1.2", // обычный `:` — String
ratio: 0.5, // :f — нативное JSON-число
count: 42, // :i — нативное JSON-число
}
```
### С `::` — ключевые слова и скобки как обычные строки
Body, который иначе был бы классифицирован как ключевое слово
(`null`, `true`, `false`), пустое составное (`{}`, `[]`) или начало
составного (`{`, `[`), требует сырого маркера `::`, чтобы стать
обычной String.
```text
# Без `::` было бы Bool true — здесь это строка "true".
on_release:: true
# Начинается с `[` — `::` предотвращает "открытие массива".
regex:: [a-z]+
# IPv6-адрес — та же причина.
ipv6:: [::1]:8080
# Ключевое слово `null` как литеральная четырёхсимвольная строка.
placeholder:: null
```
```json5
{
on_release: "true",
regex: "[a-z]+",
ipv6: "[::1]:8080",
placeholder: "null",
}
```
## Девиз
> **Будь другом конфига, а не его экзаменатором. Конфиг неидеален —
> но он лучший из возможных.**
Каждое правило локально. Каждая строка либо стоит сама по себе, либо
зависит только от явных, видимых скобок выше. Никаких ловушек с
отступами, никаких забытых кавычек, никакой арифметики замыкающих
запятых.
## Зачем Ktav
| Строки без обязательных кавычек | ✗ | ~ | ✗ | ✓ |
| Списки без разделителей (никаких запятых) | ✗ | ✓ | ✓ | ✓ |
| Нечувствительность к пробелам / отступам | ✓ | ✗ | ✓ | ✓ |
| Удобные для человека многострочные строки | ✗ | ~ | ~ | ✓ |
| Встроенные комментарии | ✗ | ✓ | ✓ | ✓ |
| Точечные ключи для плоских правок | ✗ | ✗ | ✓ | ✓ |
| Один парсер, небольшая спецификация | ✓ | ✗ | ~ | ✓ |
> ✓ = свойство есть · ✗ = нет · ~ = частично
Ktav сохраняет форму JSON (вы всегда знаете, что означает документ),
но отбрасывает синтаксис, делающий JSON враждебным для ручного
написания. Сохраняет точечные ключи TOML (удобно для плоских правок
и CLI-переопределений), но отказывается от двухмерного деления TOML
на таблицы и inline.
## Правила на одном экране
Документ Ktav — это неявный объект верхнего уровня. Внутри любого
объекта — пары; внутри любого массива — элементы.
```text
# comment — any line starting with '#'
key: value — scalar pair; value is a String (default)
key:: value — scalar pair; value is ALWAYS a literal string
key:i value — scalar pair; value is an Integer (digits only)
key:f value — scalar pair; value is a Float (needs decimal)
key: { ... } — multi-line object; `}` closes on its own line
key: [ ... ] — multi-line array; `]` closes on its own line
key: {} / key: [] — empty compound, inline
key: ( ... ) — multi-line string; common indent stripped
key: (( ... )) — multi-line string; verbatim (no stripping)
:: value — inside an array: literal-string item
:i value — inside an array: Integer item
:f value — inside an array: Float item
```
Это весь язык. Никаких запятых, никаких кавычек, никакой таблицы
escape-последовательностей — единственный «escape» это маркер `::`,
и он живёт в разделителе (для пар) или в префиксе строки (для
элементов массива).
### Точечные ключи
Ключами могут быть точечные пути. Эти два документа *идентичны*:
```text
server.host: 127.0.0.1
server.port: 8080
```
```text
server: {
host: 127.0.0.1
port: 8080
}
```
Точечные ключи свободно сочетаются с вложенной формой. Парсер строит
одно и то же дерево в обоих случаях. Полезно для:
- CLI/env-переопределений (`--set server.port=9090`).
- Частичных правок в небольших конфигах без реструктуризации.
- Файлов с плоской основой, обрастающих вложенными секциями по мере
необходимости.
### Строки напрямую
Значение по умолчанию — строка. То, что идёт после `:` (через один
пробел отступа), и есть строка, посимвольно, до конца строки.
Отсутствие кавычек означает отсутствие правил кавычек — пути, URL,
регулярки, токены с пунктуацией просто работают.
```text
pattern: .*\.onion:\d+
url: https://example.com:8080/path?x=1
key: s3cret/with:colons and-dashes
```
Когда строка конфликтовала бы с грамматикой (начинается с `{`, `[`,
`(` или равна ключевому слову вроде `true`), префиксуйте разделитель
через `::`:
```text
literal_bracket:: [
keyword_as_string:: true
```
### Числа, когда они нужны
По умолчанию значение, выглядящее как число, — строка: `port: 8080`
даёт вам `"8080"`. Потребители на типизированных языках (Rust +
serde, Go) сами кастуют это к настоящему числу на своей границе,
без содействия формата.
Для потребителей на динамических языках (JS, PHP, Python) выбирайте
типовые значения через `:i` (Integer) или `:f` (Float):
```text
port:i 8080
ratio:f 0.5
offset:i -100
eps:f 1.5e-10
```
Значения по-прежнему сохраняются в текстовой форме на уровне Value
— `Integer("8080")`, `Float("0.5")` — так что 40-значные целые
переживают round-trip, а `1.2` никогда не окажется случайно
приведённым к Number. Потребитель сужает до нативного типа, какой
ему нужен.
### Многострочные строки
Две формы, разные цели:
```text
stripped: (
line 1
line 2
relative indent preserved
)
verbatim: ((
line 1
exact leading whitespace preserved
line 3
))
```
`(` срезает общий ведущий отступ — пишите код/текст, который *читается*
в файле хорошо, а значение получается чистым. `((` сохраняет каждый
байт, так что документ round-trip'ится байт-в-байт.
### Ключевые слова
Только в нижнем регистре: `null`, `true`, `false`. Всё остальное —
`Null`, `TRUE`, `yes`, `on` — обычная строка. Никакого волшебного
приведения типов, никакого зависящего от версии списка ловушек.
```text
port: 8080
active: true
timeout: null
```
```json5
{
port: "8080", // обычный `:` — String, не число
active: true, // ключевое слово → нативный JSON bool
timeout: null, // ключевое слово → нативный JSON null
}
```
## Полная спецификация
- **Текущая стабильная:** [Ktav 0.1.0](versions/0.1/spec.ru.md) — выпущена 2026-04-22.
- **Машиночитаемый индекс** всех версий: [`versions.ktav`](versions.ktav).
- **История версий:** [`CHANGELOG.md`](CHANGELOG.md).
## Набор тестов соответствия
Каждая версия поставляется с языконезависимым набором тестов в
[`versions/<v>/tests/`](versions/0.1/tests/). Пары
`<name>.ktav` + `<name>.json` — `.json` это ожидаемый `Value`,
отображённый 1:1 для обычных скаляров (`Null`→`null`,
`Bool`→`bool`, `String`→`string`, `Array`→`array`,
`Object`→`object`; числовые body обычных `:`-пар остаются
строками на уровне Value). Типовые скаляры (`:i` / `:f`)
кодируются как нативные JSON-числа — `8080` для Integer,
`0.5` для Float — и различаются наличием десятичной точки.
Полный оракул см. в
[`versions/0.1/tests/README.ru.md`](versions/0.1/tests/README.ru.md).
Порядок полей объекта значим.
Реализация соответствует версии, если проходит каждый тест из
набора этой версии. Подключайте директорию как git submodule (или
копию); см. [`versions/0.1/tests/README.ru.md`](versions/0.1/tests/README.ru.md).
## Схема версионирования
Версии спецификации используют `MAJOR.MINOR.PATCH`:
| `x.y → x.y.(z+1)` | Редакторский — исправления опечаток, уточнения; соответствующие реализации не затрагиваются. |
| `x.y → x.(y+1)` | Обратно совместимое расширение (новое ключевое слово, новая примитивная форма). |
| `x.y → (x+1).0` | Ломающее изменение грамматики или семантики. |
Внутри любого стабильного `MAJOR` реализация, ориентированная на
`x.0`, MUST разбирать каждый документ, валидный при любом более
позднем `x.y.z`, идентично — в пределах подмножества, которое она
поддерживает.
Директория каждой версии полностью самодостаточна: `spec.md`, набор
соответствия `tests/` и добавления по версиям. Реализации
привязываются к директории версии по пути.
## Структура
```
.
├── README.md this file
├── versions.ktav machine-readable index of versions
├── CHANGELOG.md summary across versions
├── CONTRIBUTING.md how to propose changes
├── LICENSE MIT
└── versions/
└── <version>/
├── spec.md the specification document
└── tests/ language-agnostic conformance suite
├── README.md
├── valid/
└── invalid/
```
## Реализации
| Rust (эталонная) | [`ktav-lang/rust`](https://github.com/ktav-lang/rust) | `cargo add ktav` |
| C# / .NET | [`ktav-lang/csharp`](https://github.com/ktav-lang/csharp) | `dotnet add package Ktav` |
| Go | [`ktav-lang/golang`](https://github.com/ktav-lang/golang) | `go get github.com/ktav-lang/golang` |
| Java / JVM | [`ktav-lang/java`](https://github.com/ktav-lang/java) | `io.github.ktav-lang:ktav` на Maven Central |
| JS / TS | [`ktav-lang/js`](https://github.com/ktav-lang/js) | `npm install @ktav-lang/ktav` |
| PHP | [`ktav-lang/php`](https://github.com/ktav-lang/php) | `composer require ktav-lang/ktav` |
| Python | [`ktav-lang/python`](https://github.com/ktav-lang/python) | `pip install ktav` |
Rust crate — эталонный парсер; каждый из остальных биндингов поставляет
сборку `ktav_cabi` (C-ABI обёртка) и предоставляет тот же Ktav 0.1
интерфейс — language-agnostic набор `tests/` ниже прогоняется на всех
из них при каждом релизе.
Строите новую реализацию? Начните со `spec.md` целевой версии
([`spec.ru.md`](versions/0.1/spec.ru.md), раздел 8 — Compliance)
и прогоните набор [`tests/`](versions/0.1/tests/) через свой парсер.
## Вклад
Редакторские правки в существующую версию — PR напрямую. Всё
остальное — сначала открывайте issue. См.
[`CONTRIBUTING.ru.md`](CONTRIBUTING.ru.md).
## Поддержите проект
У автора много идей, которые могут быть полезны IT во всём мире, — и
далеко не только для Ktav. Их реализация требует финансирования. Если
вы хотите помочь — пишите на **phpcraftdream@gmail.com**.
## Лицензия
MIT. См. [LICENSE](LICENSE).