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()