agentmark-templatedx 0.1.0__py3-none-any.whl
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.
- agentmark_templatedx-0.1.0.dist-info/METADATA +104 -0
- agentmark_templatedx-0.1.0.dist-info/RECORD +20 -0
- agentmark_templatedx-0.1.0.dist-info/WHEEL +4 -0
- templatedx/__init__.py +58 -0
- templatedx/constants.py +21 -0
- templatedx/engine.py +155 -0
- templatedx/expression.py +763 -0
- templatedx/filter_plugins/__init__.py +5 -0
- templatedx/filter_plugins/builtin.py +179 -0
- templatedx/filter_registry.py +89 -0
- templatedx/py.typed +0 -0
- templatedx/scope.py +98 -0
- templatedx/tag_plugin.py +235 -0
- templatedx/tag_plugins/__init__.py +13 -0
- templatedx/tag_plugins/conditional.py +113 -0
- templatedx/tag_plugins/for_each.py +298 -0
- templatedx/tag_plugins/raw.py +32 -0
- templatedx/tag_registry.py +91 -0
- templatedx/transformer.py +244 -0
- templatedx/utils.py +26 -0
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: agentmark-templatedx
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python implementation of the AgentMark templatedx transformer
|
|
5
|
+
Project-URL: Homepage, https://github.com/agentmark/agentmark
|
|
6
|
+
Project-URL: Repository, https://github.com/agentmark/agentmark
|
|
7
|
+
Author: AgentMark Team
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
Keywords: agentmark,mdx,template,templatedx,transformer
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Typing :: Typed
|
|
17
|
+
Requires-Python: >=3.12
|
|
18
|
+
Provides-Extra: dev
|
|
19
|
+
Requires-Dist: mypy>=1.0; extra == 'dev'
|
|
20
|
+
Requires-Dist: pytest-asyncio>=0.21; extra == 'dev'
|
|
21
|
+
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
22
|
+
Requires-Dist: ruff>=0.1.0; extra == 'dev'
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
25
|
+
# AgentMark TemplateDX (Python)
|
|
26
|
+
|
|
27
|
+
Python implementation of the AgentMark templatedx transformer.
|
|
28
|
+
|
|
29
|
+
## Installation
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install agentmark-templatedx
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Usage
|
|
36
|
+
|
|
37
|
+
This package transforms pre-parsed MDX AST trees. The AST is typically obtained by:
|
|
38
|
+
- Parsing MDX with the TypeScript `@agentmark-ai/templatedx` package
|
|
39
|
+
- Loading a pre-parsed AST from a JSON file
|
|
40
|
+
- Receiving an AST from the AgentMark runtime
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
import asyncio
|
|
44
|
+
import json
|
|
45
|
+
from templatedx import TemplateDX
|
|
46
|
+
|
|
47
|
+
async def main():
|
|
48
|
+
engine = TemplateDX()
|
|
49
|
+
|
|
50
|
+
# Load a pre-parsed MDX AST (from TypeScript parser or JSON file)
|
|
51
|
+
with open("template.ast.json") as f:
|
|
52
|
+
ast = json.load(f)
|
|
53
|
+
|
|
54
|
+
# Transform the AST with props
|
|
55
|
+
result = await engine.transform(
|
|
56
|
+
ast,
|
|
57
|
+
props={"name": "Alice", "items": [1, 2, 3]}
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
print(result)
|
|
61
|
+
|
|
62
|
+
asyncio.run(main())
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Custom Plugins
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
from templatedx import TagPlugin, PluginContext
|
|
69
|
+
|
|
70
|
+
class MyPlugin(TagPlugin):
|
|
71
|
+
async def transform(self, props, children, context):
|
|
72
|
+
# Transform children and return result
|
|
73
|
+
transformer = context.create_node_transformer(context.scope)
|
|
74
|
+
return await transformer.transform_children(children)
|
|
75
|
+
|
|
76
|
+
engine = TemplateDX()
|
|
77
|
+
engine.register_tag_plugin(MyPlugin(), ["MyTag"])
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Custom Filters
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
engine = TemplateDX()
|
|
84
|
+
engine.register_filter("double", lambda x: x * 2)
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Built-in Filters
|
|
88
|
+
|
|
89
|
+
- `capitalize(str)` - Capitalize first character
|
|
90
|
+
- `upper(str)` - Uppercase string
|
|
91
|
+
- `lower(str)` - Lowercase string
|
|
92
|
+
- `truncate(str, length)` - Truncate with ellipsis
|
|
93
|
+
- `abs(num)` - Absolute value
|
|
94
|
+
- `join(arr, separator)` - Join array elements
|
|
95
|
+
- `round(num, decimals)` - Round number
|
|
96
|
+
- `replace(str, search, replacement)` - Replace occurrences
|
|
97
|
+
- `urlencode(str)` - URL encode string
|
|
98
|
+
- `dump(any)` - JSON stringify
|
|
99
|
+
|
|
100
|
+
## Built-in Tags
|
|
101
|
+
|
|
102
|
+
- `<If condition={...}>` / `<ElseIf condition={...}>` / `<Else>` - Conditional rendering
|
|
103
|
+
- `<ForEach arr={...}>{(item, index) => ...}</ForEach>` - Array iteration
|
|
104
|
+
- `<Raw>...</Raw>` - Raw content passthrough
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
templatedx/__init__.py,sha256=gvN3W7OwlxJ7s1n4e8AKcbE2n07jA3wssSxl1kqHBdM,1410
|
|
2
|
+
templatedx/constants.py,sha256=hBOTmHqhXaZ4BKmnH5LydMiurL4hBbtXKhPMu3AGSU8,650
|
|
3
|
+
templatedx/engine.py,sha256=GmEpybXMn2RVpYpqP775LnhSCmPKZpopTKEn8slXXy4,4849
|
|
4
|
+
templatedx/expression.py,sha256=ja5CFHke9rxjkPulwnm5o2lI6uQIAVZmJzXJSTEsgdw,23455
|
|
5
|
+
templatedx/filter_registry.py,sha256=gC1uk6-4eqfMC9jKYesKs1eqNN3SLvV2ibP2sfVT9nA,2409
|
|
6
|
+
templatedx/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
|
+
templatedx/scope.py,sha256=R9mk84tbQig2RiyXo5_dIGnBkGGgLtqS83UEJ5CgF7Y,2743
|
|
8
|
+
templatedx/tag_plugin.py,sha256=6zcTeHBldvxhUASnsKQmtLkvE8TpZ7vf8OAkbhoPp0M,7246
|
|
9
|
+
templatedx/tag_registry.py,sha256=pv_Cr6zleW3CuojJ2kqSRo-EOifdTZK5_F4Gk4-dxiY,2504
|
|
10
|
+
templatedx/transformer.py,sha256=_WB_La9QrJZoGVZjI1KdArzCyL9GJJF152u_stzaMFc,7903
|
|
11
|
+
templatedx/utils.py,sha256=EViu19Sp3OGoets-BfAOvl0PK3AEEA-Wb5s6r9NxSk4,614
|
|
12
|
+
templatedx/filter_plugins/__init__.py,sha256=GVxR-HnEk0eI6qR1iUKECvbx-hVy0Kyg5sxX1YeJUVo,154
|
|
13
|
+
templatedx/filter_plugins/builtin.py,sha256=5b9Fa_pS_IQBbxeoG0NxTd_dYs7olRiACxZfT6SAVlg,4122
|
|
14
|
+
templatedx/tag_plugins/__init__.py,sha256=dXsiOGqI_lEvBlqUiILvadK67QRRO8e6BnqivDdqwqk,259
|
|
15
|
+
templatedx/tag_plugins/conditional.py,sha256=uV0ZCW3l_9_o7McNky07FrQZXNfR-ZZ4hQTftCoYXko,3369
|
|
16
|
+
templatedx/tag_plugins/for_each.py,sha256=LkvW4Tu-yN0Yj39sSPY8WQMO9NqjmsmOLct2VVoz_og,10699
|
|
17
|
+
templatedx/tag_plugins/raw.py,sha256=yq3J_XOxmzTWULE4CBaJ828SZa8OAOkaW65NTPh-l4Y,905
|
|
18
|
+
agentmark_templatedx-0.1.0.dist-info/METADATA,sha256=yPbj1RdmgT5F6TP67A-T4QMRUWPl8bkOxtY1npENXu8,2990
|
|
19
|
+
agentmark_templatedx-0.1.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
20
|
+
agentmark_templatedx-0.1.0.dist-info/RECORD,,
|
templatedx/__init__.py
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""TemplateDX - Python implementation of the AgentMark templatedx transformer.
|
|
2
|
+
|
|
3
|
+
This package provides a Python implementation of the templatedx transformer
|
|
4
|
+
for processing AgentMark MDX AST trees.
|
|
5
|
+
|
|
6
|
+
Example:
|
|
7
|
+
```python
|
|
8
|
+
import asyncio
|
|
9
|
+
from templatedx import TemplateDX
|
|
10
|
+
|
|
11
|
+
async def main():
|
|
12
|
+
engine = TemplateDX()
|
|
13
|
+
result = await engine.transform(
|
|
14
|
+
ast,
|
|
15
|
+
props={"name": "Alice"}
|
|
16
|
+
)
|
|
17
|
+
print(result)
|
|
18
|
+
|
|
19
|
+
asyncio.run(main())
|
|
20
|
+
```
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from .constants import MDX_JSX_ATTRIBUTE_TYPES, NODE_TYPES
|
|
24
|
+
from .engine import TemplateDX
|
|
25
|
+
from .expression import EvaluationError, ExpressionEvaluator, LexerError, ParseError
|
|
26
|
+
from .filter_registry import FilterRegistry
|
|
27
|
+
from .scope import Scope
|
|
28
|
+
from .tag_plugin import Node, NodeHelpers, PluginContext, TagPlugin
|
|
29
|
+
from .tag_registry import TagPluginRegistry
|
|
30
|
+
from .transformer import NodeTransformer, transform_tree
|
|
31
|
+
|
|
32
|
+
__version__ = "0.1.0"
|
|
33
|
+
|
|
34
|
+
__all__ = [
|
|
35
|
+
# Main engine
|
|
36
|
+
"TemplateDX",
|
|
37
|
+
# Core classes
|
|
38
|
+
"NodeTransformer",
|
|
39
|
+
"Scope",
|
|
40
|
+
"TagPlugin",
|
|
41
|
+
"PluginContext",
|
|
42
|
+
"NodeHelpers",
|
|
43
|
+
# Registries
|
|
44
|
+
"TagPluginRegistry",
|
|
45
|
+
"FilterRegistry",
|
|
46
|
+
# Expression evaluation
|
|
47
|
+
"ExpressionEvaluator",
|
|
48
|
+
"LexerError",
|
|
49
|
+
"ParseError",
|
|
50
|
+
"EvaluationError",
|
|
51
|
+
# Constants
|
|
52
|
+
"NODE_TYPES",
|
|
53
|
+
"MDX_JSX_ATTRIBUTE_TYPES",
|
|
54
|
+
# Types
|
|
55
|
+
"Node",
|
|
56
|
+
# Convenience functions
|
|
57
|
+
"transform_tree",
|
|
58
|
+
]
|
templatedx/constants.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Constants for node types and attribute types."""
|
|
2
|
+
|
|
3
|
+
NODE_TYPES = {
|
|
4
|
+
"MDX_JSX_FLOW_ELEMENT": "mdxJsxFlowElement",
|
|
5
|
+
"MDX_JSX_TEXT_ELEMENT": "mdxJsxTextElement",
|
|
6
|
+
"MDX_JSX_ESM": "mdxjsEsm",
|
|
7
|
+
"YAML": "yaml",
|
|
8
|
+
"MDX_TEXT_EXPRESSION": "mdxTextExpression",
|
|
9
|
+
"MDX_FLOW_EXPRESSION": "mdxFlowExpression",
|
|
10
|
+
"LIST": "list",
|
|
11
|
+
"LIST_ITEM": "listItem",
|
|
12
|
+
"TEXT": "text",
|
|
13
|
+
"PARAGRAPH": "paragraph",
|
|
14
|
+
"HTML": "html",
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
MDX_JSX_ATTRIBUTE_TYPES = {
|
|
18
|
+
"MDX_JSX_ATTRIBUTE": "mdxJsxAttribute",
|
|
19
|
+
"MDX_JSX_ATTRIBUTE_VALUE_EXPRESSION": "mdxJsxAttributeValueExpression",
|
|
20
|
+
"MDX_JSX_EXPRESSION_ATTRIBUTE": "mdxJsxExpressionAttribute",
|
|
21
|
+
}
|
templatedx/engine.py
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
"""TemplateDX engine - main entry point for the templatedx transformer."""
|
|
2
|
+
|
|
3
|
+
from collections.abc import Callable
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from .filter_plugins import register_builtin_filters
|
|
7
|
+
from .filter_registry import FilterRegistry
|
|
8
|
+
from .scope import Scope
|
|
9
|
+
from .tag_plugin import Node, TagPlugin
|
|
10
|
+
from .tag_plugins import ElseIfPlugin, ElsePlugin, ForEachPlugin, IfPlugin, RawPlugin
|
|
11
|
+
from .tag_registry import TagPluginRegistry
|
|
12
|
+
from .transformer import NodeTransformer
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _register_builtin_tag_plugins() -> None:
|
|
16
|
+
"""Register all built-in tag plugins globally."""
|
|
17
|
+
TagPluginRegistry.register_global(IfPlugin(), ["If"])
|
|
18
|
+
TagPluginRegistry.register_global(ElseIfPlugin(), ["ElseIf"])
|
|
19
|
+
TagPluginRegistry.register_global(ElsePlugin(), ["Else"])
|
|
20
|
+
TagPluginRegistry.register_global(ForEachPlugin(), ["ForEach"])
|
|
21
|
+
TagPluginRegistry.register_global(RawPlugin(), ["Raw"])
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# Register built-in plugins on module load
|
|
25
|
+
_register_builtin_tag_plugins()
|
|
26
|
+
register_builtin_filters()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class TemplateDX:
|
|
30
|
+
"""Stateful TemplateDX engine with isolated plugin registries.
|
|
31
|
+
|
|
32
|
+
This is the main entry point for using templatedx. It provides
|
|
33
|
+
instance-level plugin registries that inherit from global registries.
|
|
34
|
+
|
|
35
|
+
Example:
|
|
36
|
+
```python
|
|
37
|
+
engine = TemplateDX()
|
|
38
|
+
|
|
39
|
+
# Transform an AST
|
|
40
|
+
result = await engine.transform(ast, props={"name": "Alice"})
|
|
41
|
+
|
|
42
|
+
# Register custom plugins
|
|
43
|
+
engine.register_tag_plugin(MyPlugin(), ["MyTag"])
|
|
44
|
+
engine.register_filter("double", lambda x: x * 2)
|
|
45
|
+
```
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
def __init__(self) -> None:
|
|
49
|
+
"""Initialize a new TemplateDX engine.
|
|
50
|
+
|
|
51
|
+
Creates instance-level registries that inherit from global registries.
|
|
52
|
+
"""
|
|
53
|
+
self._tag_registry = TagPluginRegistry()
|
|
54
|
+
self._filter_registry = FilterRegistry()
|
|
55
|
+
|
|
56
|
+
# Copy built-in plugins to instance
|
|
57
|
+
self._tag_registry.copy_from_global()
|
|
58
|
+
self._filter_registry.copy_from_global()
|
|
59
|
+
|
|
60
|
+
def register_tag_plugin(self, plugin: TagPlugin, names: list[str]) -> None:
|
|
61
|
+
"""Register a tag plugin on this instance.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
plugin: The tag plugin instance
|
|
65
|
+
names: List of tag names this plugin handles
|
|
66
|
+
"""
|
|
67
|
+
self._tag_registry.register(plugin, names)
|
|
68
|
+
|
|
69
|
+
def remove_tag_plugin(self, name: str) -> None:
|
|
70
|
+
"""Remove a tag plugin from this instance.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
name: Tag name to remove
|
|
74
|
+
"""
|
|
75
|
+
self._tag_registry.remove(name)
|
|
76
|
+
|
|
77
|
+
def get_tag_plugin(self, name: str) -> TagPlugin | None:
|
|
78
|
+
"""Get a tag plugin by name.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
name: Tag name
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
The plugin, or None if not found
|
|
85
|
+
"""
|
|
86
|
+
return self._tag_registry.get(name)
|
|
87
|
+
|
|
88
|
+
def get_tag_registry(self) -> TagPluginRegistry:
|
|
89
|
+
"""Get the tag plugin registry.
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
The tag plugin registry for this instance
|
|
93
|
+
"""
|
|
94
|
+
return self._tag_registry
|
|
95
|
+
|
|
96
|
+
def register_filter(self, name: str, func: Callable[..., Any]) -> None:
|
|
97
|
+
"""Register a filter function on this instance.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
name: Filter name
|
|
101
|
+
func: Filter function
|
|
102
|
+
"""
|
|
103
|
+
self._filter_registry.register(name, func)
|
|
104
|
+
|
|
105
|
+
def remove_filter(self, name: str) -> None:
|
|
106
|
+
"""Remove a filter from this instance.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
name: Filter name to remove
|
|
110
|
+
"""
|
|
111
|
+
self._filter_registry.remove(name)
|
|
112
|
+
|
|
113
|
+
def get_filter(self, name: str) -> Callable[..., Any] | None:
|
|
114
|
+
"""Get a filter function by name.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
name: Filter name
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
The filter function, or None if not found
|
|
121
|
+
"""
|
|
122
|
+
return self._filter_registry.get(name)
|
|
123
|
+
|
|
124
|
+
def get_filter_registry(self) -> FilterRegistry:
|
|
125
|
+
"""Get the filter registry.
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
The filter registry for this instance
|
|
129
|
+
"""
|
|
130
|
+
return self._filter_registry
|
|
131
|
+
|
|
132
|
+
async def transform(
|
|
133
|
+
self,
|
|
134
|
+
tree: Node,
|
|
135
|
+
props: dict[str, Any] | None = None,
|
|
136
|
+
shared: dict[str, Any] | None = None,
|
|
137
|
+
) -> Node:
|
|
138
|
+
"""Transform an AST tree with the given props.
|
|
139
|
+
|
|
140
|
+
Note: Props are wrapped as {"props": props} to match TS behavior.
|
|
141
|
+
Templates access variables as `props.name`, not just `name`.
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
tree: Root AST node (pre-parsed MDX AST as dict)
|
|
145
|
+
props: Props to pass to the template
|
|
146
|
+
shared: Shared/global context accessible from all scopes
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
Transformed AST tree
|
|
150
|
+
"""
|
|
151
|
+
# Wrap props to match TypeScript behavior
|
|
152
|
+
variables = {"props": props if props is not None else {}}
|
|
153
|
+
scope = Scope(variables=variables, shared=shared if shared is not None else {})
|
|
154
|
+
transformer = NodeTransformer(scope, self)
|
|
155
|
+
return await transformer.transform(tree)
|