markdown-script 0.4.2__cp311-abi3-win_amd64.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.
- markdown_script/__init__.py +74 -0
- markdown_script/__init__.pyi +49 -0
- markdown_script/_markdown_script.pyd +0 -0
- markdown_script/_markdown_script.pyi +276 -0
- markdown_script/py.typed +0 -0
- markdown_script-0.4.2.dist-info/METADATA +149 -0
- markdown_script-0.4.2.dist-info/RECORD +10 -0
- markdown_script-0.4.2.dist-info/WHEEL +4 -0
- markdown_script-0.4.2.dist-info/licenses/LICENSE +21 -0
- markdown_script-0.4.2.dist-info/sboms/mds-python.cyclonedx.json +1801 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""markdown_script — composable LLM prompt template compiler (native Python bindings).
|
|
2
|
+
|
|
3
|
+
Compile ``.mds`` templates to Markdown or structured chat messages in-process, via
|
|
4
|
+
the same Rust core that powers the MDS CLI and Node.js/WASM bindings. Output is
|
|
5
|
+
byte-identical across all bindings.
|
|
6
|
+
|
|
7
|
+
Example
|
|
8
|
+
-------
|
|
9
|
+
>>> import markdown_script
|
|
10
|
+
>>> r = markdown_script.compile("Hello {{name}}!", vars={"name": "Alice"})
|
|
11
|
+
>>> r.kind, r.output
|
|
12
|
+
('markdown', 'Hello Alice!')
|
|
13
|
+
|
|
14
|
+
Errors raise :class:`MdsError`, which carries ``.code``, ``.message``, ``.help``,
|
|
15
|
+
and ``.span``. Compilation is synchronous CPU work and releases the GIL, so it
|
|
16
|
+
parallelises across threads; wrap a call in ``asyncio.to_thread`` for async code.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
from importlib import metadata as _metadata
|
|
22
|
+
|
|
23
|
+
from ._markdown_script import (
|
|
24
|
+
CheckResult,
|
|
25
|
+
CompileResult,
|
|
26
|
+
LintDiagnostic,
|
|
27
|
+
LintFileReport,
|
|
28
|
+
LintResult,
|
|
29
|
+
MdsError,
|
|
30
|
+
Message,
|
|
31
|
+
Span,
|
|
32
|
+
check,
|
|
33
|
+
check_file,
|
|
34
|
+
check_virtual,
|
|
35
|
+
compile,
|
|
36
|
+
compile_file,
|
|
37
|
+
compile_virtual,
|
|
38
|
+
lint,
|
|
39
|
+
lint_file,
|
|
40
|
+
lint_virtual,
|
|
41
|
+
scan_imports,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
# The native exception is registered under the extension submodule `_markdown_script`.
|
|
45
|
+
# Retag it (and it alone — the result classes already declare `module = "markdown_script"`)
|
|
46
|
+
# to the public package so `pickle`, `repr`, and tracebacks resolve `markdown_script.MdsError`.
|
|
47
|
+
MdsError.__module__ = "markdown_script"
|
|
48
|
+
|
|
49
|
+
try:
|
|
50
|
+
__version__ = _metadata.version("markdown-script")
|
|
51
|
+
except _metadata.PackageNotFoundError: # pragma: no cover - source tree without an install
|
|
52
|
+
__version__ = "0.0.0"
|
|
53
|
+
|
|
54
|
+
__all__ = [
|
|
55
|
+
"CheckResult",
|
|
56
|
+
"CompileResult",
|
|
57
|
+
"LintDiagnostic",
|
|
58
|
+
"LintFileReport",
|
|
59
|
+
"LintResult",
|
|
60
|
+
"MdsError",
|
|
61
|
+
"Message",
|
|
62
|
+
"Span",
|
|
63
|
+
"__version__",
|
|
64
|
+
"check",
|
|
65
|
+
"check_file",
|
|
66
|
+
"check_virtual",
|
|
67
|
+
"compile",
|
|
68
|
+
"compile_file",
|
|
69
|
+
"compile_virtual",
|
|
70
|
+
"lint",
|
|
71
|
+
"lint_file",
|
|
72
|
+
"lint_virtual",
|
|
73
|
+
"scan_imports",
|
|
74
|
+
]
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""Public type surface for the ``markdown_script`` package.
|
|
2
|
+
|
|
3
|
+
Everything is re-exported from the native ``._markdown_script`` extension; see
|
|
4
|
+
``_markdown_script.pyi`` for the full signatures.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from ._markdown_script import CheckResult as CheckResult
|
|
10
|
+
from ._markdown_script import CompileResult as CompileResult
|
|
11
|
+
from ._markdown_script import LintDiagnostic as LintDiagnostic
|
|
12
|
+
from ._markdown_script import LintFileReport as LintFileReport
|
|
13
|
+
from ._markdown_script import LintResult as LintResult
|
|
14
|
+
from ._markdown_script import MdsError as MdsError
|
|
15
|
+
from ._markdown_script import Message as Message
|
|
16
|
+
from ._markdown_script import Span as Span
|
|
17
|
+
from ._markdown_script import check as check
|
|
18
|
+
from ._markdown_script import check_file as check_file
|
|
19
|
+
from ._markdown_script import check_virtual as check_virtual
|
|
20
|
+
from ._markdown_script import compile as compile
|
|
21
|
+
from ._markdown_script import compile_file as compile_file
|
|
22
|
+
from ._markdown_script import compile_virtual as compile_virtual
|
|
23
|
+
from ._markdown_script import lint as lint
|
|
24
|
+
from ._markdown_script import lint_file as lint_file
|
|
25
|
+
from ._markdown_script import lint_virtual as lint_virtual
|
|
26
|
+
from ._markdown_script import scan_imports as scan_imports
|
|
27
|
+
|
|
28
|
+
__version__: str
|
|
29
|
+
__all__ = [
|
|
30
|
+
"CheckResult",
|
|
31
|
+
"CompileResult",
|
|
32
|
+
"LintDiagnostic",
|
|
33
|
+
"LintFileReport",
|
|
34
|
+
"LintResult",
|
|
35
|
+
"MdsError",
|
|
36
|
+
"Message",
|
|
37
|
+
"Span",
|
|
38
|
+
"__version__",
|
|
39
|
+
"check",
|
|
40
|
+
"check_file",
|
|
41
|
+
"check_virtual",
|
|
42
|
+
"compile",
|
|
43
|
+
"compile_file",
|
|
44
|
+
"compile_virtual",
|
|
45
|
+
"lint",
|
|
46
|
+
"lint_file",
|
|
47
|
+
"lint_virtual",
|
|
48
|
+
"scan_imports",
|
|
49
|
+
]
|
|
Binary file
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
"""Type stubs for the native ``markdown_script._markdown_script`` extension module.
|
|
2
|
+
|
|
3
|
+
The runtime objects are implemented in Rust (PyO3). These stubs describe the public
|
|
4
|
+
surface for ``mypy``/``pyright``. Result classes are frozen — their attributes are
|
|
5
|
+
read-only properties, so they are declared with ``@property``.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from collections.abc import Mapping
|
|
11
|
+
from os import PathLike
|
|
12
|
+
from typing import Any, Literal, final
|
|
13
|
+
|
|
14
|
+
# `str | os.PathLike[str]` — accepted for `path` and `base_path`.
|
|
15
|
+
_StrPath = str | PathLike[str]
|
|
16
|
+
# A `vars` mapping: string keys to JSON-compatible values.
|
|
17
|
+
_Vars = Mapping[str, Any]
|
|
18
|
+
|
|
19
|
+
@final
|
|
20
|
+
class Span:
|
|
21
|
+
"""A source span attached to an :class:`MdsError` (frozen, unhashable)."""
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
def offset(self) -> int: ...
|
|
25
|
+
@property
|
|
26
|
+
def length(self) -> int: ...
|
|
27
|
+
@property
|
|
28
|
+
def line(self) -> int | None: ...
|
|
29
|
+
@property
|
|
30
|
+
def column(self) -> int | None: ...
|
|
31
|
+
def __new__(
|
|
32
|
+
cls,
|
|
33
|
+
offset: int,
|
|
34
|
+
length: int,
|
|
35
|
+
line: int | None = ...,
|
|
36
|
+
column: int | None = ...,
|
|
37
|
+
) -> Span: ...
|
|
38
|
+
def to_dict(self) -> dict[str, Any]: ...
|
|
39
|
+
def to_json(self) -> str: ...
|
|
40
|
+
def __eq__(self, other: object, /) -> bool: ...
|
|
41
|
+
__hash__: None # type: ignore[assignment]
|
|
42
|
+
|
|
43
|
+
@final
|
|
44
|
+
class Message:
|
|
45
|
+
"""A single chat message from a `@message`-bearing template (frozen)."""
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def role(self) -> str: ...
|
|
49
|
+
@property
|
|
50
|
+
def content(self) -> str: ...
|
|
51
|
+
def __new__(cls, role: str, content: str) -> Message: ...
|
|
52
|
+
def to_dict(self) -> dict[str, str]: ...
|
|
53
|
+
def to_json(self) -> str: ...
|
|
54
|
+
def __eq__(self, other: object, /) -> bool: ...
|
|
55
|
+
__hash__: None # type: ignore[assignment]
|
|
56
|
+
|
|
57
|
+
@final
|
|
58
|
+
class CheckResult:
|
|
59
|
+
"""The result of :func:`check`, :func:`check_file`, or :func:`check_virtual`."""
|
|
60
|
+
|
|
61
|
+
@property
|
|
62
|
+
def warnings(self) -> list[str]: ...
|
|
63
|
+
def __new__(cls, warnings: list[str]) -> CheckResult: ...
|
|
64
|
+
def to_dict(self) -> dict[str, Any]: ...
|
|
65
|
+
def to_json(self) -> str: ...
|
|
66
|
+
def __eq__(self, other: object, /) -> bool: ...
|
|
67
|
+
__hash__: None # type: ignore[assignment]
|
|
68
|
+
|
|
69
|
+
@final
|
|
70
|
+
class CompileResult:
|
|
71
|
+
"""The result of :func:`compile`, :func:`compile_file`, or :func:`compile_virtual`.
|
|
72
|
+
|
|
73
|
+
``kind`` is ``"markdown"`` or ``"messages"``. On a ``markdown`` result
|
|
74
|
+
``messages`` is ``None``; on a ``messages`` result ``output`` is ``None``.
|
|
75
|
+
|
|
76
|
+
``source_map`` is a Source Map v3 ``dict`` when ``source_map=True`` was passed
|
|
77
|
+
to the compile function and the result is Markdown; otherwise ``None``.
|
|
78
|
+
The wire key in ``to_dict()`` / ``to_json()`` is ``"sourceMap"`` (camelCase).
|
|
79
|
+
|
|
80
|
+
**``to_dict()`` vs ``to_json()`` asymmetry**: ``to_dict()`` always includes
|
|
81
|
+
the ``"sourceMap"`` key (``None`` when no map was generated) for
|
|
82
|
+
Python-idiomatic always-present attribute access. ``to_json()`` omits the key
|
|
83
|
+
when absent — that is the canonical wire format shared with Node.js and WASM.
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
@property
|
|
87
|
+
def kind(self) -> Literal["markdown", "messages"]: ...
|
|
88
|
+
@property
|
|
89
|
+
def output(self) -> str | None: ...
|
|
90
|
+
@property
|
|
91
|
+
def messages(self) -> list[Message] | None: ...
|
|
92
|
+
@property
|
|
93
|
+
def warnings(self) -> list[str]: ...
|
|
94
|
+
@property
|
|
95
|
+
def dependencies(self) -> list[str]: ...
|
|
96
|
+
@property
|
|
97
|
+
def source_map(self) -> dict[str, Any] | None: ...
|
|
98
|
+
def __new__(cls, canonical: Mapping[str, Any]) -> CompileResult: ...
|
|
99
|
+
def to_dict(self) -> dict[str, Any]: ...
|
|
100
|
+
def to_json(self) -> str: ...
|
|
101
|
+
def __eq__(self, other: object, /) -> bool: ...
|
|
102
|
+
__hash__: None # type: ignore[assignment]
|
|
103
|
+
|
|
104
|
+
@final
|
|
105
|
+
class LintDiagnostic:
|
|
106
|
+
"""A single lint finding within a :class:`LintFileReport` (frozen, unhashable).
|
|
107
|
+
|
|
108
|
+
Attributes map directly to the canonical wire-format diagnostic object.
|
|
109
|
+
``help`` is ``None`` when the rule emits no hint; ``span`` is ``None`` for
|
|
110
|
+
rules that do not attach a source offset. Both attributes are always present.
|
|
111
|
+
"""
|
|
112
|
+
|
|
113
|
+
@property
|
|
114
|
+
def rule(self) -> str: ...
|
|
115
|
+
@property
|
|
116
|
+
def severity(self) -> str: ...
|
|
117
|
+
@property
|
|
118
|
+
def message(self) -> str: ...
|
|
119
|
+
@property
|
|
120
|
+
def help(self) -> str | None: ...
|
|
121
|
+
@property
|
|
122
|
+
def fixable(self) -> bool: ...
|
|
123
|
+
@property
|
|
124
|
+
def span(self) -> Span | None: ...
|
|
125
|
+
@property
|
|
126
|
+
def fix_edits(self) -> list[dict[str, Any]] | None: ...
|
|
127
|
+
def __new__(
|
|
128
|
+
cls,
|
|
129
|
+
rule: str,
|
|
130
|
+
severity: str,
|
|
131
|
+
message: str,
|
|
132
|
+
help: str | None = ...,
|
|
133
|
+
fixable: bool = ...,
|
|
134
|
+
span: Span | None = ...,
|
|
135
|
+
fix_edits_json: str | None = ...,
|
|
136
|
+
) -> LintDiagnostic: ...
|
|
137
|
+
def to_dict(self) -> dict[str, Any]: ...
|
|
138
|
+
def to_json(self) -> str: ...
|
|
139
|
+
def __eq__(self, other: object, /) -> bool: ...
|
|
140
|
+
__hash__: None # type: ignore[assignment]
|
|
141
|
+
|
|
142
|
+
@final
|
|
143
|
+
class LintFileReport:
|
|
144
|
+
"""Per-file findings group from :attr:`LintResult.files` (frozen, unhashable).
|
|
145
|
+
|
|
146
|
+
``file`` is the path key for this file's diagnostics; ``diagnostics`` is a
|
|
147
|
+
typed list of :class:`LintDiagnostic` objects with fully-typed attributes.
|
|
148
|
+
"""
|
|
149
|
+
|
|
150
|
+
@property
|
|
151
|
+
def file(self) -> str: ...
|
|
152
|
+
@property
|
|
153
|
+
def diagnostics(self) -> list[LintDiagnostic]: ...
|
|
154
|
+
def __new__(
|
|
155
|
+
cls,
|
|
156
|
+
file: str,
|
|
157
|
+
diagnostics: list[LintDiagnostic],
|
|
158
|
+
) -> LintFileReport: ...
|
|
159
|
+
def to_dict(self) -> dict[str, Any]: ...
|
|
160
|
+
def to_json(self) -> str: ...
|
|
161
|
+
def __eq__(self, other: object, /) -> bool: ...
|
|
162
|
+
__hash__: None # type: ignore[assignment]
|
|
163
|
+
|
|
164
|
+
@final
|
|
165
|
+
class LintResult:
|
|
166
|
+
"""The result of :func:`lint`, :func:`lint_file`, or :func:`lint_virtual`.
|
|
167
|
+
|
|
168
|
+
Core JSON shape: ``{"files":[...],"truncated":false,"version":1}``.
|
|
169
|
+
When non-fatal warnings occur (e.g. unknown rule names),
|
|
170
|
+
``"lint_warnings"`` also appears in alphabetical key order between
|
|
171
|
+
``"files"`` and ``"truncated"``. Keys are in BTreeMap (alphabetical)
|
|
172
|
+
order. The CLI surface writes warnings to stderr rather than including
|
|
173
|
+
them in its JSON stdout.
|
|
174
|
+
|
|
175
|
+
``files`` is a list of typed :class:`LintFileReport` objects. Each report
|
|
176
|
+
exposes ``.file`` (str) and ``.diagnostics`` (list[:class:`LintDiagnostic`]).
|
|
177
|
+
"""
|
|
178
|
+
|
|
179
|
+
@property
|
|
180
|
+
def version(self) -> int: ...
|
|
181
|
+
@property
|
|
182
|
+
def truncated(self) -> bool: ...
|
|
183
|
+
@property
|
|
184
|
+
def files(self) -> list[LintFileReport]: ...
|
|
185
|
+
@property
|
|
186
|
+
def lint_warnings(self) -> list[str]:
|
|
187
|
+
"""Non-fatal warnings raised while configuring the lint run.
|
|
188
|
+
|
|
189
|
+
An unknown rule name in the ``rules`` mapping produces one entry here:
|
|
190
|
+
the rule is not enforced (it does not exist) but the call still
|
|
191
|
+
succeeds. Empty in the common case.
|
|
192
|
+
"""
|
|
193
|
+
def __new__(cls, canonical: Mapping[str, Any]) -> LintResult: ...
|
|
194
|
+
def to_dict(self) -> dict[str, Any]: ...
|
|
195
|
+
def to_json(self) -> str: ...
|
|
196
|
+
def __eq__(self, other: object, /) -> bool: ...
|
|
197
|
+
__hash__: None # type: ignore[assignment]
|
|
198
|
+
|
|
199
|
+
class MdsError(Exception):
|
|
200
|
+
"""Raised for every MDS compilation failure.
|
|
201
|
+
|
|
202
|
+
``str(err) == err.message``.
|
|
203
|
+
"""
|
|
204
|
+
|
|
205
|
+
code: str
|
|
206
|
+
message: str
|
|
207
|
+
help: str | None
|
|
208
|
+
span: Span | None
|
|
209
|
+
|
|
210
|
+
def compile(
|
|
211
|
+
source: str,
|
|
212
|
+
*,
|
|
213
|
+
vars: _Vars | None = ...,
|
|
214
|
+
base_path: _StrPath | None = ...,
|
|
215
|
+
source_map: bool = ...,
|
|
216
|
+
sources_content: bool = ...,
|
|
217
|
+
) -> CompileResult: ...
|
|
218
|
+
def compile_file(
|
|
219
|
+
path: _StrPath,
|
|
220
|
+
*,
|
|
221
|
+
vars: _Vars | None = ...,
|
|
222
|
+
source_map: bool = ...,
|
|
223
|
+
sources_content: bool = ...,
|
|
224
|
+
) -> CompileResult: ...
|
|
225
|
+
def compile_virtual(
|
|
226
|
+
modules: Mapping[str, str],
|
|
227
|
+
entry: str,
|
|
228
|
+
*,
|
|
229
|
+
vars: _Vars | None = ...,
|
|
230
|
+
source_map: bool = ...,
|
|
231
|
+
sources_content: bool = ...,
|
|
232
|
+
) -> CompileResult: ...
|
|
233
|
+
def check(
|
|
234
|
+
source: str,
|
|
235
|
+
*,
|
|
236
|
+
vars: _Vars | None = ...,
|
|
237
|
+
base_path: _StrPath | None = ...,
|
|
238
|
+
source_map: None = ...,
|
|
239
|
+
sources_content: None = ...,
|
|
240
|
+
) -> CheckResult:
|
|
241
|
+
"""Validate without rendering.
|
|
242
|
+
|
|
243
|
+
``source_map`` and ``sources_content`` are present in the signature so that
|
|
244
|
+
passing them raises ``MdsError(code="mds::invalid_options")`` rather than a
|
|
245
|
+
bare ``TypeError``. Callers should never pass these — use :func:`compile` for
|
|
246
|
+
source-map generation.
|
|
247
|
+
"""
|
|
248
|
+
...
|
|
249
|
+
def check_file(path: _StrPath, *, vars: _Vars | None = ...) -> CheckResult: ...
|
|
250
|
+
def check_virtual(
|
|
251
|
+
modules: Mapping[str, str],
|
|
252
|
+
entry: str,
|
|
253
|
+
*,
|
|
254
|
+
vars: _Vars | None = ...,
|
|
255
|
+
) -> CheckResult: ...
|
|
256
|
+
def scan_imports(source: str, /) -> list[str]: ...
|
|
257
|
+
def lint(
|
|
258
|
+
source: str,
|
|
259
|
+
*,
|
|
260
|
+
vars: _Vars | None = ...,
|
|
261
|
+
base_path: _StrPath | None = ...,
|
|
262
|
+
rules: Mapping[str, str] | None = ...,
|
|
263
|
+
) -> LintResult: ...
|
|
264
|
+
def lint_file(
|
|
265
|
+
path: _StrPath,
|
|
266
|
+
*,
|
|
267
|
+
vars: _Vars | None = ...,
|
|
268
|
+
rules: Mapping[str, str] | None = ...,
|
|
269
|
+
) -> LintResult: ...
|
|
270
|
+
def lint_virtual(
|
|
271
|
+
modules: Mapping[str, str],
|
|
272
|
+
entry: str,
|
|
273
|
+
*,
|
|
274
|
+
vars: _Vars | None = ...,
|
|
275
|
+
rules: Mapping[str, str] | None = ...,
|
|
276
|
+
) -> LintResult: ...
|
markdown_script/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: markdown-script
|
|
3
|
+
Version: 0.4.2
|
|
4
|
+
Classifier: Development Status :: 4 - Beta
|
|
5
|
+
Classifier: Intended Audience :: Developers
|
|
6
|
+
Classifier: Operating System :: OS Independent
|
|
7
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
8
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
11
|
+
Classifier: Programming Language :: Rust
|
|
12
|
+
Classifier: Topic :: Software Development :: Compilers
|
|
13
|
+
Classifier: Topic :: Text Processing :: Markup :: Markdown
|
|
14
|
+
Classifier: Typing :: Typed
|
|
15
|
+
License-File: LICENSE
|
|
16
|
+
Summary: Composable LLM prompt template compiler — native Python bindings for MDS (Markdown Script)
|
|
17
|
+
Keywords: markdown,template,llm,prompt,compiler
|
|
18
|
+
Author: Dean Sharon
|
|
19
|
+
License-Expression: MIT
|
|
20
|
+
Requires-Python: >=3.11
|
|
21
|
+
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
|
|
22
|
+
Project-URL: Homepage, https://github.com/dean0x/mdscript
|
|
23
|
+
Project-URL: Issues, https://github.com/dean0x/mdscript/issues
|
|
24
|
+
Project-URL: Repository, https://github.com/dean0x/mdscript
|
|
25
|
+
|
|
26
|
+
# markdown-script
|
|
27
|
+
|
|
28
|
+
Native **Python bindings** for [MDS (Markdown Script)](https://github.com/dean0x/mdscript) —
|
|
29
|
+
a composable LLM prompt-template compiler. Compile `.mds` templates to Markdown or
|
|
30
|
+
structured chat messages in-process, backed by the same Rust core as the MDS CLI and
|
|
31
|
+
the Node.js / WASM bindings. Output is byte-identical across every binding.
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pip install markdown-script
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
> **Not yet on PyPI** — the `markdown-script` name registration and PyPI publishing are
|
|
38
|
+
> tracked in [#292] (rename + registration) and [#132] (wheel matrix + PyPI publishing
|
|
39
|
+
> pipeline). For now, build from source: `pip install ./crates/mds-python` (or `maturin
|
|
40
|
+
> build -m crates/mds-python/Cargo.toml` to produce a wheel), with a Rust toolchain and
|
|
41
|
+
> `python3` on `PATH`. Once published, wheels ship as `cp311-abi3` (CPython 3.11+, one
|
|
42
|
+
> wheel per platform).
|
|
43
|
+
|
|
44
|
+
[#292]: https://github.com/dean0x/mdscript/issues/292
|
|
45
|
+
[#132]: https://github.com/dean0x/mdscript/issues/132
|
|
46
|
+
|
|
47
|
+
## Quick start
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
import markdown_script
|
|
51
|
+
|
|
52
|
+
# Markdown template
|
|
53
|
+
r = markdown_script.compile("Hello {{name}}!", vars={"name": "Alice"})
|
|
54
|
+
assert r.kind == "markdown"
|
|
55
|
+
assert r.output == "Hello Alice!"
|
|
56
|
+
|
|
57
|
+
# @message template → structured messages
|
|
58
|
+
r = markdown_script.compile("@message user:\nHi\n@end\n")
|
|
59
|
+
assert r.kind == "messages"
|
|
60
|
+
assert r.messages[0].role == "user"
|
|
61
|
+
assert r.output is None # inactive payload is None
|
|
62
|
+
|
|
63
|
+
# Validate without rendering
|
|
64
|
+
markdown_script.check("Hello {{name}}!", vars={"name": "Bob"})
|
|
65
|
+
|
|
66
|
+
# Compile a file (dependencies come back as absolute paths)
|
|
67
|
+
r = markdown_script.compile_file("prompts/agent.mds")
|
|
68
|
+
print(r.dependencies)
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## API
|
|
72
|
+
|
|
73
|
+
All compile/check functions return a typed, picklable result. Keyword arguments are
|
|
74
|
+
keyword-only; `scan_imports` takes its argument positionally.
|
|
75
|
+
|
|
76
|
+
| Function | Signature |
|
|
77
|
+
|----------|-----------|
|
|
78
|
+
| `compile` | `compile(source, *, vars=None, base_path=None, source_map=False, sources_content=False) -> CompileResult` |
|
|
79
|
+
| `compile_file` | `compile_file(path, *, vars=None, source_map=False, sources_content=False) -> CompileResult` |
|
|
80
|
+
| `compile_virtual` | `compile_virtual(modules, entry, *, vars=None, source_map=False, sources_content=False) -> CompileResult` |
|
|
81
|
+
| `check` | `check(source, *, vars=None, base_path=None) -> CheckResult` |
|
|
82
|
+
| `check_file` | `check_file(path, *, vars=None) -> CheckResult` |
|
|
83
|
+
| `check_virtual` | `check_virtual(modules, entry, *, vars=None) -> CheckResult` |
|
|
84
|
+
| `scan_imports` | `scan_imports(source, /) -> list[str]` |
|
|
85
|
+
| `lint` | `lint(source, *, vars=None, base_path=None, rules=None) -> LintResult` |
|
|
86
|
+
| `lint_file` | `lint_file(path, *, vars=None, rules=None) -> LintResult` |
|
|
87
|
+
| `lint_virtual` | `lint_virtual(modules, entry, *, vars=None, rules=None) -> LintResult` |
|
|
88
|
+
|
|
89
|
+
- `path` / `base_path` accept `str` or `os.PathLike`.
|
|
90
|
+
- `vars` is a mapping of string keys to JSON-compatible values; a non-mapping raises
|
|
91
|
+
`MdsError(code="mds::invalid_options")`.
|
|
92
|
+
- `compile_virtual` / `check_virtual` / `lint_virtual` resolve imports against an in-memory
|
|
93
|
+
map; `entry` must be a key in `modules`.
|
|
94
|
+
- `source_map=True` generates a Source Map v3 document; `result.source_map` is a `dict`.
|
|
95
|
+
For string-source compiles `sources[0]` is `"input.mds"`. `sources_content=True` embeds
|
|
96
|
+
the original source text in `sourcesContent[]` (requires `source_map=True`).
|
|
97
|
+
⚠ Privacy: `sources_content=True` embeds the full template source in the map.
|
|
98
|
+
- `rules` is a mapping of rule name → severity string (`"off"`, `"info"`, `"warn"`, `"error"`).
|
|
99
|
+
Unknown severity values raise `MdsError(code="mds::invalid_options")`; unknown rule names
|
|
100
|
+
emit a warning and lint continues — the unknown name has no effect, but a non-empty
|
|
101
|
+
`result.lint_warnings` list signals the problem so callers can surface it.
|
|
102
|
+
`LintResult` exposes `.version`, `.truncated`, `.lint_warnings`, `.to_dict()`, `.to_json()`, and `.files`
|
|
103
|
+
— a `list[LintFileReport]`. Each `LintFileReport` has `.file` (`str`) and `.diagnostics`
|
|
104
|
+
(`list[LintDiagnostic]`). `LintDiagnostic` carries `.rule`, `.severity`, `.message`,
|
|
105
|
+
`.help` (`str | None`), `.fixable` (`bool`), `.fix_edits` (`list[dict] | None`), and `.span` (`Span | None`).
|
|
106
|
+
`LintFileReport` and `LintDiagnostic` are frozen, picklable, and comparable by value.
|
|
107
|
+
**`files[].file` key:** `lint()` sets this to `"input.mds"` (string-source); `lint_file()` sets it to
|
|
108
|
+
the file's path; `lint_virtual()` sets it to the caller-supplied entry key. The CLI additionally
|
|
109
|
+
relabels stdin input as `"<stdin>"` — this asymmetry does not apply to the Python binding.
|
|
110
|
+
|
|
111
|
+
### Result objects
|
|
112
|
+
|
|
113
|
+
`CompileResult` exposes `.kind` (`"markdown"` | `"messages"`), `.output` (`str | None`),
|
|
114
|
+
`.messages` (`list[Message] | None`), `.warnings`, `.dependencies`, and `.source_map`
|
|
115
|
+
(`dict | None`). `CheckResult` exposes `.warnings`. Both offer `.to_dict()` and `.to_json()`.
|
|
116
|
+
|
|
117
|
+
> **`to_dict()` vs `to_json()` asymmetry (source maps):** `CompileResult.to_dict()` always
|
|
118
|
+
> includes `"sourceMap": None` when no source map was generated — Python-idiomatic
|
|
119
|
+
> always-present. `to_json()` omits the key when absent, matching the canonical wire format
|
|
120
|
+
> shared with the CLI, napi, and WASM surfaces for byte-identical cross-surface parity.
|
|
121
|
+
|
|
122
|
+
Results are frozen, comparable by value, intentionally unhashable, and picklable.
|
|
123
|
+
|
|
124
|
+
### Errors
|
|
125
|
+
|
|
126
|
+
Every failure raises `markdown_script.MdsError` (a subclass of `Exception`):
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
try:
|
|
130
|
+
markdown_script.compile("Hello {{undefined}}!")
|
|
131
|
+
except markdown_script.MdsError as e:
|
|
132
|
+
print(e.code) # "mds::undefined_var"
|
|
133
|
+
print(str(e)) # == e.message
|
|
134
|
+
print(e.help) # hint, or None
|
|
135
|
+
if e.span:
|
|
136
|
+
print(e.span.line, e.span.column) # 1-indexed
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Concurrency
|
|
140
|
+
|
|
141
|
+
Compilation is synchronous, stateless CPU work and **releases the GIL**, so calls
|
|
142
|
+
parallelise across threads. For `asyncio`, offload with `asyncio.to_thread(markdown_script.compile, src)`.
|
|
143
|
+
The extension is also free-threading (`cp314t`) ready — result classes are frozen and
|
|
144
|
+
the module declares `gil_used = false` — though a free-threaded wheel is not yet shipped.
|
|
145
|
+
|
|
146
|
+
## License
|
|
147
|
+
|
|
148
|
+
MIT © the MDS authors.
|
|
149
|
+
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
markdown_script/__init__.py,sha256=9hXho1NkWZ039Sg0seAXmtNs6dTNP0J4dY29rz3ZZow,2039
|
|
2
|
+
markdown_script/__init__.pyi,sha256=J2k2waz3MFBPrUNXtMdePSv_6Vup2lSooalPynwdqCs,1645
|
|
3
|
+
markdown_script/_markdown_script.pyd,sha256=BxfsFdX_2wUeaEbcKGN1d51sWwbWHvfmDEUj673e-nQ,1495552
|
|
4
|
+
markdown_script/_markdown_script.pyi,sha256=s-X23m6Wugfj1D8DFaigLfZzLgeHwzH4oPitRm_t2Ls,9090
|
|
5
|
+
markdown_script/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
+
markdown_script-0.4.2.dist-info/METADATA,sha256=7OJ6El0Sz97H63DfXEjzZGMKCwiWvinu38NBL7KLivg,7305
|
|
7
|
+
markdown_script-0.4.2.dist-info/WHEEL,sha256=RBTVKNPkFN9rJmwGU7ihNrh3V0g9JbkE0zuZYATRViM,96
|
|
8
|
+
markdown_script-0.4.2.dist-info/licenses/LICENSE,sha256=8HfEydh7mCgn17GknkD786w-6uQlxqgdzaNjW5YPIyo,1089
|
|
9
|
+
markdown_script-0.4.2.dist-info/sboms/mds-python.cyclonedx.json,sha256=ahmg0qGgRNeHpVyF2EytrLy-C8rjTR6Aor4vMvhFW-0,55657
|
|
10
|
+
markdown_script-0.4.2.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Dean Sharon
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|