roborean-documents-markdown 0.1.2__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.
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
venv/
|
|
2
|
+
venv-qt5/
|
|
3
|
+
venv-qt6/
|
|
4
|
+
.venv/
|
|
5
|
+
__pycache__/
|
|
6
|
+
*.py[cod]
|
|
7
|
+
.pytest_cache/
|
|
8
|
+
.mypy_cache/
|
|
9
|
+
.coverage
|
|
10
|
+
htmlcov/
|
|
11
|
+
dist/
|
|
12
|
+
build/
|
|
13
|
+
*.egg-info/
|
|
14
|
+
*.tsbuildinfo
|
|
15
|
+
node_modules/
|
|
16
|
+
.pnpm-store/
|
|
17
|
+
.DS_Store
|
|
18
|
+
.idea/
|
|
19
|
+
.vscode/*.local
|
|
20
|
+
playground/
|
|
21
|
+
.e2e-ai/
|
|
22
|
+
**/test-results/
|
|
23
|
+
**/playwright-report/
|
|
24
|
+
research/
|
|
25
|
+
.roborean/
|
|
26
|
+
.roborean-sql-artifacts/
|
|
27
|
+
*.db
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [Unreleased]
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- `roborean.markdown` driver emitting CommonMark from flow operations.
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
- Lower inter-package dependency pins to `>=0.1.1` to match lockstep
|
|
15
|
+
release versions.
|
|
16
|
+
- Normalize template newlines to LF for portable CommonMark output.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: roborean-documents-markdown
|
|
3
|
+
Version: 0.1.2
|
|
4
|
+
Summary: Markdown document driver for Roborean
|
|
5
|
+
Project-URL: Homepage, https://github.com/TNick/roborean
|
|
6
|
+
Project-URL: Repository, https://github.com/TNick/roborean
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
Requires-Python: >=3.11
|
|
9
|
+
Requires-Dist: roborean-documents-base>=0.1.1
|
|
10
|
+
Requires-Dist: roborean-spec>=0.1.1
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "roborean-documents-markdown"
|
|
7
|
+
version = "0.1.2"
|
|
8
|
+
description = "Markdown document driver for Roborean"
|
|
9
|
+
requires-python = ">=3.11"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
dependencies = [
|
|
12
|
+
"roborean-documents-base>=0.1.1",
|
|
13
|
+
"roborean-spec>=0.1.1",
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
[project.entry-points."roborean.document_drivers"]
|
|
17
|
+
"roborean.markdown" = "roborean_documents_markdown.driver:create_driver"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
[project.urls]
|
|
21
|
+
Homepage = "https://github.com/TNick/roborean"
|
|
22
|
+
Repository = "https://github.com/TNick/roborean"
|
|
23
|
+
|
|
24
|
+
[tool.hatch.build.targets.wheel]
|
|
25
|
+
packages = ["src/roborean_documents_markdown"]
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"""Markdown / CommonMark document driver."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from datetime import UTC, datetime
|
|
5
|
+
from typing import Any, Mapping
|
|
6
|
+
|
|
7
|
+
from roborean_documents_base.capabilities import assert_op_allowed
|
|
8
|
+
from roborean_spec import (
|
|
9
|
+
DocumentDriverManifest,
|
|
10
|
+
DocumentOperation,
|
|
11
|
+
DocumentPreview,
|
|
12
|
+
TemplateManifest,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class MarkdownSession:
|
|
18
|
+
"""AST buffer for Markdown generation."""
|
|
19
|
+
|
|
20
|
+
document_id: str
|
|
21
|
+
driver_id: str
|
|
22
|
+
nodes: list[dict[str, Any]] = field(default_factory=list)
|
|
23
|
+
ops_applied: list[dict[str, Any]] = field(default_factory=list)
|
|
24
|
+
template_prefix: str = ""
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def render_commonmark(nodes: list[dict[str, Any]], prefix: str = "") -> str:
|
|
28
|
+
"""Render a minimal node list to CommonMark."""
|
|
29
|
+
parts: list[str] = []
|
|
30
|
+
if prefix:
|
|
31
|
+
parts.append(prefix.rstrip("\n"))
|
|
32
|
+
for node in nodes:
|
|
33
|
+
kind = node["kind"]
|
|
34
|
+
if kind == "heading":
|
|
35
|
+
parts.append("#" * int(node["level"]) + " " + node["text"])
|
|
36
|
+
elif kind == "paragraph":
|
|
37
|
+
parts.append(node["text"])
|
|
38
|
+
elif kind == "table":
|
|
39
|
+
rows = node["rows"]
|
|
40
|
+
if not rows:
|
|
41
|
+
continue
|
|
42
|
+
header = rows[0]
|
|
43
|
+
parts.append("| " + " | ".join(header) + " |")
|
|
44
|
+
parts.append("| " + " | ".join("---" for _ in header) + " |")
|
|
45
|
+
for row in rows[1:]:
|
|
46
|
+
parts.append("| " + " | ".join(row) + " |")
|
|
47
|
+
body = "\n\n".join(parts).rstrip() + "\n"
|
|
48
|
+
return body
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class MarkdownDocumentDriver:
|
|
52
|
+
"""CommonMark driver using flow operations."""
|
|
53
|
+
|
|
54
|
+
driver_id = "roborean.markdown"
|
|
55
|
+
manifest = DocumentDriverManifest(
|
|
56
|
+
driverId="roborean.markdown",
|
|
57
|
+
version="0.3.0",
|
|
58
|
+
irFamily="flow",
|
|
59
|
+
capabilities=[
|
|
60
|
+
"set_metadata",
|
|
61
|
+
"replace_named_value",
|
|
62
|
+
"flow.insert_paragraph",
|
|
63
|
+
"flow.insert_heading",
|
|
64
|
+
"flow.replace_table_rows",
|
|
65
|
+
"plain.append_text",
|
|
66
|
+
"finalize",
|
|
67
|
+
],
|
|
68
|
+
supportsPreview=True,
|
|
69
|
+
supportsBrowserExecution=True,
|
|
70
|
+
supportsDiff=True,
|
|
71
|
+
requiresBackend=False,
|
|
72
|
+
templateMediaTypes=["text/markdown"],
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
def __init__(self) -> None:
|
|
76
|
+
"""Initialize empty template state."""
|
|
77
|
+
self._template = ""
|
|
78
|
+
self._manifest: TemplateManifest | None = None
|
|
79
|
+
|
|
80
|
+
def load_template(self, template_ref, *, store, manifest) -> None:
|
|
81
|
+
"""Load Markdown template prefix."""
|
|
82
|
+
text = store.load_bytes(template_ref).decode("utf-8")
|
|
83
|
+
self._template = text.replace("\r\n", "\n")
|
|
84
|
+
self._manifest = manifest
|
|
85
|
+
|
|
86
|
+
def begin_session(
|
|
87
|
+
self, workspace, metadata: Mapping[str, Any]
|
|
88
|
+
) -> MarkdownSession:
|
|
89
|
+
"""Start a Markdown session."""
|
|
90
|
+
return MarkdownSession(
|
|
91
|
+
document_id=str(metadata.get("documentId", "")),
|
|
92
|
+
driver_id=self.driver_id,
|
|
93
|
+
template_prefix=self._template,
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
def apply_operation(
|
|
97
|
+
self, session: MarkdownSession, op: DocumentOperation
|
|
98
|
+
) -> None:
|
|
99
|
+
"""Apply one flow/plain operation."""
|
|
100
|
+
assert_op_allowed(self.manifest, op)
|
|
101
|
+
data = op.model_dump(mode="python", by_alias=True)
|
|
102
|
+
session.ops_applied.append(op.model_dump(mode="json", by_alias=True))
|
|
103
|
+
if op.op == "flow.insert_heading":
|
|
104
|
+
session.nodes.append(
|
|
105
|
+
{
|
|
106
|
+
"kind": "heading",
|
|
107
|
+
"level": data["level"],
|
|
108
|
+
"text": data["text"],
|
|
109
|
+
}
|
|
110
|
+
)
|
|
111
|
+
elif op.op == "flow.insert_paragraph":
|
|
112
|
+
text = "".join(run["text"] for run in data["runs"])
|
|
113
|
+
session.nodes.append({"kind": "paragraph", "text": text})
|
|
114
|
+
elif op.op == "flow.replace_table_rows":
|
|
115
|
+
session.nodes.append(
|
|
116
|
+
{"kind": "table", "table": data["table"], "rows": data["rows"]}
|
|
117
|
+
)
|
|
118
|
+
elif op.op == "plain.append_text":
|
|
119
|
+
session.nodes.append({"kind": "paragraph", "text": data["text"]})
|
|
120
|
+
elif op.op == "replace_named_value":
|
|
121
|
+
value = data["value"]
|
|
122
|
+
if isinstance(value, dict) and "value" in value:
|
|
123
|
+
rendered = str(value["value"])
|
|
124
|
+
else:
|
|
125
|
+
rendered = str(value)
|
|
126
|
+
session.template_prefix = session.template_prefix.replace(
|
|
127
|
+
"{{" + str(data["name"]) + "}}", rendered
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
def finalize(self, session: MarkdownSession) -> None:
|
|
131
|
+
"""No-op finalize."""
|
|
132
|
+
return
|
|
133
|
+
|
|
134
|
+
def serialize(self, session: MarkdownSession) -> bytes:
|
|
135
|
+
"""Return CommonMark UTF-8 bytes."""
|
|
136
|
+
body = render_commonmark(session.nodes, session.template_prefix)
|
|
137
|
+
return body.encode("utf-8")
|
|
138
|
+
|
|
139
|
+
def preview(self, session: MarkdownSession) -> DocumentPreview:
|
|
140
|
+
"""Identity preview of CommonMark text."""
|
|
141
|
+
return DocumentPreview(
|
|
142
|
+
documentId=session.document_id,
|
|
143
|
+
mode="text",
|
|
144
|
+
body=self.serialize(session).decode("utf-8"),
|
|
145
|
+
warnings=[],
|
|
146
|
+
generatedAt=datetime.now(UTC).isoformat(),
|
|
147
|
+
renderer={
|
|
148
|
+
"package": "roborean-documents-markdown",
|
|
149
|
+
"version": "0.3.0",
|
|
150
|
+
},
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def create_driver() -> MarkdownDocumentDriver:
|
|
155
|
+
"""Entry-point factory."""
|
|
156
|
+
return MarkdownDocumentDriver()
|