vyyhti
Tangle (Finnish: vyyhti) — scan and process embedded processing instructions in text documents.
Requires Python 3.11 or later.
Install
pip install vyyhti
Quickstart
Run vyyhti scan with a text file to list every detected embedding:
$ vyyhti scan doc.md
line_command:1:0: '\\newpage'
inline_code:9:22: '`$.document.notes[*].group_ids[*]`'
line_command:11:0: '\\columns=10%,,%30%'
block_pi:21:0: '```{.text} <!--json-path-list-->'
Print the version:
$ vyyhti version
vyyhti 2026.6.21
$ vyyhti -V
vyyhti 2026.6.21
For a dedicated feature walkthrough you can follow in minutes visit quickstart. A step-by-step build of a JSONPath expression linter for Markdown documents is provided in the tutorial.
Embedding kinds
Inline code
A single-backtick span on any non-fenced line:
The expression `$.notes[*].id` must yield at least one result.
Line command
A line whose only content is a backslash command (as used by liitos and LaTeX preprocessors):
\newpage
\columns=10%,,30%
Block processing instruction
A fenced code block whose info string contains an HTML processing instruction comment:
```{.text} <!--json-path-list-->
$.document.notes[*].group_ids[*]
$.vulnerabilities[*].flags[*].group_ids[*]
```
The PI name (json-path-list above) is extracted and available to handlers.
The fence body is preserved verbatim.
Library API
scan(text, config=None)
Scans a text string and returns a list of Location objects — one per detected embedding.
from vyyhti import scan, LocationKind
locs = scan(open('doc.md').read())
for loc in locs:
print(loc.kind, loc.line, loc.text)
Each Location is a frozen dataclass:
| Field | Type | Meaning |
|---|---|---|
kind |
LocationKind |
INLINE_CODE, LINE_COMMAND, or BLOCK_PI |
line |
int |
1-based line number of the embedding start |
col |
int |
1-based column for INLINE_CODE; 0 for whole-line kinds |
text |
str |
Raw matched text (backtick span / command / opening fence line) |
body |
str \ None |
For BLOCK_PI: fence body; None otherwise |
pi |
str \ None |
For BLOCK_PI: content of <!--…-->; None otherwise |
run(text, handlers, stages=None, config=None)
Scans the text and applies a list of handlers through the processing pipeline.
Returns (embeddings, findings).
from vyyhti import run, Handler, LocationKind
class MyHandler(Handler):
name = 'my-handler'
def identify(self, location):
return location.kind == LocationKind.INLINE_CODE
def parse(self, location):
return location.text[1:-1] # strip backticks
def verify(self, location, payload):
return [] if payload.startswith('$') else ['not a JSONPath']
embeddings, findings = run(text, [MyHandler()])
Each matched Location becomes an Embedding (with handler name and payload set).
Each problem becomes a Finding (with stage, message, and level).
Pass stages={'parse', 'verify'} to limit which pipeline stages execute.
Pass a ScannerConfig to configure which embedding kinds are scanned.
ScannerConfig
Controls which embedding kinds are active and overrides the default match patterns.
Pass an instance to scan() or run().
from vyyhti import scan, ScannerConfig
cfg = ScannerConfig(line_command=False) # skip \commands
locs = scan(text, cfg)
| Field | Type | Default | Meaning |
|---|---|---|---|
inline_code |
bool |
True |
Scan for inline-code spans |
line_command |
bool |
True |
Scan for backslash line commands |
block_pi |
bool |
True |
Scan for fenced-block PIs |
block_pi_pi_pattern |
str |
<!--(.*?)--> |
Regex for PI comment in fence info string |
line_command_pattern |
str |
^\s*(\\...)$ |
Regex for line commands |
Load from a YAML file (kebab-case keys) with load_scanner_config(path) from vyyhti._config.
Handler
An abstract base class. Subclass it and implement the methods you need.
| Method | Signature | Required | Default |
|---|---|---|---|
name |
str class attribute |
yes | — |
identify |
(location) -> bool |
yes | — |
parse |
(location) -> Any |
no | returns location.text |
verify |
(location, payload) -> list[str] |
no | returns [] |
validate |
(location, payload) -> list[str] |
no | returns [] |
process |
(location, payload) -> Any |
no | returns None |
Finding
| Field | Type | Meaning |
|---|---|---|
location |
Location |
The embedding where the problem was found |
stage |
str |
'parse', 'verify', 'validate', 'process' |
message |
str |
Human-readable description |
level |
str |
'error' (default), 'warning', or 'info' |
Design
Handlers live in the tools that use vyyhti, not in the library itself.
scan() finds all structural embedding locations; each handler decides what it claims via identify().
The pipeline then applies only the stages the handler cares about.
Handlers for different embedding kinds can coexist in the same run() call.
Exit codes
0— success.1— error: file not found, unreadable, or missing required argument.
See also
man vyyhti
Design and requirements
| Document | Identifier | File |
|---|---|---|
| Software Requirements Specification | VYY-SRS-001 | requirements/srs/ |
| Software Design Description | VYY-SDD-001 | design/sdd/ |
Both documents follow the MIL-STD-498 DID structure and are rendered into the documentation site alongside the quickstart and tutorial.
Changes
See docs/changes.md for the release history.
Coverage
The test suite maintains 99% branch coverage.
The HTML report (if generated) is in site/coverage/.
SBOM
Runtime dependency information is published in docs/sbom/ in SPDX 3.0 (JSON-LD) and CycloneDX 1.6 (JSON) formats.
See docs/sbom/README.md for the component inventory and validation guide.
Metadata
Release files for vyyhti 2026.10.3
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| vyyhti-2026.10.3.tar.gz | 20.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| vyyhti-2026.10.3-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 31.3 kB
Release files / vyyhti-2026.10.3.tar.gz
| Download URL | vyyhti-2026.10.3.tar.gz |
|---|---|
| Size | 20.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
1ebccc23b25381fac56f8597982cbae1f29237465d88974b84acd803406974de
|
|
BLAKE2b-256 checksum How to use checksums |
f90f3719a6a8b8abb6739baa8dca3c0ca0c01ad71cbb758f5e5721b99bdc4bc8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.5
|
Release files / vyyhti-2026.10.3-py3-none-any.whl
| Download URL | vyyhti-2026.10.3-py3-none-any.whl |
|---|---|
| Size | 10.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
3124d09e1167a8ed698b22c22d2cd2d4c56a0014ff731903482204b85af1153f
|
|
BLAKE2b-256 checksum How to use checksums |
f1c8056082d7a94019a9edd0cd9bfe24855036f3506220dea97a581091f80e77
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.5
|