yuho 5.0.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.
Files changed (91) hide show
  1. yuho/__init__.py +16 -0
  2. yuho/ast/__init__.py +196 -0
  3. yuho/ast/builder.py +926 -0
  4. yuho/ast/constant_folder.py +280 -0
  5. yuho/ast/dead_code.py +199 -0
  6. yuho/ast/exhaustiveness.py +503 -0
  7. yuho/ast/nodes.py +907 -0
  8. yuho/ast/overlap.py +291 -0
  9. yuho/ast/reachability.py +293 -0
  10. yuho/ast/scope_analysis.py +490 -0
  11. yuho/ast/transformer.py +490 -0
  12. yuho/ast/type_check.py +471 -0
  13. yuho/ast/type_inference.py +425 -0
  14. yuho/ast/visitor.py +239 -0
  15. yuho/cli/__init__.py +14 -0
  16. yuho/cli/commands/__init__.py +1 -0
  17. yuho/cli/commands/api.py +431 -0
  18. yuho/cli/commands/ast_viz.py +334 -0
  19. yuho/cli/commands/check.py +218 -0
  20. yuho/cli/commands/config.py +311 -0
  21. yuho/cli/commands/contribute.py +122 -0
  22. yuho/cli/commands/diff.py +487 -0
  23. yuho/cli/commands/explain.py +240 -0
  24. yuho/cli/commands/fmt.py +253 -0
  25. yuho/cli/commands/generate.py +316 -0
  26. yuho/cli/commands/graph.py +410 -0
  27. yuho/cli/commands/init.py +120 -0
  28. yuho/cli/commands/library.py +656 -0
  29. yuho/cli/commands/lint.py +503 -0
  30. yuho/cli/commands/lsp.py +36 -0
  31. yuho/cli/commands/preview.py +377 -0
  32. yuho/cli/commands/repl.py +444 -0
  33. yuho/cli/commands/serve.py +44 -0
  34. yuho/cli/commands/test.py +528 -0
  35. yuho/cli/commands/transpile.py +121 -0
  36. yuho/cli/commands/wizard.py +370 -0
  37. yuho/cli/completions.py +182 -0
  38. yuho/cli/error_formatter.py +193 -0
  39. yuho/cli/main.py +1064 -0
  40. yuho/config/__init__.py +46 -0
  41. yuho/config/loader.py +235 -0
  42. yuho/config/mask.py +194 -0
  43. yuho/config/schema.py +147 -0
  44. yuho/library/__init__.py +84 -0
  45. yuho/library/index.py +328 -0
  46. yuho/library/install.py +699 -0
  47. yuho/library/lockfile.py +330 -0
  48. yuho/library/package.py +421 -0
  49. yuho/library/resolver.py +791 -0
  50. yuho/library/signature.py +335 -0
  51. yuho/llm/__init__.py +45 -0
  52. yuho/llm/config.py +75 -0
  53. yuho/llm/factory.py +123 -0
  54. yuho/llm/prompts.py +146 -0
  55. yuho/llm/providers.py +383 -0
  56. yuho/llm/utils.py +470 -0
  57. yuho/lsp/__init__.py +14 -0
  58. yuho/lsp/code_action_handler.py +518 -0
  59. yuho/lsp/completion_handler.py +85 -0
  60. yuho/lsp/diagnostics.py +100 -0
  61. yuho/lsp/hover_handler.py +130 -0
  62. yuho/lsp/server.py +1425 -0
  63. yuho/mcp/__init__.py +10 -0
  64. yuho/mcp/server.py +1452 -0
  65. yuho/parser/__init__.py +8 -0
  66. yuho/parser/source_location.py +108 -0
  67. yuho/parser/wrapper.py +311 -0
  68. yuho/testing/__init__.py +48 -0
  69. yuho/testing/coverage.py +274 -0
  70. yuho/testing/fixtures.py +263 -0
  71. yuho/transpile/__init__.py +52 -0
  72. yuho/transpile/alloy_transpiler.py +546 -0
  73. yuho/transpile/base.py +100 -0
  74. yuho/transpile/blocks_transpiler.py +338 -0
  75. yuho/transpile/english_transpiler.py +470 -0
  76. yuho/transpile/graphql_transpiler.py +404 -0
  77. yuho/transpile/json_transpiler.py +217 -0
  78. yuho/transpile/jsonld_transpiler.py +250 -0
  79. yuho/transpile/latex_preamble.py +161 -0
  80. yuho/transpile/latex_transpiler.py +406 -0
  81. yuho/transpile/latex_utils.py +206 -0
  82. yuho/transpile/mermaid_transpiler.py +357 -0
  83. yuho/transpile/registry.py +275 -0
  84. yuho/verify/__init__.py +43 -0
  85. yuho/verify/alloy.py +352 -0
  86. yuho/verify/combined.py +218 -0
  87. yuho/verify/z3_solver.py +1155 -0
  88. yuho-5.0.0.dist-info/METADATA +186 -0
  89. yuho-5.0.0.dist-info/RECORD +91 -0
  90. yuho-5.0.0.dist-info/WHEEL +4 -0
  91. yuho-5.0.0.dist-info/entry_points.txt +2 -0
@@ -0,0 +1,274 @@
1
+ """
2
+ Coverage tracking for Yuho statute test implementations.
3
+
4
+ Tracks which elements, conditions, and branches are exercised by tests.
5
+ """
6
+
7
+ from typing import Dict, List, Set, Optional, Any
8
+ from dataclasses import dataclass, field
9
+ from pathlib import Path
10
+ import json
11
+
12
+
13
+ @dataclass
14
+ class ElementCoverage:
15
+ """Coverage tracking for a single statute element."""
16
+ element_type: str # actus_reus, mens_rea, circumstance
17
+ name: str
18
+ covered: bool = False
19
+ test_count: int = 0
20
+ test_files: List[str] = field(default_factory=list)
21
+
22
+
23
+ @dataclass
24
+ class StatuteCoverage:
25
+ """Coverage tracking for a single statute."""
26
+ section_number: str
27
+ title: str = ""
28
+ elements: Dict[str, ElementCoverage] = field(default_factory=dict)
29
+ penalty_covered: bool = False
30
+ illustrations_covered: Set[str] = field(default_factory=set)
31
+ total_illustrations: int = 0
32
+
33
+ @property
34
+ def element_coverage_percent(self) -> float:
35
+ """Calculate percentage of elements covered."""
36
+ if not self.elements:
37
+ return 0.0
38
+ covered = sum(1 for e in self.elements.values() if e.covered)
39
+ return (covered / len(self.elements)) * 100
40
+
41
+ @property
42
+ def overall_coverage_percent(self) -> float:
43
+ """Calculate overall coverage percentage."""
44
+ total_items = len(self.elements) + 1 # +1 for penalty
45
+ if self.total_illustrations > 0:
46
+ total_items += self.total_illustrations
47
+
48
+ covered_items = sum(1 for e in self.elements.values() if e.covered)
49
+ if self.penalty_covered:
50
+ covered_items += 1
51
+ covered_items += len(self.illustrations_covered)
52
+
53
+ return (covered_items / total_items) * 100 if total_items > 0 else 0.0
54
+
55
+
56
+ @dataclass
57
+ class CoverageReport:
58
+ """Complete coverage report for a test run."""
59
+ statutes: Dict[str, StatuteCoverage] = field(default_factory=dict)
60
+ total_tests: int = 0
61
+ passed_tests: int = 0
62
+ failed_tests: int = 0
63
+
64
+ @property
65
+ def overall_coverage_percent(self) -> float:
66
+ """Calculate overall coverage across all statutes."""
67
+ if not self.statutes:
68
+ return 0.0
69
+ total = sum(s.overall_coverage_percent for s in self.statutes.values())
70
+ return total / len(self.statutes)
71
+
72
+ def to_dict(self) -> Dict[str, Any]:
73
+ """Convert to dictionary for JSON serialization."""
74
+ return {
75
+ "summary": {
76
+ "total_statutes": len(self.statutes),
77
+ "overall_coverage": f"{self.overall_coverage_percent:.1f}%",
78
+ "total_tests": self.total_tests,
79
+ "passed_tests": self.passed_tests,
80
+ "failed_tests": self.failed_tests,
81
+ },
82
+ "statutes": {
83
+ section: {
84
+ "title": cov.title,
85
+ "element_coverage": f"{cov.element_coverage_percent:.1f}%",
86
+ "overall_coverage": f"{cov.overall_coverage_percent:.1f}%",
87
+ "elements": {
88
+ name: {
89
+ "type": elem.element_type,
90
+ "covered": elem.covered,
91
+ "test_count": elem.test_count,
92
+ }
93
+ for name, elem in cov.elements.items()
94
+ },
95
+ "penalty_covered": cov.penalty_covered,
96
+ "illustrations_covered": len(cov.illustrations_covered),
97
+ "total_illustrations": cov.total_illustrations,
98
+ }
99
+ for section, cov in self.statutes.items()
100
+ },
101
+ }
102
+
103
+ def to_json(self, indent: int = 2) -> str:
104
+ """Convert to JSON string."""
105
+ return json.dumps(self.to_dict(), indent=indent)
106
+
107
+
108
+ class CoverageTracker:
109
+ """
110
+ Tracks test coverage for Yuho statute implementations.
111
+
112
+ Usage:
113
+ tracker = CoverageTracker()
114
+ tracker.load_statutes_from_ast(ast)
115
+ tracker.mark_element_covered("378", "actus_reus", "taking", "test_theft.yh")
116
+ report = tracker.generate_report()
117
+ """
118
+
119
+ def __init__(self):
120
+ self.report = CoverageReport()
121
+
122
+ def load_statutes_from_ast(self, ast) -> None:
123
+ """Load statute structure from AST for coverage tracking."""
124
+ for statute in ast.statutes:
125
+ section = statute.section_number
126
+ title = statute.title.value if statute.title else ""
127
+
128
+ cov = StatuteCoverage(section_number=section, title=title)
129
+
130
+ # Track elements
131
+ for element in (statute.elements or []):
132
+ elem_key = f"{element.element_type}:{element.name}"
133
+ cov.elements[elem_key] = ElementCoverage(
134
+ element_type=element.element_type,
135
+ name=element.name,
136
+ )
137
+
138
+ # Track illustrations
139
+ if hasattr(statute, 'illustrations') and statute.illustrations:
140
+ cov.total_illustrations = len(statute.illustrations)
141
+
142
+ self.report.statutes[section] = cov
143
+
144
+ def mark_element_covered(
145
+ self,
146
+ section: str,
147
+ element_type: str,
148
+ element_name: str,
149
+ test_file: str,
150
+ ) -> None:
151
+ """Mark an element as covered by a test."""
152
+ if section not in self.report.statutes:
153
+ return
154
+
155
+ elem_key = f"{element_type}:{element_name}"
156
+ cov = self.report.statutes[section]
157
+
158
+ if elem_key in cov.elements:
159
+ cov.elements[elem_key].covered = True
160
+ cov.elements[elem_key].test_count += 1
161
+ if test_file not in cov.elements[elem_key].test_files:
162
+ cov.elements[elem_key].test_files.append(test_file)
163
+
164
+ def mark_penalty_covered(self, section: str) -> None:
165
+ """Mark penalty section as covered."""
166
+ if section in self.report.statutes:
167
+ self.report.statutes[section].penalty_covered = True
168
+
169
+ def mark_illustration_covered(self, section: str, illustration_label: str) -> None:
170
+ """Mark an illustration as covered."""
171
+ if section in self.report.statutes:
172
+ self.report.statutes[section].illustrations_covered.add(illustration_label)
173
+
174
+ def add_test_result(self, passed: bool) -> None:
175
+ """Record a test result."""
176
+ self.report.total_tests += 1
177
+ if passed:
178
+ self.report.passed_tests += 1
179
+ else:
180
+ self.report.failed_tests += 1
181
+
182
+ def generate_report(self) -> CoverageReport:
183
+ """Generate the coverage report."""
184
+ return self.report
185
+
186
+ def print_summary(self) -> None:
187
+ """Print a human-readable coverage summary."""
188
+ print("\n" + "=" * 60)
189
+ print("STATUTE COVERAGE REPORT")
190
+ print("=" * 60)
191
+
192
+ for section, cov in self.report.statutes.items():
193
+ print(f"\n{section}: {cov.title}")
194
+ print(f" Element coverage: {cov.element_coverage_percent:.1f}%")
195
+
196
+ for elem_key, elem in cov.elements.items():
197
+ status = "COVERED" if elem.covered else "NOT COVERED"
198
+ print(f" [{status}] {elem.element_type}: {elem.name}")
199
+
200
+ penalty_status = "COVERED" if cov.penalty_covered else "NOT COVERED"
201
+ print(f" [{penalty_status}] penalty")
202
+
203
+ if cov.total_illustrations > 0:
204
+ ill_cov = len(cov.illustrations_covered)
205
+ print(f" Illustrations: {ill_cov}/{cov.total_illustrations}")
206
+
207
+ print("\n" + "-" * 60)
208
+ print(f"Overall coverage: {self.report.overall_coverage_percent:.1f}%")
209
+ print(f"Tests: {self.report.passed_tests}/{self.report.total_tests} passed")
210
+ print("=" * 60)
211
+
212
+
213
+ def analyze_test_coverage(
214
+ statute_files: List[Path],
215
+ test_files: List[Path],
216
+ ) -> CoverageReport:
217
+ """
218
+ Analyze test coverage for a set of statute and test files.
219
+
220
+ Args:
221
+ statute_files: List of .yh statute files
222
+ test_files: List of test files
223
+
224
+ Returns:
225
+ CoverageReport with coverage analysis
226
+ """
227
+ from yuho.parser import Parser
228
+ from yuho.ast import ASTBuilder
229
+
230
+ tracker = CoverageTracker()
231
+ parser = Parser()
232
+
233
+ # Load statute structure
234
+ for statute_file in statute_files:
235
+ try:
236
+ result = parser.parse_file(statute_file)
237
+ if result.is_valid:
238
+ builder = ASTBuilder(result.source, str(statute_file))
239
+ ast = builder.build(result.root_node)
240
+ tracker.load_statutes_from_ast(ast)
241
+ except Exception:
242
+ continue
243
+
244
+ # Analyze test files for coverage
245
+ for test_file in test_files:
246
+ try:
247
+ result = parser.parse_file(test_file)
248
+ if result.is_valid:
249
+ builder = ASTBuilder(result.source, str(test_file))
250
+ ast = builder.build(result.root_node)
251
+
252
+ # Check what elements are referenced in tests
253
+ for statute in ast.statutes:
254
+ section = statute.section_number
255
+
256
+ # Mark elements as covered if they appear in test assertions
257
+ for elem in (statute.elements or []):
258
+ tracker.mark_element_covered(
259
+ section,
260
+ elem.element_type,
261
+ elem.name,
262
+ str(test_file),
263
+ )
264
+
265
+ if statute.penalty:
266
+ tracker.mark_penalty_covered(section)
267
+
268
+ tracker.add_test_result(passed=True)
269
+ else:
270
+ tracker.add_test_result(passed=False)
271
+ except Exception:
272
+ tracker.add_test_result(passed=False)
273
+
274
+ return tracker.generate_report()
@@ -0,0 +1,263 @@
1
+ """
2
+ Pytest fixtures for testing Yuho statute implementations.
3
+
4
+ Provides ready-to-use fixtures for parsing, AST building, and validating
5
+ Yuho code in user tests.
6
+
7
+ Usage:
8
+ # In conftest.py
9
+ pytest_plugins = ["yuho.testing.fixtures"]
10
+
11
+ # In test files
12
+ def test_my_statute(yuho_parser, yuho_ast, parse_statute):
13
+ ast = parse_statute("statute 299 {...}")
14
+ assert len(ast.statutes) == 1
15
+ """
16
+
17
+ from typing import Optional, List, Dict, Any, Callable
18
+ from pathlib import Path
19
+ from dataclasses import dataclass, field
20
+ import pytest
21
+
22
+
23
+ @pytest.fixture
24
+ def yuho_parser():
25
+ """
26
+ Fixture providing a Yuho parser instance.
27
+
28
+ Usage:
29
+ def test_parse(yuho_parser):
30
+ result = yuho_parser.parse("statute 299 {...}")
31
+ assert result.is_valid
32
+ """
33
+ from yuho.parser import Parser
34
+ return Parser()
35
+
36
+
37
+ @pytest.fixture
38
+ def yuho_ast(yuho_parser):
39
+ """
40
+ Fixture providing an AST builder function.
41
+
42
+ Usage:
43
+ def test_ast(yuho_ast):
44
+ ast = yuho_ast("statute 299 {...}")
45
+ assert len(ast.statutes) == 1
46
+ """
47
+ from yuho.ast import ASTBuilder
48
+
49
+ def build_ast(source: str):
50
+ result = yuho_parser.parse(source)
51
+ if not result.is_valid:
52
+ raise ValueError(f"Parse error: {result.errors[0].message}")
53
+ builder = ASTBuilder(source)
54
+ return builder.build(result.root_node)
55
+
56
+ return build_ast
57
+
58
+
59
+ @pytest.fixture
60
+ def parse_statute(yuho_ast):
61
+ """
62
+ Fixture providing a function to parse statute code and return the first statute.
63
+
64
+ Usage:
65
+ def test_theft(parse_statute):
66
+ statute = parse_statute('''
67
+ statute 378 "Theft" {
68
+ elements {
69
+ actus_reus taking: "takes property"
70
+ }
71
+ }
72
+ ''')
73
+ assert statute.section_number == "378"
74
+ """
75
+ def _parse(source: str):
76
+ ast = yuho_ast(source)
77
+ if not ast.statutes:
78
+ raise ValueError("No statute found in source")
79
+ return ast.statutes[0]
80
+
81
+ return _parse
82
+
83
+
84
+ @pytest.fixture
85
+ def parse_file(yuho_ast):
86
+ """
87
+ Fixture providing a function to parse a .yh file.
88
+
89
+ Usage:
90
+ def test_file(parse_file):
91
+ ast = parse_file("path/to/statute.yh")
92
+ assert len(ast.statutes) >= 1
93
+ """
94
+ def _parse_file(path: str):
95
+ file_path = Path(path)
96
+ if not file_path.exists():
97
+ raise FileNotFoundError(f"File not found: {path}")
98
+ source = file_path.read_text()
99
+ return yuho_ast(source)
100
+
101
+ return _parse_file
102
+
103
+
104
+ @pytest.fixture
105
+ def statute_validator():
106
+ """
107
+ Fixture providing a statute validator for checking statute completeness.
108
+
109
+ Usage:
110
+ def test_valid(statute_validator, parse_statute):
111
+ statute = parse_statute("...")
112
+ result = statute_validator(statute)
113
+ assert result.valid
114
+ assert not result.errors
115
+ """
116
+ @dataclass
117
+ class ValidationResult:
118
+ valid: bool
119
+ errors: List[str] = field(default_factory=list)
120
+ warnings: List[str] = field(default_factory=list)
121
+
122
+ def _validate(statute) -> ValidationResult:
123
+ errors = []
124
+ warnings = []
125
+
126
+ # Check required fields
127
+ if not statute.section_number:
128
+ errors.append("Missing section number")
129
+
130
+ if not statute.title:
131
+ warnings.append("Missing title")
132
+
133
+ if not statute.elements:
134
+ errors.append("No elements defined")
135
+ else:
136
+ # Check for at least one actus_reus
137
+ has_actus = any(
138
+ e.element_type == "actus_reus"
139
+ for e in statute.elements
140
+ )
141
+ if not has_actus:
142
+ warnings.append("No actus_reus element defined")
143
+
144
+ # Check for mens_rea
145
+ has_mens = any(
146
+ e.element_type == "mens_rea"
147
+ for e in statute.elements
148
+ )
149
+ if not has_mens:
150
+ warnings.append("No mens_rea element defined")
151
+
152
+ if not statute.penalty:
153
+ warnings.append("No penalty section defined")
154
+
155
+ return ValidationResult(
156
+ valid=len(errors) == 0,
157
+ errors=errors,
158
+ warnings=warnings,
159
+ )
160
+
161
+ return _validate
162
+
163
+
164
+ @dataclass
165
+ class StatuteTestCase:
166
+ """
167
+ Helper class for defining statute test cases.
168
+
169
+ Usage:
170
+ cases = [
171
+ StatuteTestCase(
172
+ name="theft_basic",
173
+ source='''statute 378 "Theft" {...}''',
174
+ expected_elements=["actus_reus:taking"],
175
+ expected_penalty_max=7, # years
176
+ ),
177
+ ]
178
+
179
+ @pytest.mark.parametrize("case", cases, ids=lambda c: c.name)
180
+ def test_statute(case, parse_statute):
181
+ statute = parse_statute(case.source)
182
+ case.verify(statute)
183
+ """
184
+ name: str
185
+ source: str
186
+ expected_section: Optional[str] = None
187
+ expected_title: Optional[str] = None
188
+ expected_elements: List[str] = field(default_factory=list)
189
+ expected_penalty_max: Optional[int] = None # years
190
+ expected_fine_max: Optional[float] = None
191
+
192
+ def verify(self, statute) -> None:
193
+ """Verify statute against expected values. Raises AssertionError on mismatch."""
194
+ if self.expected_section:
195
+ assert statute.section_number == self.expected_section, \
196
+ f"Section mismatch: expected {self.expected_section}, got {statute.section_number}"
197
+
198
+ if self.expected_title:
199
+ title = statute.title.value if statute.title else None
200
+ assert title == self.expected_title, \
201
+ f"Title mismatch: expected {self.expected_title}, got {title}"
202
+
203
+ if self.expected_elements:
204
+ actual_elements = [
205
+ f"{e.element_type}:{e.name}"
206
+ for e in (statute.elements or [])
207
+ ]
208
+ for expected in self.expected_elements:
209
+ assert expected in actual_elements, \
210
+ f"Missing element: {expected}. Found: {actual_elements}"
211
+
212
+
213
+ def element_check(statute, element_type: str, name: str) -> bool:
214
+ """
215
+ Check if a statute has a specific element.
216
+
217
+ Usage:
218
+ def test_has_actus_reus(parse_statute):
219
+ statute = parse_statute("...")
220
+ assert element_check(statute, "actus_reus", "taking")
221
+ """
222
+ if not statute.elements:
223
+ return False
224
+ return any(
225
+ e.element_type == element_type and e.name == name
226
+ for e in statute.elements
227
+ )
228
+
229
+
230
+ def penalty_check(statute, penalty_type: str, max_value: Any = None) -> bool:
231
+ """
232
+ Check if a statute has a specific penalty type.
233
+
234
+ Usage:
235
+ def test_has_imprisonment(parse_statute):
236
+ statute = parse_statute("...")
237
+ assert penalty_check(statute, "imprisonment")
238
+ """
239
+ if not statute.penalty:
240
+ return False
241
+
242
+ # Check penalty structure
243
+ penalty = statute.penalty
244
+ if hasattr(penalty, penalty_type):
245
+ penalty_val = getattr(penalty, penalty_type)
246
+ if penalty_val is None:
247
+ return False
248
+ if max_value is not None and hasattr(penalty_val, 'max'):
249
+ return penalty_val.max is not None
250
+ return True
251
+
252
+ return False
253
+
254
+
255
+ # Register as pytest plugin
256
+ def pytest_configure(config):
257
+ """Register Yuho markers."""
258
+ config.addinivalue_line(
259
+ "markers", "statute(section): mark test as testing a specific statute section"
260
+ )
261
+ config.addinivalue_line(
262
+ "markers", "slow: mark test as slow running"
263
+ )
@@ -0,0 +1,52 @@
1
+ """
2
+ Yuho transpilation module - multi-target code generation.
3
+
4
+ Supports transpilation to:
5
+ - JSON: Structured AST representation
6
+ - JSON-LD: Linked data with legal ontology
7
+ - English: Controlled natural language
8
+ - LaTeX: Legal document formatting
9
+ - Mermaid: Decision tree flowcharts
10
+ - Alloy: Formal verification models
11
+ """
12
+
13
+ from yuho.transpile.base import TranspileTarget, TranspilerBase
14
+ from yuho.transpile.json_transpiler import JSONTranspiler
15
+ from yuho.transpile.jsonld_transpiler import JSONLDTranspiler
16
+ from yuho.transpile.english_transpiler import EnglishTranspiler
17
+ from yuho.transpile.mermaid_transpiler import MermaidTranspiler
18
+ from yuho.transpile.alloy_transpiler import AlloyTranspiler
19
+ from yuho.transpile.latex_transpiler import LaTeXTranspiler, compile_to_pdf
20
+ from yuho.transpile.registry import TranspilerRegistry
21
+
22
+ __all__ = [
23
+ "TranspileTarget",
24
+ "TranspilerBase",
25
+ "TranspilerRegistry",
26
+ "JSONTranspiler",
27
+ "JSONLDTranspiler",
28
+ "EnglishTranspiler",
29
+ "LaTeXTranspiler",
30
+ "compile_to_pdf",
31
+ "MermaidTranspiler",
32
+ "AlloyTranspiler",
33
+ "get_transpiler",
34
+ ]
35
+
36
+
37
+ def get_transpiler(target: TranspileTarget) -> TranspilerBase:
38
+ """
39
+ Get a transpiler instance for the given target.
40
+
41
+ This is a convenience function that uses the TranspilerRegistry singleton.
42
+
43
+ Args:
44
+ target: The transpilation target
45
+
46
+ Returns:
47
+ A transpiler instance for the target
48
+
49
+ Raises:
50
+ KeyError: If no transpiler is registered for the target
51
+ """
52
+ return TranspilerRegistry.instance().get(target)