Download the PHP package mougrim/yaml-cst without Composer
On this page you can find all versions of the php package mougrim/yaml-cst. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Download mougrim/yaml-cst
More information about mougrim/yaml-cst
Files in mougrim/yaml-cst
Package yaml-cst
Short Description PHP library for parsing and manipulating YAML documents while preserving format using Tree-sitter via FFI (Concrete Syntax Tree)
License MIT
Homepage https://github.com/mougrim/php-yaml-cst
Informations about the package yaml-cst
mougrim/yaml-cst
A PHP library for parsing and manipulating YAML documents while preserving format using Tree-sitter via FFI (Foreign Function Interface).
It exposes a Concrete Syntax Tree (CST) — every byte of the original source is accounted for, including whitespace and comments — and provides a high-level API for:
- Path-based lookups — navigate to any mapping pair with a dotted path like
database.host. - Non-destructive patching — queue text replacements and apply them all at once without re-formatting the rest of the file.
- Precise error reporting — syntax errors include the exact line and byte offset.
- Line/column mapping — convert any byte offset to a human-readable line and column.
Note: Path-based access via
YamlIndexis limited to mapping pairs (key–value entries). Sequences (YAML arrays) are not indexed by path. To traverse sequences or other non-mapping constructs, useYamlDocument::$treeand theYamlCstNodeRefAPI directly.Note: Multi-document YAML files (with
---document separators) are not supported.YamlCstParser::parse()expects a single YAML document. Passing a multi-document file may produce unexpected results or a syntax error.Note: Parsing an empty string (
'') is valid and produces aYamlDocumentwith an empty index.YamlDocument::isEmpty()returnstruefor empty strings and for documents containing only comments.
Requirements
| Requirement | Version |
|---|---|
| PHP | ≥ 8.4 |
ext-ffi |
any (enabled with ffi.enable=true) |
libtree-sitter |
≥ 0.26.8 |
tree-sitter-yaml |
≥ 0.7.2 |
The native .so libraries must be present on the system. The included Dockerfile builds and installs them automatically.
Quick start
See Setting up the parser for detailed construction notes and DI container recommendations.
Installation
Building the native libraries
The library uses tree-sitter's C API through PHP FFI. You need two shared libraries:
libtree-sitter.so— core tree-sitter runtimelibtree-sitter-yaml.so— YAML grammar
Option A — Docker (recommended)
The provided Dockerfile compiles both libraries from source:
Option B — Build manually
Then enable FFI in php.ini:
Usage
Setting up the parser
It is recommended to create the parser and its dependencies using a dependency-injection (DI) container so that all collaborators are wired and shared automatically. The example below shows manual construction for illustration purposes.
The YamlTreeSitterCore instance wraps the native FFI bindings and is expensive to initialise.
Create it once per process (e.g. as a DI singleton) and reuse it for every parse() call.
Parsing a YAML document
Path-based access
Limitation:
YamlIndexonly indexes mapping pairs (key–value entries). Sequences (YAML lists) are not accessible by path — traverse them viaYamlDocument::$treeandYamlCstNodeRefinstead.Key normalization: Quoted keys are stripped of their surrounding quotes and only the following escape sequences are unescaped:
\"inside double-quoted keys, and''inside single-quoted keys. Other YAML escape sequences (\n,\t,\\,\uXXXX, etc.) are not unescaped. Keys with such sequences will be stored with the literal backslash in the path index.Dot-collision: If a YAML file contains a quoted key whose text includes a literal
.(e.g."foo.bar": value), the dot-path convenience methods (getByPath,hasByPath, etc.) cannot distinguish it from a nested keyfoo → bar. Use the primary segment-based API to be unambiguous:get(['foo.bar'])is one key namedfoo.bar;get(['foo', 'bar'])is a nested keybarinsidefoo.
The primary API uses list<string> segments. Dot-path strings are available as convenience
wrappers (getByPath, hasByPath, findByPath, childrenOfByPath) for simple cases where
key names are known not to contain dots.
Patching a document
Patches are text replacements applied atomically. The original document is never mutated.
Applying overlapping patches throws PatchConflictException. The exception exposes
$previousSpan and $currentSpan so you can inspect which patches conflict.
Traversing sequences with YamlCstSearcher
YamlIndex only covers mapping pairs. To work with sequences (YAML arrays) or to navigate
the raw CST, use YamlCstSearcher together with YamlDocument::$tree.
Common recipes
Reading a value
Note:
valueText()returns the raw source text as-is, including any YAML quoting (e.g."localhost"notlocalhost). To strip surrounding quotes use either:
YamlTextStyleHelper::normalizeScalar()— minimal unescaping (\"and''only)YamlTextStyleHelper::fullyNormalizeScalar()— all YAML 1.2 escape sequences
Reading the raw pair text
pairText() returns the full source text of a mapping pair (key + colon + value), useful for
debugging or when you need the raw representation:
Replacing a value
The shortest form uses YamlMappingPatchHelper::replacementPatch():
Alternatively, build the patch manually from the value span:
Deleting a key–value pair
YamlMappingPatchHelper handles the span calculation automatically — it covers the full line
including the trailing newline so no empty line is left behind:
Inserting a new key–value pair
YamlMappingPatchHelper::insertionPatch() matches the indent and end-of-line sequence of the
reference pair automatically:
Error handling
Line/column mapping
Text-style helpers
YamlTextStyleHelper provides low-level utilities for working with raw YAML source text.
These are useful when building patches that must preserve the original formatting style.
Custom library paths
If the .so files are not in /usr/local/lib, pass explicit paths to the factory:
Docker
A Dockerfile is included for a fully reproducible environment.
Development
See CONTRIBUTING.md for the full guide.
Quick start:
Available make targets:
| Target | Description |
|---|---|
build |
Build Docker image |
install |
Install Composer dependencies (Docker) |
test |
Run PHPUnit (Docker) |
test-unit |
Run unit tests only (Docker) |
test-integration |
Run integration tests only (Docker) |
test-coverage |
Run PHPUnit with HTML + text coverage report (Docker, requires pcov) |
phpstan |
Run PHPStan at level max (Docker) |
cs-check |
Check code style with php-cs-fixer (Docker) |
cs-fix |
Auto-fix code style with php-cs-fixer (Docker) |
clean |
Remove local build artifacts (vendor, caches) |
bash |
Open an interactive bash shell in Docker |
ci |
Full pipeline: build → install → test → phpstan → cs-check |
Node types reference
YamlNodeType is the vocabulary you need when calling YamlCstSearcher methods or checking
YamlCstNodeRef::type(). The most common cases:
YamlNodeType case |
tree-sitter string | When you'll use it |
|---|---|---|
BLOCK_MAPPING |
block_mapping |
Root of an indented key–value block (key: value) |
BLOCK_MAPPING_PAIR |
block_mapping_pair |
A single key: value entry inside a block mapping |
FLOW_MAPPING |
flow_mapping |
Inline mapping ({key: value}) |
FLOW_PAIR |
flow_pair |
A single key: value entry inside a flow mapping |
BLOCK_SEQUENCE |
block_sequence |
Root of a dash-list block (- item) |
BLOCK_SEQUENCE_ITEM |
block_sequence_item |
A single - item entry inside a block sequence |
PLAIN_SCALAR |
plain_scalar |
An unquoted scalar value |
SINGLE_QUOTE_SCALAR |
single_quote_scalar |
A 'single-quoted' scalar |
DOUBLE_QUOTE_SCALAR |
double_quote_scalar |
A "double-quoted" scalar |
BLOCK_SCALAR |
block_scalar |
A literal (\|) or folded (>) block scalar |
COMMENT |
comment |
A # comment node (also reported by isExtra()) |
ERROR |
ERROR |
A syntax-error node; triggers YamlSyntaxException from YamlCstParser |
The full list is in src/Enum/YamlNodeType.php. YamlNodeType::tryFrom(string) returns null
for any type string not in the enum (future grammar additions).
API overview
It is recommended to create all objects via a DI container with auto-wiring. For manual construction see the Setting up the parser example above.
Core classes
| Class | Description |
|---|---|
YamlCstParser |
Entry point — parses a YAML string into a YamlDocument. |
YamlDocumentPatchApplier |
Accepts a list<YamlPatch> and a document, applies all patches, and returns a new re-parsed document. |
YamlPatchConflictChecker |
Validates that a list of YamlPatch objects do not overlap; throws PatchConflictException on conflict. |
YamlCstSearcher |
Tree-navigation helpers: firstDescendantOfType(), allDescendantsOfType(), directMappingPairs(), directSequenceItems(). |
YamlTextStyleHelper |
Low-level text utilities: EOL detection, indent extraction, line offsets, normalizeScalar() (minimal), fullyNormalizeScalar() (full YAML 1.2 unescaping). |
YamlMappingPatchHelper |
High-level patch helpers: replacementPatch() (replace a value), deletionPatch() (delete full key–value line), insertionPatch() (insert after a pair with auto-indent). |
YamlTreeSitterCoreFactory |
Creates the YamlTreeSitterCore FFI binding (call once per process, share as singleton). |
Domain model
| Class | Description |
|---|---|
YamlDocument |
Fully immutable value object: source, tree, index, lineMap; isEmpty() returns true for empty or comment-only documents. |
YamlIndex |
Maps segment paths to YamlMappingPairRef entries (mappings only); primary API uses list<string> segments (get(), find(), has(), childrenOf(), allPaths()); dot-string convenience variants (getByPath(), hasByPath(), etc.) available for simple cases. |
YamlMappingPairRef |
A reference to a key–value pair: segments (list<string>), keyText, spans (keySpan(), valueSpan(), pairSpan()), text helpers (keyText(), valueText(), pairText()); path() returns the dot-joined string as a convenience. |
YamlPatch |
A single text replacement: YamlSpan + replacement string. |
YamlSpan |
Byte range [startByte, endByte). |
YamlLineMap |
Binary-search index for byte-offset → YamlLocation conversion. |
YamlLocation |
Human-readable location: 1-based line and byte col. |
YamlCstTree |
Parsed CST tree; use $document->tree to access sequences and nodes. |
YamlCstNodeRef |
Reference to a single CST node; type(), nextNamedSibling(), previousNamedSibling(). |
YamlNodeType |
Enum of known YAML node types (BLOCK_MAPPING_PAIR, FLOW_PAIR, ERROR, …). |
YamlNodeField |
Enum of tree-sitter field names (KEY, VALUE) for childByField(). |
YamlTreeSitterCore |
Low-level FFI wrapper around the tree-sitter C library; pass to parse() / apply(). |
Exceptions
All library exceptions implement YamlCstExceptionInterface, so a single catch is enough:
| Exception | Thrown when |
|---|---|
YamlSyntaxException |
The YAML source contains syntax errors. |
PathNotFoundException |
YamlIndex::get() or YamlIndex::getByPath() is called with a non-existent path. |
PatchConflictException |
Two patches overlap; exposes $previousSpan and $currentSpan. |
MaxNestingDepthExceededException |
YAML document nesting depth exceeds the built-in limit (512 levels). |
AbiMismatchException |
libtree-sitter and tree-sitter-yaml have incompatible ABI versions. |
YamlTreeSitterException |
Native library not found, FFI disabled, or a tree-sitter C function returned NULL. |
YamlCstExceptionInterface |
Marker interface implemented by all of the above. |
License
This project is licensed under the MIT License.
All versions of yaml-cst with dependencies
ext-ffi Version *