From af89d8bcdda4023484abf71519ece855e430d960 Mon Sep 17 00:00:00 2001 From: Marat K Date: Sun, 25 Oct 2026 22:22:38 +0100 Subject: [PATCH] =?UTF-8?q?docs:=20=D1=80=D0=B0=D1=81=D0=BA=D1=80=D1=8B?= =?UTF-8?q?=D1=82=D1=8C=20source-edit=200.9=20=D0=BD=D0=B0=20EN/RU/ZH?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Добавить восемь практических примеров, точные byte diagnostics и правила безопасного отказа. Сохранить source units и generated README/changelog согласованными; версию и runtime API не менять. Co-Authored-By: Claude Opus 5.5 --- .gitattributes | 3 + CHANGELOG.md | 4 + CHANGELOG.ru.md | 4 + CHANGELOG.zh.md | 4 + README.md | 152 ++++++++++++++++++++ README.ru.md | 152 ++++++++++++++++++++ README.zh.md | 152 ++++++++++++++++++++ root-docs/CHANGELOG/unreleased/body-1.md | 12 ++ root-docs/README/diagnostics09/body-1.md | 45 ++++++ root-docs/README/diagnostics09/meta.js | 7 + root-docs/README/editing-cookbook/body-1.md | 87 +++++++++++ root-docs/README/editing-cookbook/body-2.md | 90 ++++++++++++ root-docs/README/editing-cookbook/body-3.md | 81 +++++++++++ root-docs/README/editing-cookbook/body-4.md | 87 +++++++++++ root-docs/README/editing-cookbook/body-5.md | 84 +++++++++++ root-docs/README/editing-cookbook/meta.js | 7 + root-docs/README/manifest.js | 2 + 17 files changed, 973 insertions(+) create mode 100644 root-docs/README/diagnostics09/body-1.md create mode 100644 root-docs/README/diagnostics09/meta.js create mode 100644 root-docs/README/editing-cookbook/body-1.md create mode 100644 root-docs/README/editing-cookbook/body-2.md create mode 100644 root-docs/README/editing-cookbook/body-3.md create mode 100644 root-docs/README/editing-cookbook/body-4.md create mode 100644 root-docs/README/editing-cookbook/body-5.md create mode 100644 root-docs/README/editing-cookbook/meta.js diff --git a/.gitattributes b/.gitattributes index 55da72c..a5d53bd 100644 --- a/.gitattributes +++ b/.gitattributes @@ -3,3 +3,6 @@ # on all platforms check out with LF — no more CRLF/LF noise in # `git status` from Windows clients. * text=auto eol=lf + +# Polydoc требует пустую строку в конце промежуточных source parts. +root-docs/**/body-*.md whitespace=-blank-at-eof diff --git a/CHANGELOG.md b/CHANGELOG.md index b772a8d..e4bff23 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,10 @@ For the format specification's own history, see the - Borrow literal path slices and unescaped textual segments instead of copying each segment. Assemble raw-marker prefixes and unchanged replacement bodies directly into the result; render verbatim replacements in one buffer without an intermediate EOL-converted string. Preserve exact output and full semantic verification; measure text and segment paths separately in short paired benchmarks. - Locate diagnostic line text by reverse terminator search instead of rescanning the document prefix. Probe JSON strings with safe eight-byte operations, bulk-copy unescaped UTF-8, specialize escaped ASCII and retain the Unicode encoder. Allocate reopen-merge child buffers and seen-key storage lazily. Preserve exact bytes and locations; add allocation/escaping regressions and paired runtime benchmarks with one external 15-second limit for the entire process batch. +### Documentation + +- Expand README examples for dotted and literal-key paths, mixed arrays, String/Integer identity, multiline unfolding, byte-identical no-op and BOM/CRLF/EOF preservation. Clarify 0.9 byte diagnostics, safe refusal, ABI 2 and standalone pure usage; these additions are not part of published 0.8.1. + ## [0.8.1] — 2026-10-21 ### Fixed diff --git a/CHANGELOG.ru.md b/CHANGELOG.ru.md index e0f8873..956207a 100644 --- a/CHANGELOG.ru.md +++ b/CHANGELOG.ru.md @@ -34,6 +34,10 @@ Cargo-конвенцией: до 1.0 bump MINOR считается ломающ - Буквальные сегменты пути и сегменты текстового пути без escape заимствуются без копирования каждого сегмента. Префиксы raw-маркера и тела замен, не требующие преобразования, переносятся непосредственно в результат; verbatim-замены рендерятся в один буфер без промежуточной строки с преобразованными EOL. Точные выходные байты и полная семантическая проверка сохранены; короткие парные benchmarks отдельно измеряют text- и segments-пути. - Строка диагностики находится обратным поиском терминатора без повторного прохода по префиксу документа. JSON-строки проверяются безопасными восьмибайтовыми операциями; UTF-8 без escapes переносится целиком, экранируемый ASCII обрабатывается специализированно, Unicode encoder сохраняется. Дочерние буферы и хранилища seen-ключей reopen-merge выделяются лениво. Точные байты и координаты сохранены; добавлены регрессии аллокаций/экранирования и парные runtime benchmarks с единым внешним лимитом 15 секунд на весь batch процессов. +### Документация + +- Дополнены примеры README: dotted-пути и буквальные ключи, смешанные массивы, различие String/Integer, раскрытие многострочной замены, побайтовый no-op и сохранение BOM/CRLF/EOF. Уточнены байтовая диагностика 0.9, безопасные отказы, ABI 2 и самостоятельный pure-бэкенд; дополнения не относятся к опубликованному 0.8.1. + ## [0.8.1] — 2026-10-21 ### Исправлено diff --git a/CHANGELOG.zh.md b/CHANGELOG.zh.md index b6b9f9a..7f27768 100644 --- a/CHANGELOG.zh.md +++ b/CHANGELOG.zh.md @@ -34,6 +34,10 @@ - 借用字面路径段及不含转义的文本路径段,不再逐段复制。将 raw 标记前缀及无需转换的替换正文直接写入结果;verbatim 替换使用单一缓冲区,不再生成转换 EOL 的中间字符串。保留精确输出与完整语义验证;短时配对基准分别测量文本路径和路径段 API。 - 通过反向查找换行符定位诊断行,不再重复扫描文档前缀。用安全的八字节运算检查 JSON 字符串,整体复制无需转义的 UTF-8,专门处理需转义的 ASCII,并保留 Unicode encoder。按需分配 reopen-merge 子缓冲区和 seen 键存储。保留精确字节与坐标;增加分配及转义回归测试,并提供配对 runtime 基准,整个进程 batch 共用一个外部 15 秒限制。 +### 文档 + +- 扩展 README 示例:点分路径与字面键、混合数组、String/Integer 区别、多行展开、逐字节 no-op 及 BOM/CRLF/EOF 保留。明确 0.9 字节诊断、安全拒绝、ABI 2 和独立纯实现用法;这些新增功能不属于已发布的 0.8.1。 + ## [0.8.1] — 2026-10-21 ### 修复 diff --git a/README.md b/README.md index bd72dc2..0da02d4 100644 --- a/README.md +++ b/README.md @@ -921,6 +921,158 @@ Only existing values can be replaced: no automatic container creation, removal o `EditError::InvalidPath` reports a malformed textual path and byte offset; `NotFound` covers missing keys, invalid/out-of-range array indexes and descent through a scalar. `Unsupported` rejects an invalid source, an unrepresentable replacement or a replacement for which this implementation cannot preserve the surrounding text. In particular, a whole object assembled from separate dotted/reopened definitions has no single replacement range, and replacing a container must not delete its internal comments. Its individual leaves remain editable. Inline values use escapes for CR and LF. Outside inline compounds, CR-containing strings and some context-dependent multiline/container replacements are rejected rather than silently rewritten. Every returned patch is parsed and checked against the intended value tree; errors never return a partially edited document. +### Source-edit cookbook + +These examples describe the unreleased 0.9 API, not the published 0.8.1 package. They replace existing values only: keys are not renamed, missing paths are not created, and entries are not removed. Each snippet is independent; Rust `?` assumes an enclosing `Result`-returning function. Go examples panic only for these fixed, known-valid inputs; applications should handle the returned error. + +```rust +use ktav::edit::{edit_value, edit_value_segments}; +use ktav::Value; +``` + +#### 1. Dotted deployment settings + +Only the address changes; the comment, dotted form and neighbouring user entry remain byte-for-byte unchanged. + +```rust +let source = "## deployment\ntargets.storagehost.address: old\ntargets.storagehost.user: mcp-agent\n"; +let updated = edit_value(source, "targets.storagehost.address", &Value::String("192.168.0.88:22".into()))?; +``` + +Result: + +```ktav +## deployment +targets.storagehost.address: 192.168.0.88:22 +targets.storagehost.user: mcp-agent +``` + +#### 2. Literal dots: textual path or segments + +The quoted source key is literally `a.b`. The textual path escapes its dot; the segments call passes the decoded key directly. Both calls return the same result. + +```rust +let source = "\"a.b\": {port: 80}\n"; +let updated = edit_value(source, "a\\.b.port", &Value::Integer("90".into()))?; +let same = edit_value_segments(source, &["a.b", "port"], &Value::Integer("90".into()))?; +``` + +Result: + +```ktav +"a.b": {port: 90} +``` + +#### 3. A leaf inside a mixed-form array + +`0` is an array index here. Inside an Object, the same segment would address the key named `0`; untouched containers remain inline. + +```rust +let source = "servers: [{host: old, keep: yes}, standby]\n"; +let updated = edit_value_segments(source, &["servers", "0", "host"], &Value::String("new".into()))?; +``` + +Result: + +```ktav +servers: [{host: new, keep: yes}, standby] +``` + +#### 4. A numeric-looking String stays a String + +The replacement is a String, not an Integer. The raw marker preserves that distinction. Supply a typed Integer when a numeric value is intended. + +```rust +let source = "port: 8080\n"; +let updated = edit_value(source, "port", &Value::String("9090".into()))?; +``` + +Result: + +```ktav +port:: 9090 +``` + +#### 5. Multiline replacement: expand only what is necessary + +The changed array expands with four-space indentation. The untouched object stays inline, and verbatim string contents are not reindented. + +```rust +let source = "a: [old, {keep: yes}]\n"; +let updated = edit_value(source, "a.0", &Value::String("x\ny".into()))?; +``` + +Result: + +```ktav +a: [ + (( +x +y + )) + {keep: yes} +] +``` + +#### 6. No-op preserves noncanonical spelling + +The Integer value is already 1. Equality returns the original bytes, including `+1` and the original separator spacing; readability thresholds are bypassed. + +```rust +let source = "n : +1\n"; +let updated = edit_value(source, "n", &Value::Integer("1".into()))?; +assert_eq!(updated, source); +``` + +Result: + +```ktav +n : +1 +``` + +#### 7. BOM, CRLF and an unterminated final line + +The returned string keeps the BOM and CRLF, and does not gain a final newline. Its escaped representation is shown to make the bytes visible. + +```rust +let source = "\u{FEFF}port: 8080\r\nhost: db"; +let updated = edit_value(source, "port", &Value::Integer("9090".into()))?; +``` + +Result: + +```text +"\uFEFFport: 9090\r\nhost: db" +``` + +Expansion is deterministic: a multiline replacement, growth beyond `max(100, original line length)`, or at least four escapes with at least 25% encoding overhead for an individual replacement string. Width counts Unicode scalar values, not UTF-8 bytes or terminal columns. An already-long line without growth is kept. New levels use four spaces and the source EOL. If CR or a `))` data line prevents lossless standalone encoding, a valid inline candidate is retained; these characters do not automatically make every inline edit unsupported. + +#### 8. Exact Integer replacement + +The source Integer domain is signed 64-bit. This value exceeds JavaScript's safe-Number range but fits i64; use an exact host representation, not a rounded floating-point value. A typed Integer outside i64 cannot be preserved as an Integer in source and is refused instead of truncated. Use a String when a large identifier is text. Float identity distinguishes negative and positive zero; Object no-op equality ignores key order, while Array order remains significant. + +```rust +let updated = edit_value("n: 1\n", "n", &Value::Integer("9007199254740993".into()))?; +``` + +```ktav +n: 9007199254740993 +``` + +### Diagnostics and safe failures in 0.9 + +Source coordinates are UTF-8 byte offsets, including a BOM; spans are half-open `[start, end)`, and lines are 1-based. LF, lone CR and CRLF are handled, with CRLF counted as one terminator. A Unicode character may occupy several bytes. + +An unfinished `a: [\n` reports `UnclosedCompound`, line 1 and span `3..5`: the span begins at the innermost unmatched opener, not the start of its key or line. For raw bytes `61 3A 20 FF` (`a: ` followed by invalid UTF-8), validation reports `InvalidUtf8`, line 1 and a covering span `3..4`; `line_text` is null rather than fabricated by lossy decoding. UTF-8 validation precedes grammar processing. + +A malformed textual path, including an empty textual path or empty textual segment, is `EditInvalidPath`; its offset is measured in the path's UTF-8 bytes. An empty segments list, missing key, out-of-range index or scalar descent is `EditNotFound`. Object numeric segments remain keys; array indexes use ASCII digits, allow leading zeros and reject signs. + +Whole dotted/reopened intermediate objects may occupy several physical ranges and are `EditUnsupported`; their existing leaves can still be edited. Replacing a container must not discard interior comments. No failed call returns a partial patch or changes the supplied source. The returned text is re-parsed and semantically checked: the target must equal the replacement and the rest of the value tree must remain equal. + +For bindings, invalid source is reported with the ordinary parser envelope before path validation. Malformed wire payloads are `Message` errors without invented source coordinates. The wire envelope has `error`, `reason`, `line`, `line_text`, `span`, `path`, `body`, `canonical`, `spec_section` and `message`; host accessor names are documented in the errors section. `body` carries the decimal path-byte offset for `EditInvalidPath`, null for `EditNotFound`, and the refusal reason for `EditUnsupported`. + +The coordinate examples describe ordinary parse errors (`Error` / `ErrorEnvelope`). Rust source editing itself returns `EditError::{InvalidPath, NotFound, Unsupported}`: `InvalidPath { offset }` refers to the path bytes, and an invalid source is wrapped as `Unsupported`. The C ABI validates the source first and exposes the ordinary source envelope to bindings. These Rust variants are not the wire envelope field layout. + ## The C ABI for other languages The Go, Java, PHP, C#, JS and Python bindings share the Rust core. The off-by-default `cabi` feature provides wire decoding, document operations and error encoding; one macro call in a binding's `cdylib` exports the C symbols. Direct Rust bridges such as PyO3 and N-API can use the same safe operations without another FFI layer. diff --git a/README.ru.md b/README.ru.md index c497e8a..e4a60ba 100644 --- a/README.ru.md +++ b/README.ru.md @@ -932,6 +932,158 @@ Dotted-ключи остаются dotted, однострочные объект `EditError::InvalidPath` сообщает об ошибочном текстовом пути и смещении в байтах; `NotFound` — об отсутствующем ключе, неверном/выходящем за границы индексе или спуске через скаляр. `Unsupported` отвергает невалидный исходник, непредставимую замену либо замену, для которой реализация не может сохранить окружающий текст. В частности, у целого объекта из отдельных dotted/reopened-определений нет единого диапазона замены, а замена контейнера не должна удалять внутренние комментарии. Отдельные листья такого объекта остаются редактируемыми. В inline-значениях CR и LF передаются escape-последовательностями. Вне inline-контейнеров строки с CR и некоторые контекстно-зависимые многострочные/контейнерные замены отвергаются, а не переписываются молча. Каждый возвращаемый результат разбирается и сверяется с ожидаемым деревом значений; ошибка не возвращает частично изменённый документ. +### Практические примеры правки исходника + +Примеры относятся к ещё не выпущенному API 0.9, а не к опубликованному пакету 0.8.1. Они заменяют только существующие значения: не переименовывают ключи, не создают отсутствующие пути и не удаляют записи. Фрагменты независимы; Rust `?` предполагает внешнюю функцию с результатом `Result`. В Go panic используется только для фиксированных корректных данных примера; приложение должно обработать возвращённую ошибку. + +```rust +use ktav::edit::{edit_value, edit_value_segments}; +use ktav::Value; +``` + +#### 1. Настройка развёртывания через dotted-ключ + +Меняется только адрес; комментарий, dotted-форма и соседнее поле пользователя сохраняются байт-в-байт. + +```rust +let source = "## deployment\ntargets.storagehost.address: old\ntargets.storagehost.user: mcp-agent\n"; +let updated = edit_value(source, "targets.storagehost.address", &Value::String("192.168.0.88:22".into()))?; +``` + +Результат: + +```ktav +## deployment +targets.storagehost.address: 192.168.0.88:22 +targets.storagehost.user: mcp-agent +``` + +#### 2. Буквальные точки: текстовый путь или сегменты + +Ключ исходника в кавычках буквально называется `a.b`. В текстовом пути точка экранируется; API сегментов получает декодированное имя напрямую. Оба вызова возвращают одинаковый результат. + +```rust +let source = "\"a.b\": {port: 80}\n"; +let updated = edit_value(source, "a\\.b.port", &Value::Integer("90".into()))?; +let same = edit_value_segments(source, &["a.b", "port"], &Value::Integer("90".into()))?; +``` + +Результат: + +```ktav +"a.b": {port: 90} +``` + +#### 3. Лист внутри массива со смешанными формами + +Здесь `0` — индекс массива. В объекте такой же сегмент обозначал бы ключ с именем `0`; нетронутые контейнеры остаются inline. + +```rust +let source = "servers: [{host: old, keep: yes}, standby]\n"; +let updated = edit_value_segments(source, &["servers", "0", "host"], &Value::String("new".into()))?; +``` + +Результат: + +```ktav +servers: [{host: new, keep: yes}, standby] +``` + +#### 4. Похожая на число строка остаётся строкой + +Замена имеет тип String, а не Integer. Raw-маркер сохраняет это различие. Для числового значения передавайте типизированный Integer. + +```rust +let source = "port: 8080\n"; +let updated = edit_value(source, "port", &Value::String("9090".into()))?; +``` + +Результат: + +```ktav +port:: 9090 +``` + +#### 5. Многострочная замена: раскрывается только необходимое + +Изменённый массив раскрывается с отступом четыре пробела. Нетронутый объект остаётся inline, содержимое verbatim-строки не переотступается. + +```rust +let source = "a: [old, {keep: yes}]\n"; +let updated = edit_value(source, "a.0", &Value::String("x\ny".into()))?; +``` + +Результат: + +```ktav +a: [ + (( +x +y + )) + {keep: yes} +] +``` + +#### 6. No-op сохраняет неканоническое написание + +Значение Integer уже равно 1. Проверка равенства возвращает исходные байты, включая `+1` и пробелы разделителя; критерии читаемости не применяются. + +```rust +let source = "n : +1\n"; +let updated = edit_value(source, "n", &Value::Integer("1".into()))?; +assert_eq!(updated, source); +``` + +Результат: + +```ktav +n : +1 +``` + +#### 7. BOM, CRLF и последняя строка без перевода + +Результат сохраняет BOM и CRLF, не добавляя финальный перевод строки. Экранированное представление делает эти байты видимыми. + +```rust +let source = "\u{FEFF}port: 8080\r\nhost: db"; +let updated = edit_value(source, "port", &Value::Integer("9090".into()))?; +``` + +Результат: + +```text +"\uFEFFport: 9090\r\nhost: db" +``` + +Критерии раскрытия фиксированы: многострочная замена, рост сверх `max(100, исходная длина строки)` либо не менее четырёх экранирований с накладными расходами от 25% для отдельной строки замены. Ширина считается в Unicode scalar values, а не байтах UTF-8 или колонках терминала. Уже длинная строка без роста сохраняется. Новые уровни используют четыре пробела и EOL исходника. Если CR или строка данных `))` мешают lossless-кодированию вне inline, сохраняется корректный inline-вариант; эти символы не означают автоматический отказ любой inline-правки. + +#### 8. Точная замена Integer + +Домен Integer в исходнике — знаковый 64-битный. Это значение больше безопасного диапазона JavaScript Number, но помещается в i64; используйте точное host-представление, а не округлённое число с плавающей точкой. Типизированный Integer вне i64 невозможно сохранить в исходнике как Integer: правка отказывает, не обрезая значение. Для текстового большого идентификатора используйте String. Float различает отрицательный и положительный нуль; no-op для Object не зависит от порядка ключей, порядок Array значим. + +```rust +let updated = edit_value("n: 1\n", "n", &Value::Integer("9007199254740993".into()))?; +``` + +```ktav +n: 9007199254740993 +``` + +### Диагностика и безопасные отказы в 0.9 + +Координаты исходника — байтовые смещения UTF-8 с учётом BOM; spans полуоткрыты `[start, end)`, строки нумеруются с 1. Поддерживаются LF, отдельный CR и CRLF; CRLF считается одним переводом строки. Один Unicode-символ может занимать несколько байтов. + +Незавершённый `a: [\n` даёт `UnclosedCompound`, строку 1 и span `3..5`: начало — самая внутренняя незакрытая скобка, а не начало ключа или строки. Для сырых байтов `61 3A 20 FF` (`a: ` и невалидный UTF-8) валидация сообщает `InvalidUtf8`, строку 1 и покрывающий span `3..4`; `line_text` равен null, а не придуманному тексту после lossy-декодирования. Проверка UTF-8 предшествует грамматике. + +Некорректный текстовый путь, включая пустой путь или пустой текстовый сегмент, даёт `EditInvalidPath`; смещение считается в байтах UTF-8 самого пути. Пустой список сегментов, отсутствующий ключ, индекс вне диапазона или спуск через скаляр дают `EditNotFound`. В объекте числовой сегмент остаётся ключом; индекс массива содержит ASCII-цифры, допускает ведущие нули, но не знаки. + +Целые промежуточные объекты из dotted/reopened-определений могут занимать несколько физических диапазонов и давать `EditUnsupported`; их существующие листья остаются редактируемыми. Структурная замена контейнера не должна удалять внутренние комментарии. Ошибка не возвращает частичный патч и не меняет переданный исходник. Результат повторно разбирается и семантически проверяется: цель должна равняться замене, остальное дерево значений — исходному. + +В привязках ошибочный исходник даёт обычный конверт парсера до проверки пути. Некорректный wire-payload — ошибка `Message` без выдуманных координат. Wire-конверт содержит `error`, `reason`, `line`, `line_text`, `span`, `path`, `body`, `canonical`, `spec_section`, `message`; имена host-accessors описаны в разделе ошибок. В `body` находятся десятичное байтовое смещение пути для `EditInvalidPath`, null для `EditNotFound` и причина отказа для `EditUnsupported`. + +Примеры координат относятся к обычным ошибкам разбора (`Error` / `ErrorEnvelope`). Сам Rust API правки возвращает `EditError::{InvalidPath, NotFound, Unsupported}`: `InvalidPath { offset }` относится к байтам пути, невалидный исходник оборачивается в `Unsupported`. C ABI сначала проверяет исходник и передаёт привязкам обычный конверт исходной ошибки. Rust-варианты не являются структурой полей wire-конверта. + ## C ABI для других языков Биндинги Go, Java, PHP, C#, JS и Python используют общее Rust-ядро. Выключенная по умолчанию фича `cabi` предоставляет wire-декодирование, операции над документами и кодирование ошибок; один макровызов в `cdylib` биндинга экспортирует C-символы. Прямые Rust-мосты, например PyO3 и N-API, могут использовать те же безопасные операции без дополнительного FFI-слоя. diff --git a/README.zh.md b/README.zh.md index 8a7b5bd..567385c 100644 --- a/README.zh.md +++ b/README.zh.md @@ -864,6 +864,158 @@ assert_eq!(edit_value_segments(source, &path, &replacement)?, expected); `EditError::InvalidPath` 报告文本路径错误及字节偏移;`NotFound` 表示键不存在、数组索引无效或越界,或试图穿过标量继续访问。`Unsupported` 拒绝无效源文档、无法表示的新值,或当前实现无法在保留周围文本的前提下完成的替换。例如,由分散的点分键或重新打开的定义构成的整个对象没有单一替换范围;替换容器也不能删除其内部注释,但其中的叶子值仍可编辑。行内值使用转义表示 CR 和 LF。在行内复合值之外,含 CR 的字符串及部分依赖上下文的多行/容器替换会被拒绝,而不会被静默改写。所有成功返回的文本都经过解析,并与预期值树比较;错误不会返回部分编辑的文档。 +### 源文本编辑示例 + +以下示例适用于尚未发布的 0.9 API,而非已发布的 0.8.1 包。只能替换已有值:不会重命名键、创建缺失路径或删除条目。各代码片段相互独立;Rust 的 `?` 假定外层函数返回 `Result`。Go 示例仅针对固定且有效的输入使用 panic;应用程序应处理返回的错误。 + +```rust +use ktav::edit::{edit_value, edit_value_segments}; +use ktav::Value; +``` + +#### 1. 通过点分键修改部署配置 + +只修改地址;注释、点分形式及相邻的用户字段逐字节保持不变。 + +```rust +let source = "## deployment\ntargets.storagehost.address: old\ntargets.storagehost.user: mcp-agent\n"; +let updated = edit_value(source, "targets.storagehost.address", &Value::String("192.168.0.88:22".into()))?; +``` + +结果: + +```ktav +## deployment +targets.storagehost.address: 192.168.0.88:22 +targets.storagehost.user: mcp-agent +``` + +#### 2. 字面点号:文本路径或路径段 + +源码中带引号的键名就是 `a.b`。文本路径需要转义点号;路径段 API 直接接收解码后的键名。两个调用返回相同结果。 + +```rust +let source = "\"a.b\": {port: 80}\n"; +let updated = edit_value(source, "a\\.b.port", &Value::Integer("90".into()))?; +let same = edit_value_segments(source, &["a.b", "port"], &Value::Integer("90".into()))?; +``` + +结果: + +```ktav +"a.b": {port: 90} +``` + +#### 3. 修改混合形式数组中的叶子值 + +此处 `0` 是数组索引;在对象中,同一个路径段表示名称为 `0` 的键。未修改的容器保持行内形式。 + +```rust +let source = "servers: [{host: old, keep: yes}, standby]\n"; +let updated = edit_value_segments(source, &["servers", "0", "host"], &Value::String("new".into()))?; +``` + +结果: + +```ktav +servers: [{host: new, keep: yes}, standby] +``` + +#### 4. 看似数字的字符串仍然是字符串 + +替换值是 String,而不是 Integer。raw 标记保留这种区别;需要数值时请传入类型化 Integer。 + +```rust +let source = "port: 8080\n"; +let updated = edit_value(source, "port", &Value::String("9090".into()))?; +``` + +结果: + +```ktav +port:: 9090 +``` + +#### 5. 多行替换:仅展开必要的容器 + +修改的数组以四空格缩进展开。未修改的对象保持行内形式,verbatim 字符串内容不会重新缩进。 + +```rust +let source = "a: [old, {keep: yes}]\n"; +let updated = edit_value(source, "a.0", &Value::String("x\ny".into()))?; +``` + +结果: + +```ktav +a: [ + (( +x +y + )) + {keep: yes} +] +``` + +#### 6. 无变化时保留非规范拼写 + +Integer 的值已经是 1。相等判断直接返回原始字节,包括 `+1` 及分隔符周围的空白,不应用可读性阈值。 + +```rust +let source = "n : +1\n"; +let updated = edit_value(source, "n", &Value::Integer("1".into()))?; +assert_eq!(updated, source); +``` + +结果: + +```ktav +n : +1 +``` + +#### 7. BOM、CRLF 及无换行符的末行 + +返回值保留 BOM 和 CRLF,且不会增加末尾换行。以下转义表示使这些字节清晰可见。 + +```rust +let source = "\u{FEFF}port: 8080\r\nhost: db"; +let updated = edit_value(source, "port", &Value::Integer("9090".into()))?; +``` + +结果: + +```text +"\uFEFFport: 9090\r\nhost: db" +``` + +展开条件固定:替换中包含多行字符串、修改后行长增长并超过 `max(100, 原始行长)`,或单个替换字符串需要至少四个转义且编码开销达到 25%。宽度按 Unicode 标量值计算,而非 UTF-8 字节或终端列。原本已很长但没有增长的行保持不变。新层级使用四空格及源文本的 EOL。若 CR 或数据行 `))` 无法无损转换为独立行,则保留有效的行内候选;这些字符不会使所有行内编辑自动失败。 + +#### 8. 精确替换 Integer + +源文本的 Integer 域是有符号 64 位。此值超过 JavaScript Number 的安全整数范围,但仍在 i64 内;应使用精确的宿主表示,而非已舍入的浮点值。超出 i64 的类型化 Integer 无法在源码中保持 Integer 身份,因此会被拒绝而不是截断。大型文本标识符应使用 String。Float 区分负零和正零;Object 的 no-op 相等忽略键顺序,而 Array 顺序仍然重要。 + +```rust +let updated = edit_value("n: 1\n", "n", &Value::Integer("9007199254740993".into()))?; +``` + +```ktav +n: 9007199254740993 +``` + +### 0.9 的诊断与安全失败 + +源码坐标为包含 BOM 的 UTF-8 字节偏移,span 使用半开区间 `[start, end)`,行号从 1 开始。支持 LF、独立 CR 和 CRLF,且 CRLF 计为一个换行。一个 Unicode 字符可能占用多个字节。 + +未完成的 `a: [\n` 报告 `UnclosedCompound`、第 1 行和 span `3..5`:区间从最内层未闭合的起始括号开始,而非键或行的开头。原始字节 `61 3A 20 FF`(`a: ` 后接无效 UTF-8)报告 `InvalidUtf8`、第 1 行及覆盖区间 `3..4`;`line_text` 为 null,不会通过有损解码伪造文本。UTF-8 校验优先于语法处理。 + +非法文本路径(包括空路径或空文本段)报告 `EditInvalidPath`,偏移按路径本身的 UTF-8 字节计算。空路径段列表、缺失键、索引越界或穿过标量下钻报告 `EditNotFound`。对象中的数字段仍是键;数组索引仅接受 ASCII 数字,允许前导零,不允许正负号。 + +由 dotted/reopened 定义拼成的整个中间对象可能占据多个物理范围,因此报告 `EditUnsupported`;已有叶子值仍可修改。容器结构替换不能删除内部注释。失败调用不会返回部分补丁,也不会修改传入的源码。返回文本会重新解析并进行语义检查:目标必须等于替换值,其余值树必须与原文相等。 + +在绑定中,无效源码会先于路径验证报告普通解析错误信封。非法 wire 载荷是无虚构源码坐标的 `Message` 错误。wire 信封包含 `error`、`reason`、`line`、`line_text`、`span`、`path`、`body`、`canonical`、`spec_section`、`message`;宿主语言的访问器名称见错误章节。`body` 分别承载 `EditInvalidPath` 的十进制路径字节偏移、`EditNotFound` 的 null,以及 `EditUnsupported` 的拒绝原因。 + +坐标示例描述普通解析错误(`Error` / `ErrorEnvelope`)。Rust 编辑 API 本身返回 `EditError::{InvalidPath, NotFound, Unsupported}`:`InvalidPath { offset }` 指向路径字节,无效源码包装为 `Unsupported`。C ABI 会先验证源码,并向绑定公开普通源码错误信封。这些 Rust 变体不是 wire 信封的字段布局。 + ## 面向其他语言的 C ABI Go、Java、PHP、C#、JS 和 Python 绑定共用 Rust 核心。默认关闭的 `cabi` feature 提供 wire 解码、文档操作和错误编码;绑定的 `cdylib` 中一次宏调用即可导出 C 符号。PyO3 和 N-API 等直接 Rust 桥接可以调用相同的安全操作,无需增加 FFI 层。 diff --git a/root-docs/CHANGELOG/unreleased/body-1.md b/root-docs/CHANGELOG/unreleased/body-1.md index 15afac0..38feb61 100644 --- a/root-docs/CHANGELOG/unreleased/body-1.md +++ b/root-docs/CHANGELOG/unreleased/body-1.md @@ -23,6 +23,10 @@ - Borrow literal path slices and unescaped textual segments instead of copying each segment. Assemble raw-marker prefixes and unchanged replacement bodies directly into the result; render verbatim replacements in one buffer without an intermediate EOL-converted string. Preserve exact output and full semantic verification; measure text and segment paths separately in short paired benchmarks. - Locate diagnostic line text by reverse terminator search instead of rescanning the document prefix. Probe JSON strings with safe eight-byte operations, bulk-copy unescaped UTF-8, specialize escaped ASCII and retain the Unicode encoder. Allocate reopen-merge child buffers and seen-key storage lazily. Preserve exact bytes and locations; add allocation/escaping regressions and paired runtime benchmarks with one external 15-second limit for the entire process batch. +### Documentation + +- Expand README examples for dotted and literal-key paths, mixed arrays, String/Integer identity, multiline unfolding, byte-identical no-op and BOM/CRLF/EOF preservation. Clarify 0.9 byte diagnostics, safe refusal, ABI 2 and standalone pure usage; these additions are not part of published 0.8.1. + >>>>> lang=ru ## [Не выпущено] @@ -48,6 +52,10 @@ - Буквальные сегменты пути и сегменты текстового пути без escape заимствуются без копирования каждого сегмента. Префиксы raw-маркера и тела замен, не требующие преобразования, переносятся непосредственно в результат; verbatim-замены рендерятся в один буфер без промежуточной строки с преобразованными EOL. Точные выходные байты и полная семантическая проверка сохранены; короткие парные benchmarks отдельно измеряют text- и segments-пути. - Строка диагностики находится обратным поиском терминатора без повторного прохода по префиксу документа. JSON-строки проверяются безопасными восьмибайтовыми операциями; UTF-8 без escapes переносится целиком, экранируемый ASCII обрабатывается специализированно, Unicode encoder сохраняется. Дочерние буферы и хранилища seen-ключей reopen-merge выделяются лениво. Точные байты и координаты сохранены; добавлены регрессии аллокаций/экранирования и парные runtime benchmarks с единым внешним лимитом 15 секунд на весь batch процессов. +### Документация + +- Дополнены примеры README: dotted-пути и буквальные ключи, смешанные массивы, различие String/Integer, раскрытие многострочной замены, побайтовый no-op и сохранение BOM/CRLF/EOF. Уточнены байтовая диагностика 0.9, безопасные отказы, ABI 2 и самостоятельный pure-бэкенд; дополнения не относятся к опубликованному 0.8.1. + >>>>> lang=zh ## [尚未发布] @@ -73,3 +81,7 @@ - 借用字面路径段及不含转义的文本路径段,不再逐段复制。将 raw 标记前缀及无需转换的替换正文直接写入结果;verbatim 替换使用单一缓冲区,不再生成转换 EOL 的中间字符串。保留精确输出与完整语义验证;短时配对基准分别测量文本路径和路径段 API。 - 通过反向查找换行符定位诊断行,不再重复扫描文档前缀。用安全的八字节运算检查 JSON 字符串,整体复制无需转义的 UTF-8,专门处理需转义的 ASCII,并保留 Unicode encoder。按需分配 reopen-merge 子缓冲区和 seen 键存储。保留精确字节与坐标;增加分配及转义回归测试,并提供配对 runtime 基准,整个进程 batch 共用一个外部 15 秒限制。 +### 文档 + +- 扩展 README 示例:点分路径与字面键、混合数组、String/Integer 区别、多行展开、逐字节 no-op 及 BOM/CRLF/EOF 保留。明确 0.9 字节诊断、安全拒绝、ABI 2 和独立纯实现用法;这些新增功能不属于已发布的 0.8.1。 + diff --git a/root-docs/README/diagnostics09/body-1.md b/root-docs/README/diagnostics09/body-1.md new file mode 100644 index 0000000..5a8e562 --- /dev/null +++ b/root-docs/README/diagnostics09/body-1.md @@ -0,0 +1,45 @@ +>>>>> lang=en +### Diagnostics and safe failures in 0.9 + +Source coordinates are UTF-8 byte offsets, including a BOM; spans are half-open `[start, end)`, and lines are 1-based. LF, lone CR and CRLF are handled, with CRLF counted as one terminator. A Unicode character may occupy several bytes. + +An unfinished `a: [\n` reports `UnclosedCompound`, line 1 and span `3..5`: the span begins at the innermost unmatched opener, not the start of its key or line. For raw bytes `61 3A 20 FF` (`a: ` followed by invalid UTF-8), validation reports `InvalidUtf8`, line 1 and a covering span `3..4`; `line_text` is null rather than fabricated by lossy decoding. UTF-8 validation precedes grammar processing. + +A malformed textual path, including an empty textual path or empty textual segment, is `EditInvalidPath`; its offset is measured in the path's UTF-8 bytes. An empty segments list, missing key, out-of-range index or scalar descent is `EditNotFound`. Object numeric segments remain keys; array indexes use ASCII digits, allow leading zeros and reject signs. + +Whole dotted/reopened intermediate objects may occupy several physical ranges and are `EditUnsupported`; their existing leaves can still be edited. Replacing a container must not discard interior comments. No failed call returns a partial patch or changes the supplied source. The returned text is re-parsed and semantically checked: the target must equal the replacement and the rest of the value tree must remain equal. + +For bindings, invalid source is reported with the ordinary parser envelope before path validation. Malformed wire payloads are `Message` errors without invented source coordinates. The wire envelope has `error`, `reason`, `line`, `line_text`, `span`, `path`, `body`, `canonical`, `spec_section` and `message`; host accessor names are documented in the errors section. `body` carries the decimal path-byte offset for `EditInvalidPath`, null for `EditNotFound`, and the refusal reason for `EditUnsupported`. + +The coordinate examples describe ordinary parse errors (`Error` / `ErrorEnvelope`). Rust source editing itself returns `EditError::{InvalidPath, NotFound, Unsupported}`: `InvalidPath { offset }` refers to the path bytes, and an invalid source is wrapped as `Unsupported`. The C ABI validates the source first and exposes the ordinary source envelope to bindings. These Rust variants are not the wire envelope field layout. + +>>>>> lang=ru +### Диагностика и безопасные отказы в 0.9 + +Координаты исходника — байтовые смещения UTF-8 с учётом BOM; spans полуоткрыты `[start, end)`, строки нумеруются с 1. Поддерживаются LF, отдельный CR и CRLF; CRLF считается одним переводом строки. Один Unicode-символ может занимать несколько байтов. + +Незавершённый `a: [\n` даёт `UnclosedCompound`, строку 1 и span `3..5`: начало — самая внутренняя незакрытая скобка, а не начало ключа или строки. Для сырых байтов `61 3A 20 FF` (`a: ` и невалидный UTF-8) валидация сообщает `InvalidUtf8`, строку 1 и покрывающий span `3..4`; `line_text` равен null, а не придуманному тексту после lossy-декодирования. Проверка UTF-8 предшествует грамматике. + +Некорректный текстовый путь, включая пустой путь или пустой текстовый сегмент, даёт `EditInvalidPath`; смещение считается в байтах UTF-8 самого пути. Пустой список сегментов, отсутствующий ключ, индекс вне диапазона или спуск через скаляр дают `EditNotFound`. В объекте числовой сегмент остаётся ключом; индекс массива содержит ASCII-цифры, допускает ведущие нули, но не знаки. + +Целые промежуточные объекты из dotted/reopened-определений могут занимать несколько физических диапазонов и давать `EditUnsupported`; их существующие листья остаются редактируемыми. Структурная замена контейнера не должна удалять внутренние комментарии. Ошибка не возвращает частичный патч и не меняет переданный исходник. Результат повторно разбирается и семантически проверяется: цель должна равняться замене, остальное дерево значений — исходному. + +В привязках ошибочный исходник даёт обычный конверт парсера до проверки пути. Некорректный wire-payload — ошибка `Message` без выдуманных координат. Wire-конверт содержит `error`, `reason`, `line`, `line_text`, `span`, `path`, `body`, `canonical`, `spec_section`, `message`; имена host-accessors описаны в разделе ошибок. В `body` находятся десятичное байтовое смещение пути для `EditInvalidPath`, null для `EditNotFound` и причина отказа для `EditUnsupported`. + +Примеры координат относятся к обычным ошибкам разбора (`Error` / `ErrorEnvelope`). Сам Rust API правки возвращает `EditError::{InvalidPath, NotFound, Unsupported}`: `InvalidPath { offset }` относится к байтам пути, невалидный исходник оборачивается в `Unsupported`. C ABI сначала проверяет исходник и передаёт привязкам обычный конверт исходной ошибки. Rust-варианты не являются структурой полей wire-конверта. + +>>>>> lang=zh +### 0.9 的诊断与安全失败 + +源码坐标为包含 BOM 的 UTF-8 字节偏移,span 使用半开区间 `[start, end)`,行号从 1 开始。支持 LF、独立 CR 和 CRLF,且 CRLF 计为一个换行。一个 Unicode 字符可能占用多个字节。 + +未完成的 `a: [\n` 报告 `UnclosedCompound`、第 1 行和 span `3..5`:区间从最内层未闭合的起始括号开始,而非键或行的开头。原始字节 `61 3A 20 FF`(`a: ` 后接无效 UTF-8)报告 `InvalidUtf8`、第 1 行及覆盖区间 `3..4`;`line_text` 为 null,不会通过有损解码伪造文本。UTF-8 校验优先于语法处理。 + +非法文本路径(包括空路径或空文本段)报告 `EditInvalidPath`,偏移按路径本身的 UTF-8 字节计算。空路径段列表、缺失键、索引越界或穿过标量下钻报告 `EditNotFound`。对象中的数字段仍是键;数组索引仅接受 ASCII 数字,允许前导零,不允许正负号。 + +由 dotted/reopened 定义拼成的整个中间对象可能占据多个物理范围,因此报告 `EditUnsupported`;已有叶子值仍可修改。容器结构替换不能删除内部注释。失败调用不会返回部分补丁,也不会修改传入的源码。返回文本会重新解析并进行语义检查:目标必须等于替换值,其余值树必须与原文相等。 + +在绑定中,无效源码会先于路径验证报告普通解析错误信封。非法 wire 载荷是无虚构源码坐标的 `Message` 错误。wire 信封包含 `error`、`reason`、`line`、`line_text`、`span`、`path`、`body`、`canonical`、`spec_section`、`message`;宿主语言的访问器名称见错误章节。`body` 分别承载 `EditInvalidPath` 的十进制路径字节偏移、`EditNotFound` 的 null,以及 `EditUnsupported` 的拒绝原因。 + +坐标示例描述普通解析错误(`Error` / `ErrorEnvelope`)。Rust 编辑 API 本身返回 `EditError::{InvalidPath, NotFound, Unsupported}`:`InvalidPath { offset }` 指向路径字节,无效源码包装为 `Unsupported`。C ABI 会先验证源码,并向绑定公开普通源码错误信封。这些 Rust 变体不是 wire 信封的字段布局。 + diff --git a/root-docs/README/diagnostics09/meta.js b/root-docs/README/diagnostics09/meta.js new file mode 100644 index 0000000..eb86416 --- /dev/null +++ b/root-docs/README/diagnostics09/meta.js @@ -0,0 +1,7 @@ +export default { + "kind": "frontmatter", + "number": null, + "level": null, + "title": null, + "bodyParts": 1 +} diff --git a/root-docs/README/editing-cookbook/body-1.md b/root-docs/README/editing-cookbook/body-1.md new file mode 100644 index 0000000..d0423a5 --- /dev/null +++ b/root-docs/README/editing-cookbook/body-1.md @@ -0,0 +1,87 @@ +>>>>> lang=en +### Source-edit cookbook + +These examples describe the unreleased 0.9 API, not the published 0.8.1 package. They replace existing values only: keys are not renamed, missing paths are not created, and entries are not removed. Each snippet is independent; Rust `?` assumes an enclosing `Result`-returning function. Go examples panic only for these fixed, known-valid inputs; applications should handle the returned error. + +```rust +use ktav::edit::{edit_value, edit_value_segments}; +use ktav::Value; +``` + +#### 1. Dotted deployment settings + +Only the address changes; the comment, dotted form and neighbouring user entry remain byte-for-byte unchanged. + +```rust +let source = "## deployment\ntargets.storagehost.address: old\ntargets.storagehost.user: mcp-agent\n"; +let updated = edit_value(source, "targets.storagehost.address", &Value::String("192.168.0.88:22".into()))?; +``` + +Result: + +```ktav +## deployment +targets.storagehost.address: 192.168.0.88:22 +targets.storagehost.user: mcp-agent +``` + +#### 2. Literal dots: textual path or segments + +>>>>> lang=ru +### Практические примеры правки исходника + +Примеры относятся к ещё не выпущенному API 0.9, а не к опубликованному пакету 0.8.1. Они заменяют только существующие значения: не переименовывают ключи, не создают отсутствующие пути и не удаляют записи. Фрагменты независимы; Rust `?` предполагает внешнюю функцию с результатом `Result`. В Go panic используется только для фиксированных корректных данных примера; приложение должно обработать возвращённую ошибку. + +```rust +use ktav::edit::{edit_value, edit_value_segments}; +use ktav::Value; +``` + +#### 1. Настройка развёртывания через dotted-ключ + +Меняется только адрес; комментарий, dotted-форма и соседнее поле пользователя сохраняются байт-в-байт. + +```rust +let source = "## deployment\ntargets.storagehost.address: old\ntargets.storagehost.user: mcp-agent\n"; +let updated = edit_value(source, "targets.storagehost.address", &Value::String("192.168.0.88:22".into()))?; +``` + +Результат: + +```ktav +## deployment +targets.storagehost.address: 192.168.0.88:22 +targets.storagehost.user: mcp-agent +``` + +#### 2. Буквальные точки: текстовый путь или сегменты + +>>>>> lang=zh +### 源文本编辑示例 + +以下示例适用于尚未发布的 0.9 API,而非已发布的 0.8.1 包。只能替换已有值:不会重命名键、创建缺失路径或删除条目。各代码片段相互独立;Rust 的 `?` 假定外层函数返回 `Result`。Go 示例仅针对固定且有效的输入使用 panic;应用程序应处理返回的错误。 + +```rust +use ktav::edit::{edit_value, edit_value_segments}; +use ktav::Value; +``` + +#### 1. 通过点分键修改部署配置 + +只修改地址;注释、点分形式及相邻的用户字段逐字节保持不变。 + +```rust +let source = "## deployment\ntargets.storagehost.address: old\ntargets.storagehost.user: mcp-agent\n"; +let updated = edit_value(source, "targets.storagehost.address", &Value::String("192.168.0.88:22".into()))?; +``` + +结果: + +```ktav +## deployment +targets.storagehost.address: 192.168.0.88:22 +targets.storagehost.user: mcp-agent +``` + +#### 2. 字面点号:文本路径或路径段 + diff --git a/root-docs/README/editing-cookbook/body-2.md b/root-docs/README/editing-cookbook/body-2.md new file mode 100644 index 0000000..1da0f94 --- /dev/null +++ b/root-docs/README/editing-cookbook/body-2.md @@ -0,0 +1,90 @@ +>>>>> lang=en +The quoted source key is literally `a.b`. The textual path escapes its dot; the segments call passes the decoded key directly. Both calls return the same result. + +```rust +let source = "\"a.b\": {port: 80}\n"; +let updated = edit_value(source, "a\\.b.port", &Value::Integer("90".into()))?; +let same = edit_value_segments(source, &["a.b", "port"], &Value::Integer("90".into()))?; +``` + +Result: + +```ktav +"a.b": {port: 90} +``` + +#### 3. A leaf inside a mixed-form array + +`0` is an array index here. Inside an Object, the same segment would address the key named `0`; untouched containers remain inline. + +```rust +let source = "servers: [{host: old, keep: yes}, standby]\n"; +let updated = edit_value_segments(source, &["servers", "0", "host"], &Value::String("new".into()))?; +``` + +Result: + +```ktav +servers: [{host: new, keep: yes}, standby] +``` + +>>>>> lang=ru +Ключ исходника в кавычках буквально называется `a.b`. В текстовом пути точка экранируется; API сегментов получает декодированное имя напрямую. Оба вызова возвращают одинаковый результат. + +```rust +let source = "\"a.b\": {port: 80}\n"; +let updated = edit_value(source, "a\\.b.port", &Value::Integer("90".into()))?; +let same = edit_value_segments(source, &["a.b", "port"], &Value::Integer("90".into()))?; +``` + +Результат: + +```ktav +"a.b": {port: 90} +``` + +#### 3. Лист внутри массива со смешанными формами + +Здесь `0` — индекс массива. В объекте такой же сегмент обозначал бы ключ с именем `0`; нетронутые контейнеры остаются inline. + +```rust +let source = "servers: [{host: old, keep: yes}, standby]\n"; +let updated = edit_value_segments(source, &["servers", "0", "host"], &Value::String("new".into()))?; +``` + +Результат: + +```ktav +servers: [{host: new, keep: yes}, standby] +``` + +>>>>> lang=zh +源码中带引号的键名就是 `a.b`。文本路径需要转义点号;路径段 API 直接接收解码后的键名。两个调用返回相同结果。 + +```rust +let source = "\"a.b\": {port: 80}\n"; +let updated = edit_value(source, "a\\.b.port", &Value::Integer("90".into()))?; +let same = edit_value_segments(source, &["a.b", "port"], &Value::Integer("90".into()))?; +``` + +结果: + +```ktav +"a.b": {port: 90} +``` + +#### 3. 修改混合形式数组中的叶子值 + +此处 `0` 是数组索引;在对象中,同一个路径段表示名称为 `0` 的键。未修改的容器保持行内形式。 + +```rust +let source = "servers: [{host: old, keep: yes}, standby]\n"; +let updated = edit_value_segments(source, &["servers", "0", "host"], &Value::String("new".into()))?; +``` + +结果: + +```ktav +servers: [{host: new, keep: yes}, standby] +``` + diff --git a/root-docs/README/editing-cookbook/body-3.md b/root-docs/README/editing-cookbook/body-3.md new file mode 100644 index 0000000..f5c0377 --- /dev/null +++ b/root-docs/README/editing-cookbook/body-3.md @@ -0,0 +1,81 @@ +>>>>> lang=en +#### 4. A numeric-looking String stays a String + +The replacement is a String, not an Integer. The raw marker preserves that distinction. Supply a typed Integer when a numeric value is intended. + +```rust +let source = "port: 8080\n"; +let updated = edit_value(source, "port", &Value::String("9090".into()))?; +``` + +Result: + +```ktav +port:: 9090 +``` + +#### 5. Multiline replacement: expand only what is necessary + +The changed array expands with four-space indentation. The untouched object stays inline, and verbatim string contents are not reindented. + +```rust +let source = "a: [old, {keep: yes}]\n"; +let updated = edit_value(source, "a.0", &Value::String("x\ny".into()))?; +``` + +Result: + +>>>>> lang=ru +#### 4. Похожая на число строка остаётся строкой + +Замена имеет тип String, а не Integer. Raw-маркер сохраняет это различие. Для числового значения передавайте типизированный Integer. + +```rust +let source = "port: 8080\n"; +let updated = edit_value(source, "port", &Value::String("9090".into()))?; +``` + +Результат: + +```ktav +port:: 9090 +``` + +#### 5. Многострочная замена: раскрывается только необходимое + +Изменённый массив раскрывается с отступом четыре пробела. Нетронутый объект остаётся inline, содержимое verbatim-строки не переотступается. + +```rust +let source = "a: [old, {keep: yes}]\n"; +let updated = edit_value(source, "a.0", &Value::String("x\ny".into()))?; +``` + +Результат: + +>>>>> lang=zh +#### 4. 看似数字的字符串仍然是字符串 + +替换值是 String,而不是 Integer。raw 标记保留这种区别;需要数值时请传入类型化 Integer。 + +```rust +let source = "port: 8080\n"; +let updated = edit_value(source, "port", &Value::String("9090".into()))?; +``` + +结果: + +```ktav +port:: 9090 +``` + +#### 5. 多行替换:仅展开必要的容器 + +修改的数组以四空格缩进展开。未修改的对象保持行内形式,verbatim 字符串内容不会重新缩进。 + +```rust +let source = "a: [old, {keep: yes}]\n"; +let updated = edit_value(source, "a.0", &Value::String("x\ny".into()))?; +``` + +结果: + diff --git a/root-docs/README/editing-cookbook/body-4.md b/root-docs/README/editing-cookbook/body-4.md new file mode 100644 index 0000000..33f572c --- /dev/null +++ b/root-docs/README/editing-cookbook/body-4.md @@ -0,0 +1,87 @@ +>>>>> lang=en +```ktav +a: [ + (( +x +y + )) + {keep: yes} +] +``` + +#### 6. No-op preserves noncanonical spelling + +The Integer value is already 1. Equality returns the original bytes, including `+1` and the original separator spacing; readability thresholds are bypassed. + +```rust +let source = "n : +1\n"; +let updated = edit_value(source, "n", &Value::Integer("1".into()))?; +assert_eq!(updated, source); +``` + +Result: + +```ktav +n : +1 +``` + +#### 7. BOM, CRLF and an unterminated final line + +>>>>> lang=ru +```ktav +a: [ + (( +x +y + )) + {keep: yes} +] +``` + +#### 6. No-op сохраняет неканоническое написание + +Значение Integer уже равно 1. Проверка равенства возвращает исходные байты, включая `+1` и пробелы разделителя; критерии читаемости не применяются. + +```rust +let source = "n : +1\n"; +let updated = edit_value(source, "n", &Value::Integer("1".into()))?; +assert_eq!(updated, source); +``` + +Результат: + +```ktav +n : +1 +``` + +#### 7. BOM, CRLF и последняя строка без перевода + +>>>>> lang=zh +```ktav +a: [ + (( +x +y + )) + {keep: yes} +] +``` + +#### 6. 无变化时保留非规范拼写 + +Integer 的值已经是 1。相等判断直接返回原始字节,包括 `+1` 及分隔符周围的空白,不应用可读性阈值。 + +```rust +let source = "n : +1\n"; +let updated = edit_value(source, "n", &Value::Integer("1".into()))?; +assert_eq!(updated, source); +``` + +结果: + +```ktav +n : +1 +``` + +#### 7. BOM、CRLF 及无换行符的末行 + diff --git a/root-docs/README/editing-cookbook/body-5.md b/root-docs/README/editing-cookbook/body-5.md new file mode 100644 index 0000000..051e96e --- /dev/null +++ b/root-docs/README/editing-cookbook/body-5.md @@ -0,0 +1,84 @@ +>>>>> lang=en +The returned string keeps the BOM and CRLF, and does not gain a final newline. Its escaped representation is shown to make the bytes visible. + +```rust +let source = "\u{FEFF}port: 8080\r\nhost: db"; +let updated = edit_value(source, "port", &Value::Integer("9090".into()))?; +``` + +Result: + +```text +"\uFEFFport: 9090\r\nhost: db" +``` + +Expansion is deterministic: a multiline replacement, growth beyond `max(100, original line length)`, or at least four escapes with at least 25% encoding overhead for an individual replacement string. Width counts Unicode scalar values, not UTF-8 bytes or terminal columns. An already-long line without growth is kept. New levels use four spaces and the source EOL. If CR or a `))` data line prevents lossless standalone encoding, a valid inline candidate is retained; these characters do not automatically make every inline edit unsupported. + +#### 8. Exact Integer replacement + +The source Integer domain is signed 64-bit. This value exceeds JavaScript's safe-Number range but fits i64; use an exact host representation, not a rounded floating-point value. A typed Integer outside i64 cannot be preserved as an Integer in source and is refused instead of truncated. Use a String when a large identifier is text. Float identity distinguishes negative and positive zero; Object no-op equality ignores key order, while Array order remains significant. + +```rust +let updated = edit_value("n: 1\n", "n", &Value::Integer("9007199254740993".into()))?; +``` + +```ktav +n: 9007199254740993 +``` + +>>>>> lang=ru +Результат сохраняет BOM и CRLF, не добавляя финальный перевод строки. Экранированное представление делает эти байты видимыми. + +```rust +let source = "\u{FEFF}port: 8080\r\nhost: db"; +let updated = edit_value(source, "port", &Value::Integer("9090".into()))?; +``` + +Результат: + +```text +"\uFEFFport: 9090\r\nhost: db" +``` + +Критерии раскрытия фиксированы: многострочная замена, рост сверх `max(100, исходная длина строки)` либо не менее четырёх экранирований с накладными расходами от 25% для отдельной строки замены. Ширина считается в Unicode scalar values, а не байтах UTF-8 или колонках терминала. Уже длинная строка без роста сохраняется. Новые уровни используют четыре пробела и EOL исходника. Если CR или строка данных `))` мешают lossless-кодированию вне inline, сохраняется корректный inline-вариант; эти символы не означают автоматический отказ любой inline-правки. + +#### 8. Точная замена Integer + +Домен Integer в исходнике — знаковый 64-битный. Это значение больше безопасного диапазона JavaScript Number, но помещается в i64; используйте точное host-представление, а не округлённое число с плавающей точкой. Типизированный Integer вне i64 невозможно сохранить в исходнике как Integer: правка отказывает, не обрезая значение. Для текстового большого идентификатора используйте String. Float различает отрицательный и положительный нуль; no-op для Object не зависит от порядка ключей, порядок Array значим. + +```rust +let updated = edit_value("n: 1\n", "n", &Value::Integer("9007199254740993".into()))?; +``` + +```ktav +n: 9007199254740993 +``` + +>>>>> lang=zh +返回值保留 BOM 和 CRLF,且不会增加末尾换行。以下转义表示使这些字节清晰可见。 + +```rust +let source = "\u{FEFF}port: 8080\r\nhost: db"; +let updated = edit_value(source, "port", &Value::Integer("9090".into()))?; +``` + +结果: + +```text +"\uFEFFport: 9090\r\nhost: db" +``` + +展开条件固定:替换中包含多行字符串、修改后行长增长并超过 `max(100, 原始行长)`,或单个替换字符串需要至少四个转义且编码开销达到 25%。宽度按 Unicode 标量值计算,而非 UTF-8 字节或终端列。原本已很长但没有增长的行保持不变。新层级使用四空格及源文本的 EOL。若 CR 或数据行 `))` 无法无损转换为独立行,则保留有效的行内候选;这些字符不会使所有行内编辑自动失败。 + +#### 8. 精确替换 Integer + +源文本的 Integer 域是有符号 64 位。此值超过 JavaScript Number 的安全整数范围,但仍在 i64 内;应使用精确的宿主表示,而非已舍入的浮点值。超出 i64 的类型化 Integer 无法在源码中保持 Integer 身份,因此会被拒绝而不是截断。大型文本标识符应使用 String。Float 区分负零和正零;Object 的 no-op 相等忽略键顺序,而 Array 顺序仍然重要。 + +```rust +let updated = edit_value("n: 1\n", "n", &Value::Integer("9007199254740993".into()))?; +``` + +```ktav +n: 9007199254740993 +``` + diff --git a/root-docs/README/editing-cookbook/meta.js b/root-docs/README/editing-cookbook/meta.js new file mode 100644 index 0000000..f763e8e --- /dev/null +++ b/root-docs/README/editing-cookbook/meta.js @@ -0,0 +1,7 @@ +export default { + "kind": "frontmatter", + "number": null, + "level": null, + "title": null, + "bodyParts": 5 +} diff --git a/root-docs/README/manifest.js b/root-docs/README/manifest.js index 9eb6d17..cd8b599 100644 --- a/root-docs/README/manifest.js +++ b/root-docs/README/manifest.js @@ -12,6 +12,8 @@ export default [ "round-trip", "formatting", "editing", + "editing-cookbook", + "diagnostics09", "cabi", "architecture", "not-do",