ktav 0.3.1

Ktav — a plain configuration format. Three rules, zero indentation, zero quoting. Serde-native.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
# 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

| Свойство                                             | JSON | YAML | TOML | 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`:

| Bump              | Смысл                                                                                              |
|-------------------|----------------------------------------------------------------------------------------------------|
| `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).