intenttext 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- intenttext-1.0.0/.github/workflows/publish.yml +21 -0
- intenttext-1.0.0/.github/workflows/test.yml +18 -0
- intenttext-1.0.0/PKG-INFO +106 -0
- intenttext-1.0.0/README.md +92 -0
- intenttext-1.0.0/examples/basic.py +27 -0
- intenttext-1.0.0/intenttext/__init__.py +39 -0
- intenttext-1.0.0/intenttext/merge.py +85 -0
- intenttext-1.0.0/intenttext/parser.py +389 -0
- intenttext-1.0.0/intenttext/query.py +55 -0
- intenttext-1.0.0/intenttext/renderer.py +144 -0
- intenttext-1.0.0/intenttext/source.py +32 -0
- intenttext-1.0.0/intenttext/types.py +68 -0
- intenttext-1.0.0/intenttext/validate.py +121 -0
- intenttext-1.0.0/pyproject.toml +23 -0
- intenttext-1.0.0/tests/test_merge.py +45 -0
- intenttext-1.0.0/tests/test_parser.py +123 -0
- intenttext-1.0.0/tests/test_query.py +53 -0
- intenttext-1.0.0/tests/test_renderer.py +43 -0
- intenttext-1.0.0/tests/test_source.py +30 -0
- intenttext-1.0.0/tests/test_validate.py +46 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*"
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
publish:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
steps:
|
|
12
|
+
- uses: actions/checkout@v4
|
|
13
|
+
- uses: actions/setup-python@v5
|
|
14
|
+
with:
|
|
15
|
+
python-version: "3.11"
|
|
16
|
+
- run: pip install hatch
|
|
17
|
+
- run: hatch build
|
|
18
|
+
- run: pip install pytest && pytest
|
|
19
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
20
|
+
with:
|
|
21
|
+
password: ${{ secrets.PYPI_API_TOKEN }}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
name: Test
|
|
2
|
+
|
|
3
|
+
on: [push, pull_request]
|
|
4
|
+
|
|
5
|
+
jobs:
|
|
6
|
+
test:
|
|
7
|
+
runs-on: ubuntu-latest
|
|
8
|
+
strategy:
|
|
9
|
+
matrix:
|
|
10
|
+
python-version: ["3.10", "3.11", "3.12"]
|
|
11
|
+
steps:
|
|
12
|
+
- uses: actions/checkout@v4
|
|
13
|
+
- uses: actions/setup-python@v5
|
|
14
|
+
with:
|
|
15
|
+
python-version: ${{ matrix.python-version }}
|
|
16
|
+
- run: python -m pip install -U pip
|
|
17
|
+
- run: pip install -e .[dev]
|
|
18
|
+
- run: pytest -q
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: intenttext
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: IntentText - the semantic document format that is natively JSON
|
|
5
|
+
Project-URL: Homepage, https://github.com/intenttext/IntentText
|
|
6
|
+
Project-URL: Repository, https://github.com/intenttext/intenttext-python
|
|
7
|
+
Project-URL: Documentation, https://github.com/intenttext/IntentText/blob/main/docs/SPEC.md
|
|
8
|
+
License: MIT
|
|
9
|
+
Requires-Python: >=3.10
|
|
10
|
+
Provides-Extra: dev
|
|
11
|
+
Requires-Dist: pytest; extra == 'dev'
|
|
12
|
+
Requires-Dist: pytest-cov; extra == 'dev'
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
|
|
15
|
+
# IntentText Python
|
|
16
|
+
|
|
17
|
+
Python implementation of the IntentText parser and renderer.
|
|
18
|
+
|
|
19
|
+
Independent implementation (not a Node.js wrapper), designed for Python workflows and AI stacks.
|
|
20
|
+
|
|
21
|
+
## Install
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pip install intenttext
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Quick Start
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
from intenttext import parse, render_html, merge_data, validate, query
|
|
31
|
+
|
|
32
|
+
# Parse a document
|
|
33
|
+
source = """
|
|
34
|
+
title: Sprint Planning
|
|
35
|
+
section: Tasks
|
|
36
|
+
task: Write tests | owner: Ahmed | due: Friday
|
|
37
|
+
task: Deploy to staging | owner: Sarah | due: Monday
|
|
38
|
+
gate: Final approval | approver: Lead | timeout: 24h
|
|
39
|
+
""".strip()
|
|
40
|
+
|
|
41
|
+
doc = parse(source)
|
|
42
|
+
|
|
43
|
+
# Query for tasks
|
|
44
|
+
tasks = query(doc, type="task")
|
|
45
|
+
for task in tasks:
|
|
46
|
+
print(f"{task.content} -> {task.properties.get('owner', 'unassigned')}")
|
|
47
|
+
|
|
48
|
+
# Validate workflow semantics
|
|
49
|
+
result = validate(doc)
|
|
50
|
+
if not result.valid:
|
|
51
|
+
for issue in result.issues:
|
|
52
|
+
print(f"[{issue.type.upper()}] {issue.message}")
|
|
53
|
+
|
|
54
|
+
# Render to HTML
|
|
55
|
+
html = render_html(doc)
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## API
|
|
59
|
+
|
|
60
|
+
- `parse(source: str) -> IntentDocument`
|
|
61
|
+
- `parse_safe(source: str, ...) -> ParseResult`
|
|
62
|
+
- `render_html(doc: IntentDocument, include_css: bool = True) -> str`
|
|
63
|
+
- `render_print(doc: IntentDocument) -> str`
|
|
64
|
+
- `render_markdown(doc: IntentDocument) -> str`
|
|
65
|
+
- `merge_data(template: IntentDocument, data: dict) -> IntentDocument`
|
|
66
|
+
- `parse_and_merge(template_source: str, data: dict) -> IntentDocument`
|
|
67
|
+
- `validate(doc: IntentDocument) -> ValidationResult`
|
|
68
|
+
- `query(doc: IntentDocument, ...) -> list[IntentBlock]`
|
|
69
|
+
- `to_source(doc: IntentDocument) -> str`
|
|
70
|
+
|
|
71
|
+
## Development
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
pip install -e .[dev]
|
|
75
|
+
pytest
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Release (PyPI)
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
# 1) Ensure tests pass
|
|
82
|
+
python3 -m pytest -q
|
|
83
|
+
|
|
84
|
+
# 2) Build source + wheel
|
|
85
|
+
python3 -m pip install -U hatch twine
|
|
86
|
+
hatch build
|
|
87
|
+
|
|
88
|
+
# 3) Validate package metadata and long description
|
|
89
|
+
twine check dist/*
|
|
90
|
+
|
|
91
|
+
# 4) Upload (interactive)
|
|
92
|
+
twine upload dist/*
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Tag-based release flow:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
git tag v1.0.0
|
|
99
|
+
git push origin v1.0.0
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
GitHub Action publish workflow uses `PYPI_API_TOKEN` for automated release on `v*` tags.
|
|
103
|
+
|
|
104
|
+
## License
|
|
105
|
+
|
|
106
|
+
MIT
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# IntentText Python
|
|
2
|
+
|
|
3
|
+
Python implementation of the IntentText parser and renderer.
|
|
4
|
+
|
|
5
|
+
Independent implementation (not a Node.js wrapper), designed for Python workflows and AI stacks.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install intenttext
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
from intenttext import parse, render_html, merge_data, validate, query
|
|
17
|
+
|
|
18
|
+
# Parse a document
|
|
19
|
+
source = """
|
|
20
|
+
title: Sprint Planning
|
|
21
|
+
section: Tasks
|
|
22
|
+
task: Write tests | owner: Ahmed | due: Friday
|
|
23
|
+
task: Deploy to staging | owner: Sarah | due: Monday
|
|
24
|
+
gate: Final approval | approver: Lead | timeout: 24h
|
|
25
|
+
""".strip()
|
|
26
|
+
|
|
27
|
+
doc = parse(source)
|
|
28
|
+
|
|
29
|
+
# Query for tasks
|
|
30
|
+
tasks = query(doc, type="task")
|
|
31
|
+
for task in tasks:
|
|
32
|
+
print(f"{task.content} -> {task.properties.get('owner', 'unassigned')}")
|
|
33
|
+
|
|
34
|
+
# Validate workflow semantics
|
|
35
|
+
result = validate(doc)
|
|
36
|
+
if not result.valid:
|
|
37
|
+
for issue in result.issues:
|
|
38
|
+
print(f"[{issue.type.upper()}] {issue.message}")
|
|
39
|
+
|
|
40
|
+
# Render to HTML
|
|
41
|
+
html = render_html(doc)
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## API
|
|
45
|
+
|
|
46
|
+
- `parse(source: str) -> IntentDocument`
|
|
47
|
+
- `parse_safe(source: str, ...) -> ParseResult`
|
|
48
|
+
- `render_html(doc: IntentDocument, include_css: bool = True) -> str`
|
|
49
|
+
- `render_print(doc: IntentDocument) -> str`
|
|
50
|
+
- `render_markdown(doc: IntentDocument) -> str`
|
|
51
|
+
- `merge_data(template: IntentDocument, data: dict) -> IntentDocument`
|
|
52
|
+
- `parse_and_merge(template_source: str, data: dict) -> IntentDocument`
|
|
53
|
+
- `validate(doc: IntentDocument) -> ValidationResult`
|
|
54
|
+
- `query(doc: IntentDocument, ...) -> list[IntentBlock]`
|
|
55
|
+
- `to_source(doc: IntentDocument) -> str`
|
|
56
|
+
|
|
57
|
+
## Development
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
pip install -e .[dev]
|
|
61
|
+
pytest
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Release (PyPI)
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
# 1) Ensure tests pass
|
|
68
|
+
python3 -m pytest -q
|
|
69
|
+
|
|
70
|
+
# 2) Build source + wheel
|
|
71
|
+
python3 -m pip install -U hatch twine
|
|
72
|
+
hatch build
|
|
73
|
+
|
|
74
|
+
# 3) Validate package metadata and long description
|
|
75
|
+
twine check dist/*
|
|
76
|
+
|
|
77
|
+
# 4) Upload (interactive)
|
|
78
|
+
twine upload dist/*
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Tag-based release flow:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
git tag v1.0.0
|
|
85
|
+
git push origin v1.0.0
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
GitHub Action publish workflow uses `PYPI_API_TOKEN` for automated release on `v*` tags.
|
|
89
|
+
|
|
90
|
+
## License
|
|
91
|
+
|
|
92
|
+
MIT
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from intenttext import parse, parse_and_merge, query, render_html, to_source, validate
|
|
2
|
+
|
|
3
|
+
source = """
|
|
4
|
+
title: Sprint Planning
|
|
5
|
+
section: Tasks
|
|
6
|
+
task: Write tests | owner: Ahmed | due: Friday
|
|
7
|
+
task: Deploy to staging | owner: Sarah | due: Monday
|
|
8
|
+
gate: Final approval | approver: Lead | timeout: 24h
|
|
9
|
+
""".strip()
|
|
10
|
+
|
|
11
|
+
doc = parse(source)
|
|
12
|
+
|
|
13
|
+
tasks = query(doc, type="task")
|
|
14
|
+
for task in tasks:
|
|
15
|
+
print(f"{task.content} -> {task.properties.get('owner', 'unassigned')}")
|
|
16
|
+
|
|
17
|
+
validation = validate(doc)
|
|
18
|
+
print("Valid:", validation.valid)
|
|
19
|
+
|
|
20
|
+
html = render_html(doc)
|
|
21
|
+
print("Rendered HTML length:", len(html))
|
|
22
|
+
|
|
23
|
+
merged = parse_and_merge(
|
|
24
|
+
"title: Invoice {{invoice.number}}\nnote: Bill To {{client.name}}",
|
|
25
|
+
{"invoice": {"number": "2026-042"}, "client": {"name": "Acme Corp"}},
|
|
26
|
+
)
|
|
27
|
+
print(to_source(merged))
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from .merge import merge_data, parse_and_merge
|
|
2
|
+
from .parser import parse, parse_safe
|
|
3
|
+
from .query import query
|
|
4
|
+
from .renderer import render_html, render_markdown, render_print
|
|
5
|
+
from .source import to_source
|
|
6
|
+
from .types import (
|
|
7
|
+
InlineSegment,
|
|
8
|
+
IntentBlock,
|
|
9
|
+
IntentDocument,
|
|
10
|
+
IntentMetadata,
|
|
11
|
+
ParseResult,
|
|
12
|
+
ParseWarning,
|
|
13
|
+
ValidationIssue,
|
|
14
|
+
ValidationResult,
|
|
15
|
+
)
|
|
16
|
+
from .validate import validate
|
|
17
|
+
|
|
18
|
+
__version__ = "1.0.0"
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"parse",
|
|
22
|
+
"parse_safe",
|
|
23
|
+
"render_html",
|
|
24
|
+
"render_print",
|
|
25
|
+
"render_markdown",
|
|
26
|
+
"merge_data",
|
|
27
|
+
"parse_and_merge",
|
|
28
|
+
"validate",
|
|
29
|
+
"query",
|
|
30
|
+
"to_source",
|
|
31
|
+
"IntentDocument",
|
|
32
|
+
"IntentBlock",
|
|
33
|
+
"IntentMetadata",
|
|
34
|
+
"InlineSegment",
|
|
35
|
+
"ParseResult",
|
|
36
|
+
"ParseWarning",
|
|
37
|
+
"ValidationResult",
|
|
38
|
+
"ValidationIssue",
|
|
39
|
+
]
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from copy import deepcopy
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from .parser import parse
|
|
9
|
+
from .types import IntentDocument
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def merge_data(template: IntentDocument, data: dict[str, Any]) -> IntentDocument:
|
|
13
|
+
doc = deepcopy(template)
|
|
14
|
+
now = datetime.now()
|
|
15
|
+
system_vars = {
|
|
16
|
+
"timestamp": now.isoformat(),
|
|
17
|
+
"date": now.strftime("%d %B %Y"),
|
|
18
|
+
"year": str(now.year),
|
|
19
|
+
}
|
|
20
|
+
merged_data = {**system_vars, **data}
|
|
21
|
+
|
|
22
|
+
for block in doc.blocks:
|
|
23
|
+
block.content = _resolve_string(block.content, merged_data)
|
|
24
|
+
block.original_content = _resolve_string(block.original_content, merged_data)
|
|
25
|
+
block.properties = {
|
|
26
|
+
k: _resolve_string(v, merged_data) if isinstance(v, str) else v
|
|
27
|
+
for k, v in block.properties.items()
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
_refresh_metadata(doc)
|
|
31
|
+
|
|
32
|
+
return doc
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def parse_and_merge(template_source: str, data: dict[str, Any]) -> IntentDocument:
|
|
36
|
+
template = parse(template_source)
|
|
37
|
+
return merge_data(template, data)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _resolve_string(text: str, data: dict[str, Any]) -> str:
|
|
41
|
+
def replacer(match: re.Match[str]) -> str:
|
|
42
|
+
path = match.group(1).strip()
|
|
43
|
+
if path in ("page", "pages"):
|
|
44
|
+
return match.group(0)
|
|
45
|
+
value = _get_by_path(data, path)
|
|
46
|
+
return str(value) if value is not None else match.group(0)
|
|
47
|
+
|
|
48
|
+
return re.sub(r"\{\{([^}]+)\}\}", replacer, text)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _get_by_path(obj: Any, path: str) -> Any:
|
|
52
|
+
parts = path.split(".")
|
|
53
|
+
current = obj
|
|
54
|
+
|
|
55
|
+
for part in parts:
|
|
56
|
+
if current is None:
|
|
57
|
+
return None
|
|
58
|
+
|
|
59
|
+
if isinstance(current, list):
|
|
60
|
+
try:
|
|
61
|
+
current = current[int(part)]
|
|
62
|
+
except (ValueError, IndexError):
|
|
63
|
+
return None
|
|
64
|
+
elif isinstance(current, dict):
|
|
65
|
+
current = current.get(part)
|
|
66
|
+
else:
|
|
67
|
+
return None
|
|
68
|
+
|
|
69
|
+
return current
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _refresh_metadata(doc: IntentDocument) -> None:
|
|
73
|
+
for block in doc.blocks:
|
|
74
|
+
if block.type == "title":
|
|
75
|
+
doc.metadata.title = block.content
|
|
76
|
+
elif block.type == "summary":
|
|
77
|
+
doc.metadata.summary = block.content
|
|
78
|
+
elif block.type == "agent":
|
|
79
|
+
doc.metadata.agent = block.content
|
|
80
|
+
if "model" in block.properties:
|
|
81
|
+
doc.metadata.model = str(block.properties["model"])
|
|
82
|
+
elif block.type == "context":
|
|
83
|
+
doc.metadata.context.update(
|
|
84
|
+
{k: str(v) for k, v in block.properties.items()}
|
|
85
|
+
)
|