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.
- yuho/__init__.py +16 -0
- yuho/ast/__init__.py +196 -0
- yuho/ast/builder.py +926 -0
- yuho/ast/constant_folder.py +280 -0
- yuho/ast/dead_code.py +199 -0
- yuho/ast/exhaustiveness.py +503 -0
- yuho/ast/nodes.py +907 -0
- yuho/ast/overlap.py +291 -0
- yuho/ast/reachability.py +293 -0
- yuho/ast/scope_analysis.py +490 -0
- yuho/ast/transformer.py +490 -0
- yuho/ast/type_check.py +471 -0
- yuho/ast/type_inference.py +425 -0
- yuho/ast/visitor.py +239 -0
- yuho/cli/__init__.py +14 -0
- yuho/cli/commands/__init__.py +1 -0
- yuho/cli/commands/api.py +431 -0
- yuho/cli/commands/ast_viz.py +334 -0
- yuho/cli/commands/check.py +218 -0
- yuho/cli/commands/config.py +311 -0
- yuho/cli/commands/contribute.py +122 -0
- yuho/cli/commands/diff.py +487 -0
- yuho/cli/commands/explain.py +240 -0
- yuho/cli/commands/fmt.py +253 -0
- yuho/cli/commands/generate.py +316 -0
- yuho/cli/commands/graph.py +410 -0
- yuho/cli/commands/init.py +120 -0
- yuho/cli/commands/library.py +656 -0
- yuho/cli/commands/lint.py +503 -0
- yuho/cli/commands/lsp.py +36 -0
- yuho/cli/commands/preview.py +377 -0
- yuho/cli/commands/repl.py +444 -0
- yuho/cli/commands/serve.py +44 -0
- yuho/cli/commands/test.py +528 -0
- yuho/cli/commands/transpile.py +121 -0
- yuho/cli/commands/wizard.py +370 -0
- yuho/cli/completions.py +182 -0
- yuho/cli/error_formatter.py +193 -0
- yuho/cli/main.py +1064 -0
- yuho/config/__init__.py +46 -0
- yuho/config/loader.py +235 -0
- yuho/config/mask.py +194 -0
- yuho/config/schema.py +147 -0
- yuho/library/__init__.py +84 -0
- yuho/library/index.py +328 -0
- yuho/library/install.py +699 -0
- yuho/library/lockfile.py +330 -0
- yuho/library/package.py +421 -0
- yuho/library/resolver.py +791 -0
- yuho/library/signature.py +335 -0
- yuho/llm/__init__.py +45 -0
- yuho/llm/config.py +75 -0
- yuho/llm/factory.py +123 -0
- yuho/llm/prompts.py +146 -0
- yuho/llm/providers.py +383 -0
- yuho/llm/utils.py +470 -0
- yuho/lsp/__init__.py +14 -0
- yuho/lsp/code_action_handler.py +518 -0
- yuho/lsp/completion_handler.py +85 -0
- yuho/lsp/diagnostics.py +100 -0
- yuho/lsp/hover_handler.py +130 -0
- yuho/lsp/server.py +1425 -0
- yuho/mcp/__init__.py +10 -0
- yuho/mcp/server.py +1452 -0
- yuho/parser/__init__.py +8 -0
- yuho/parser/source_location.py +108 -0
- yuho/parser/wrapper.py +311 -0
- yuho/testing/__init__.py +48 -0
- yuho/testing/coverage.py +274 -0
- yuho/testing/fixtures.py +263 -0
- yuho/transpile/__init__.py +52 -0
- yuho/transpile/alloy_transpiler.py +546 -0
- yuho/transpile/base.py +100 -0
- yuho/transpile/blocks_transpiler.py +338 -0
- yuho/transpile/english_transpiler.py +470 -0
- yuho/transpile/graphql_transpiler.py +404 -0
- yuho/transpile/json_transpiler.py +217 -0
- yuho/transpile/jsonld_transpiler.py +250 -0
- yuho/transpile/latex_preamble.py +161 -0
- yuho/transpile/latex_transpiler.py +406 -0
- yuho/transpile/latex_utils.py +206 -0
- yuho/transpile/mermaid_transpiler.py +357 -0
- yuho/transpile/registry.py +275 -0
- yuho/verify/__init__.py +43 -0
- yuho/verify/alloy.py +352 -0
- yuho/verify/combined.py +218 -0
- yuho/verify/z3_solver.py +1155 -0
- yuho-5.0.0.dist-info/METADATA +186 -0
- yuho-5.0.0.dist-info/RECORD +91 -0
- yuho-5.0.0.dist-info/WHEEL +4 -0
- yuho-5.0.0.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Live preview mode with file watching for Yuho.
|
|
3
|
+
|
|
4
|
+
Watches a .yh file, auto-transpiles to mermaid/english on changes,
|
|
5
|
+
and serves the result in a browser with live reload.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
import sys
|
|
10
|
+
import time
|
|
11
|
+
import webbrowser
|
|
12
|
+
import hashlib
|
|
13
|
+
import threading
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from http.server import HTTPServer, SimpleHTTPRequestHandler
|
|
16
|
+
from typing import Optional, Dict, Any
|
|
17
|
+
import json
|
|
18
|
+
|
|
19
|
+
import click
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class PreviewState:
|
|
23
|
+
"""Shared state for preview server."""
|
|
24
|
+
|
|
25
|
+
def __init__(self):
|
|
26
|
+
self.content: str = ""
|
|
27
|
+
self.format: str = "english"
|
|
28
|
+
self.last_error: Optional[str] = None
|
|
29
|
+
self.file_path: str = ""
|
|
30
|
+
self.last_modified: float = 0
|
|
31
|
+
|
|
32
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
33
|
+
return {
|
|
34
|
+
"content": self.content,
|
|
35
|
+
"format": self.format,
|
|
36
|
+
"error": self.last_error,
|
|
37
|
+
"file": self.file_path,
|
|
38
|
+
"modified": self.last_modified,
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
# Global state
|
|
43
|
+
_preview_state = PreviewState()
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def transpile_file(path: Path, target: str) -> tuple[Optional[str], Optional[str]]:
|
|
47
|
+
"""
|
|
48
|
+
Transpile a file to the target format.
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
(content, error) - one will be None
|
|
52
|
+
"""
|
|
53
|
+
try:
|
|
54
|
+
from yuho.parser.scanner import Scanner
|
|
55
|
+
from yuho.parser.parser import Parser
|
|
56
|
+
from yuho.ast.builder import ASTBuilder
|
|
57
|
+
from yuho.transpile.registry import TranspilerRegistry
|
|
58
|
+
|
|
59
|
+
source = path.read_text()
|
|
60
|
+
|
|
61
|
+
scanner = Scanner(source)
|
|
62
|
+
tokens = scanner.scan_tokens()
|
|
63
|
+
|
|
64
|
+
parser = Parser(tokens)
|
|
65
|
+
tree = parser.parse()
|
|
66
|
+
|
|
67
|
+
builder = ASTBuilder()
|
|
68
|
+
ast = builder.build(tree)
|
|
69
|
+
|
|
70
|
+
registry = TranspilerRegistry()
|
|
71
|
+
transpiler = registry.get(target)
|
|
72
|
+
|
|
73
|
+
if transpiler is None:
|
|
74
|
+
return None, f"Unknown target format: {target}"
|
|
75
|
+
|
|
76
|
+
result = transpiler.transpile(ast)
|
|
77
|
+
return result, None
|
|
78
|
+
|
|
79
|
+
except Exception as e:
|
|
80
|
+
return None, str(e)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def get_html_template(format_type: str) -> str:
|
|
84
|
+
"""Get HTML template for preview."""
|
|
85
|
+
|
|
86
|
+
mermaid_script = ""
|
|
87
|
+
if format_type == "mermaid":
|
|
88
|
+
mermaid_script = """
|
|
89
|
+
<script src="https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js"></script>
|
|
90
|
+
<script>mermaid.initialize({startOnLoad: true, theme: 'dark'});</script>
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
return f"""<!DOCTYPE html>
|
|
94
|
+
<html lang="en">
|
|
95
|
+
<head>
|
|
96
|
+
<meta charset="UTF-8">
|
|
97
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
98
|
+
<title>Yuho Live Preview</title>
|
|
99
|
+
{mermaid_script}
|
|
100
|
+
<style>
|
|
101
|
+
:root {{
|
|
102
|
+
--bg-primary: #1a1a2e;
|
|
103
|
+
--bg-secondary: #16213e;
|
|
104
|
+
--text-primary: #eee;
|
|
105
|
+
--text-secondary: #aaa;
|
|
106
|
+
--accent: #4cc9f0;
|
|
107
|
+
--error: #ff6b6b;
|
|
108
|
+
--success: #4ecdc4;
|
|
109
|
+
}}
|
|
110
|
+
* {{ box-sizing: border-box; margin: 0; padding: 0; }}
|
|
111
|
+
body {{
|
|
112
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
|
|
113
|
+
background: var(--bg-primary);
|
|
114
|
+
color: var(--text-primary);
|
|
115
|
+
line-height: 1.6;
|
|
116
|
+
min-height: 100vh;
|
|
117
|
+
}}
|
|
118
|
+
.header {{
|
|
119
|
+
background: var(--bg-secondary);
|
|
120
|
+
padding: 1rem 2rem;
|
|
121
|
+
border-bottom: 1px solid rgba(255,255,255,0.1);
|
|
122
|
+
display: flex;
|
|
123
|
+
justify-content: space-between;
|
|
124
|
+
align-items: center;
|
|
125
|
+
}}
|
|
126
|
+
.header h1 {{
|
|
127
|
+
font-size: 1.25rem;
|
|
128
|
+
color: var(--accent);
|
|
129
|
+
}}
|
|
130
|
+
.status {{
|
|
131
|
+
display: flex;
|
|
132
|
+
align-items: center;
|
|
133
|
+
gap: 0.5rem;
|
|
134
|
+
font-size: 0.875rem;
|
|
135
|
+
color: var(--text-secondary);
|
|
136
|
+
}}
|
|
137
|
+
.status-dot {{
|
|
138
|
+
width: 8px;
|
|
139
|
+
height: 8px;
|
|
140
|
+
border-radius: 50%;
|
|
141
|
+
background: var(--success);
|
|
142
|
+
}}
|
|
143
|
+
.status-dot.error {{
|
|
144
|
+
background: var(--error);
|
|
145
|
+
}}
|
|
146
|
+
.container {{
|
|
147
|
+
max-width: 1200px;
|
|
148
|
+
margin: 0 auto;
|
|
149
|
+
padding: 2rem;
|
|
150
|
+
}}
|
|
151
|
+
.preview-content {{
|
|
152
|
+
background: var(--bg-secondary);
|
|
153
|
+
border-radius: 8px;
|
|
154
|
+
padding: 2rem;
|
|
155
|
+
white-space: pre-wrap;
|
|
156
|
+
font-family: 'JetBrains Mono', 'Fira Code', monospace;
|
|
157
|
+
font-size: 0.9rem;
|
|
158
|
+
overflow-x: auto;
|
|
159
|
+
}}
|
|
160
|
+
.preview-content.mermaid-container {{
|
|
161
|
+
background: #fff;
|
|
162
|
+
color: #333;
|
|
163
|
+
text-align: center;
|
|
164
|
+
}}
|
|
165
|
+
.error-message {{
|
|
166
|
+
background: rgba(255, 107, 107, 0.1);
|
|
167
|
+
border: 1px solid var(--error);
|
|
168
|
+
border-radius: 8px;
|
|
169
|
+
padding: 1rem;
|
|
170
|
+
color: var(--error);
|
|
171
|
+
margin-bottom: 1rem;
|
|
172
|
+
}}
|
|
173
|
+
.file-info {{
|
|
174
|
+
color: var(--text-secondary);
|
|
175
|
+
font-size: 0.875rem;
|
|
176
|
+
margin-bottom: 1rem;
|
|
177
|
+
}}
|
|
178
|
+
.mermaid {{
|
|
179
|
+
background: #fff;
|
|
180
|
+
}}
|
|
181
|
+
</style>
|
|
182
|
+
</head>
|
|
183
|
+
<body>
|
|
184
|
+
<div class="header">
|
|
185
|
+
<h1>📜 Yuho Live Preview</h1>
|
|
186
|
+
<div class="status">
|
|
187
|
+
<div class="status-dot" id="statusDot"></div>
|
|
188
|
+
<span id="statusText">Watching...</span>
|
|
189
|
+
</div>
|
|
190
|
+
</div>
|
|
191
|
+
<div class="container">
|
|
192
|
+
<div class="file-info" id="fileInfo"></div>
|
|
193
|
+
<div id="errorBox" class="error-message" style="display:none;"></div>
|
|
194
|
+
<div id="content" class="preview-content"></div>
|
|
195
|
+
</div>
|
|
196
|
+
<script>
|
|
197
|
+
let lastModified = 0;
|
|
198
|
+
|
|
199
|
+
async function fetchPreview() {{
|
|
200
|
+
try {{
|
|
201
|
+
const res = await fetch('/api/preview');
|
|
202
|
+
const data = await res.json();
|
|
203
|
+
|
|
204
|
+
const dot = document.getElementById('statusDot');
|
|
205
|
+
const status = document.getElementById('statusText');
|
|
206
|
+
const errorBox = document.getElementById('errorBox');
|
|
207
|
+
const content = document.getElementById('content');
|
|
208
|
+
const fileInfo = document.getElementById('fileInfo');
|
|
209
|
+
|
|
210
|
+
if (data.modified !== lastModified) {{
|
|
211
|
+
lastModified = data.modified;
|
|
212
|
+
|
|
213
|
+
fileInfo.textContent = 'File: ' + data.file + ' • Format: ' + data.format;
|
|
214
|
+
|
|
215
|
+
if (data.error) {{
|
|
216
|
+
dot.classList.add('error');
|
|
217
|
+
status.textContent = 'Error';
|
|
218
|
+
errorBox.textContent = data.error;
|
|
219
|
+
errorBox.style.display = 'block';
|
|
220
|
+
}} else {{
|
|
221
|
+
dot.classList.remove('error');
|
|
222
|
+
status.textContent = 'Updated ' + new Date().toLocaleTimeString();
|
|
223
|
+
errorBox.style.display = 'none';
|
|
224
|
+
|
|
225
|
+
if (data.format === 'mermaid') {{
|
|
226
|
+
content.className = 'preview-content mermaid-container';
|
|
227
|
+
content.innerHTML = '<pre class="mermaid">' + data.content + '</pre>';
|
|
228
|
+
if (window.mermaid) {{
|
|
229
|
+
mermaid.contentLoaded();
|
|
230
|
+
}}
|
|
231
|
+
}} else {{
|
|
232
|
+
content.className = 'preview-content';
|
|
233
|
+
content.textContent = data.content;
|
|
234
|
+
}}
|
|
235
|
+
}}
|
|
236
|
+
}}
|
|
237
|
+
}} catch (e) {{
|
|
238
|
+
console.error('Fetch error:', e);
|
|
239
|
+
}}
|
|
240
|
+
}}
|
|
241
|
+
|
|
242
|
+
// Poll for updates
|
|
243
|
+
setInterval(fetchPreview, 500);
|
|
244
|
+
fetchPreview();
|
|
245
|
+
</script>
|
|
246
|
+
</body>
|
|
247
|
+
</html>
|
|
248
|
+
"""
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
class PreviewHandler(SimpleHTTPRequestHandler):
|
|
252
|
+
"""HTTP handler for preview server."""
|
|
253
|
+
|
|
254
|
+
def log_message(self, format: str, *args) -> None:
|
|
255
|
+
"""Suppress default logging."""
|
|
256
|
+
pass
|
|
257
|
+
|
|
258
|
+
def do_GET(self) -> None:
|
|
259
|
+
if self.path == "/" or self.path == "/index.html":
|
|
260
|
+
self.send_response(200)
|
|
261
|
+
self.send_header("Content-type", "text/html")
|
|
262
|
+
self.end_headers()
|
|
263
|
+
html = get_html_template(_preview_state.format)
|
|
264
|
+
self.wfile.write(html.encode())
|
|
265
|
+
|
|
266
|
+
elif self.path == "/api/preview":
|
|
267
|
+
self.send_response(200)
|
|
268
|
+
self.send_header("Content-type", "application/json")
|
|
269
|
+
self.send_header("Access-Control-Allow-Origin", "*")
|
|
270
|
+
self.end_headers()
|
|
271
|
+
self.wfile.write(json.dumps(_preview_state.to_dict()).encode())
|
|
272
|
+
|
|
273
|
+
else:
|
|
274
|
+
self.send_error(404)
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def watch_file(path: Path, target: str, interval: float = 0.5) -> None:
|
|
278
|
+
"""Watch file for changes and update preview state."""
|
|
279
|
+
global _preview_state
|
|
280
|
+
|
|
281
|
+
last_hash = ""
|
|
282
|
+
|
|
283
|
+
while True:
|
|
284
|
+
try:
|
|
285
|
+
if path.exists():
|
|
286
|
+
content = path.read_bytes()
|
|
287
|
+
current_hash = hashlib.md5(content).hexdigest()
|
|
288
|
+
|
|
289
|
+
if current_hash != last_hash:
|
|
290
|
+
last_hash = current_hash
|
|
291
|
+
_preview_state.last_modified = time.time()
|
|
292
|
+
|
|
293
|
+
result, error = transpile_file(path, target)
|
|
294
|
+
|
|
295
|
+
if error:
|
|
296
|
+
_preview_state.last_error = error
|
|
297
|
+
else:
|
|
298
|
+
_preview_state.content = result or ""
|
|
299
|
+
_preview_state.last_error = None
|
|
300
|
+
|
|
301
|
+
except Exception as e:
|
|
302
|
+
_preview_state.last_error = str(e)
|
|
303
|
+
|
|
304
|
+
time.sleep(interval)
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
def run_preview(
|
|
308
|
+
file: str,
|
|
309
|
+
target: str = "english",
|
|
310
|
+
port: int = 8000,
|
|
311
|
+
no_browser: bool = False,
|
|
312
|
+
verbose: bool = False,
|
|
313
|
+
color: bool = True,
|
|
314
|
+
) -> None:
|
|
315
|
+
"""
|
|
316
|
+
Run live preview server.
|
|
317
|
+
|
|
318
|
+
Args:
|
|
319
|
+
file: Input .yh file to watch
|
|
320
|
+
target: Transpile target (english, mermaid)
|
|
321
|
+
port: Server port
|
|
322
|
+
no_browser: Don't auto-open browser
|
|
323
|
+
verbose: Verbose output
|
|
324
|
+
color: Use colors
|
|
325
|
+
"""
|
|
326
|
+
global _preview_state
|
|
327
|
+
|
|
328
|
+
path = Path(file)
|
|
329
|
+
|
|
330
|
+
if not path.exists():
|
|
331
|
+
click.echo(f"Error: File not found: {file}", err=True)
|
|
332
|
+
raise SystemExit(1)
|
|
333
|
+
|
|
334
|
+
# Validate target
|
|
335
|
+
valid_targets = ["english", "mermaid"]
|
|
336
|
+
if target not in valid_targets:
|
|
337
|
+
click.echo(f"Error: Preview supports: {', '.join(valid_targets)}", err=True)
|
|
338
|
+
raise SystemExit(1)
|
|
339
|
+
|
|
340
|
+
# Initialize state
|
|
341
|
+
_preview_state.file_path = str(path)
|
|
342
|
+
_preview_state.format = target
|
|
343
|
+
|
|
344
|
+
# Initial transpile
|
|
345
|
+
result, error = transpile_file(path, target)
|
|
346
|
+
if error:
|
|
347
|
+
_preview_state.last_error = error
|
|
348
|
+
else:
|
|
349
|
+
_preview_state.content = result or ""
|
|
350
|
+
_preview_state.last_modified = time.time()
|
|
351
|
+
|
|
352
|
+
# Start file watcher thread
|
|
353
|
+
watcher = threading.Thread(target=watch_file, args=(path, target), daemon=True)
|
|
354
|
+
watcher.start()
|
|
355
|
+
|
|
356
|
+
# Start server
|
|
357
|
+
server = HTTPServer(("127.0.0.1", port), PreviewHandler)
|
|
358
|
+
|
|
359
|
+
url = f"http://127.0.0.1:{port}"
|
|
360
|
+
|
|
361
|
+
click.echo(click.style("🔴 Yuho Live Preview", fg="cyan", bold=True))
|
|
362
|
+
click.echo(f" Watching: {click.style(str(path), fg='yellow')}")
|
|
363
|
+
click.echo(f" Format: {click.style(target, fg='green')}")
|
|
364
|
+
click.echo(f" Server: {click.style(url, fg='blue')}")
|
|
365
|
+
click.echo("")
|
|
366
|
+
click.echo(" Press Ctrl+C to stop")
|
|
367
|
+
click.echo("")
|
|
368
|
+
|
|
369
|
+
# Open browser
|
|
370
|
+
if not no_browser:
|
|
371
|
+
webbrowser.open(url)
|
|
372
|
+
|
|
373
|
+
try:
|
|
374
|
+
server.serve_forever()
|
|
375
|
+
except KeyboardInterrupt:
|
|
376
|
+
click.echo("\n\nStopping preview server...")
|
|
377
|
+
server.shutdown()
|