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
yuho/library/lockfile.py
ADDED
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Version locking for reproducible Yuho package installs.
|
|
3
|
+
|
|
4
|
+
Manages yuho.lock file that records exact resolved versions
|
|
5
|
+
for reproducible installations across environments.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Dict, List, Optional, Any
|
|
9
|
+
from dataclasses import dataclass, field
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from datetime import datetime
|
|
12
|
+
import json
|
|
13
|
+
import hashlib
|
|
14
|
+
import logging
|
|
15
|
+
|
|
16
|
+
from yuho.library.resolver import Version, Resolution
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# Default lock file name
|
|
22
|
+
LOCK_FILE_NAME = "yuho.lock"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass
|
|
26
|
+
class LockedPackage:
|
|
27
|
+
"""A locked package version with integrity hash."""
|
|
28
|
+
section_number: str
|
|
29
|
+
version: str
|
|
30
|
+
content_hash: str
|
|
31
|
+
source: str # "registry" or local path
|
|
32
|
+
dependencies: List[str] = field(default_factory=list)
|
|
33
|
+
|
|
34
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
35
|
+
"""Convert to dictionary."""
|
|
36
|
+
return {
|
|
37
|
+
"version": self.version,
|
|
38
|
+
"content_hash": self.content_hash,
|
|
39
|
+
"source": self.source,
|
|
40
|
+
"dependencies": self.dependencies,
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
@classmethod
|
|
44
|
+
def from_dict(cls, section: str, data: Dict[str, Any]) -> "LockedPackage":
|
|
45
|
+
"""Create from dictionary."""
|
|
46
|
+
return cls(
|
|
47
|
+
section_number=section,
|
|
48
|
+
version=data["version"],
|
|
49
|
+
content_hash=data.get("content_hash", ""),
|
|
50
|
+
source=data.get("source", "registry"),
|
|
51
|
+
dependencies=data.get("dependencies", []),
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@dataclass
|
|
56
|
+
class LockFile:
|
|
57
|
+
"""
|
|
58
|
+
Lock file for reproducible installs.
|
|
59
|
+
|
|
60
|
+
Format (JSON):
|
|
61
|
+
{
|
|
62
|
+
"lock_version": "1",
|
|
63
|
+
"generated_at": "2024-01-15T10:30:00Z",
|
|
64
|
+
"packages": {
|
|
65
|
+
"S403": {
|
|
66
|
+
"version": "1.2.3",
|
|
67
|
+
"content_hash": "sha256:abc123...",
|
|
68
|
+
"source": "registry",
|
|
69
|
+
"dependencies": ["S400@^1.0.0"]
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
"""
|
|
74
|
+
lock_version: str = "1"
|
|
75
|
+
generated_at: str = ""
|
|
76
|
+
packages: Dict[str, LockedPackage] = field(default_factory=dict)
|
|
77
|
+
|
|
78
|
+
@classmethod
|
|
79
|
+
def from_file(cls, path: Path) -> "LockFile":
|
|
80
|
+
"""Load lock file from path."""
|
|
81
|
+
if not path.exists():
|
|
82
|
+
return cls()
|
|
83
|
+
|
|
84
|
+
with open(path) as f:
|
|
85
|
+
data = json.load(f)
|
|
86
|
+
|
|
87
|
+
packages = {}
|
|
88
|
+
for section, pkg_data in data.get("packages", {}).items():
|
|
89
|
+
packages[section] = LockedPackage.from_dict(section, pkg_data)
|
|
90
|
+
|
|
91
|
+
return cls(
|
|
92
|
+
lock_version=data.get("lock_version", "1"),
|
|
93
|
+
generated_at=data.get("generated_at", ""),
|
|
94
|
+
packages=packages,
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
def to_file(self, path: Path) -> None:
|
|
98
|
+
"""Save lock file to path."""
|
|
99
|
+
data = {
|
|
100
|
+
"lock_version": self.lock_version,
|
|
101
|
+
"generated_at": self.generated_at or datetime.utcnow().isoformat() + "Z",
|
|
102
|
+
"packages": {
|
|
103
|
+
section: pkg.to_dict()
|
|
104
|
+
for section, pkg in sorted(self.packages.items())
|
|
105
|
+
},
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
with open(path, "w") as f:
|
|
109
|
+
json.dump(data, f, indent=2)
|
|
110
|
+
f.write("\n")
|
|
111
|
+
|
|
112
|
+
def add_package(
|
|
113
|
+
self,
|
|
114
|
+
section: str,
|
|
115
|
+
version: str,
|
|
116
|
+
content_hash: str,
|
|
117
|
+
source: str = "registry",
|
|
118
|
+
dependencies: Optional[List[str]] = None,
|
|
119
|
+
) -> None:
|
|
120
|
+
"""Add or update a locked package."""
|
|
121
|
+
self.packages[section] = LockedPackage(
|
|
122
|
+
section_number=section,
|
|
123
|
+
version=version,
|
|
124
|
+
content_hash=content_hash,
|
|
125
|
+
source=source,
|
|
126
|
+
dependencies=dependencies or [],
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
def remove_package(self, section: str) -> bool:
|
|
130
|
+
"""Remove a package from lock file."""
|
|
131
|
+
if section in self.packages:
|
|
132
|
+
del self.packages[section]
|
|
133
|
+
return True
|
|
134
|
+
return False
|
|
135
|
+
|
|
136
|
+
def get_version(self, section: str) -> Optional[str]:
|
|
137
|
+
"""Get locked version for a package."""
|
|
138
|
+
if section in self.packages:
|
|
139
|
+
return self.packages[section].version
|
|
140
|
+
return None
|
|
141
|
+
|
|
142
|
+
def get_hash(self, section: str) -> Optional[str]:
|
|
143
|
+
"""Get content hash for a package."""
|
|
144
|
+
if section in self.packages:
|
|
145
|
+
return self.packages[section].content_hash
|
|
146
|
+
return None
|
|
147
|
+
|
|
148
|
+
def verify_integrity(self, section: str, content_hash: str) -> bool:
|
|
149
|
+
"""Verify package integrity against lock file."""
|
|
150
|
+
locked_hash = self.get_hash(section)
|
|
151
|
+
if not locked_hash:
|
|
152
|
+
return True # No hash recorded
|
|
153
|
+
return locked_hash == content_hash
|
|
154
|
+
|
|
155
|
+
def get_locked_versions(self) -> Dict[str, str]:
|
|
156
|
+
"""Get all locked versions as dict."""
|
|
157
|
+
return {
|
|
158
|
+
section: pkg.version
|
|
159
|
+
for section, pkg in self.packages.items()
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
class LockFileManager:
|
|
164
|
+
"""
|
|
165
|
+
Manages lock file operations for a project.
|
|
166
|
+
|
|
167
|
+
Handles creating, updating, and using lock files for
|
|
168
|
+
reproducible package installations.
|
|
169
|
+
"""
|
|
170
|
+
|
|
171
|
+
def __init__(self, project_dir: Path):
|
|
172
|
+
"""
|
|
173
|
+
Initialize manager for a project directory.
|
|
174
|
+
|
|
175
|
+
Args:
|
|
176
|
+
project_dir: Project root directory
|
|
177
|
+
"""
|
|
178
|
+
self.project_dir = Path(project_dir)
|
|
179
|
+
self.lock_path = self.project_dir / LOCK_FILE_NAME
|
|
180
|
+
self._lock_file: Optional[LockFile] = None
|
|
181
|
+
|
|
182
|
+
@property
|
|
183
|
+
def lock_file(self) -> LockFile:
|
|
184
|
+
"""Get or load the lock file."""
|
|
185
|
+
if self._lock_file is None:
|
|
186
|
+
self._lock_file = LockFile.from_file(self.lock_path)
|
|
187
|
+
return self._lock_file
|
|
188
|
+
|
|
189
|
+
def exists(self) -> bool:
|
|
190
|
+
"""Check if lock file exists."""
|
|
191
|
+
return self.lock_path.exists()
|
|
192
|
+
|
|
193
|
+
def create_from_resolution(self, resolution: Resolution) -> LockFile:
|
|
194
|
+
"""
|
|
195
|
+
Create lock file from dependency resolution.
|
|
196
|
+
|
|
197
|
+
Args:
|
|
198
|
+
resolution: Resolved dependencies
|
|
199
|
+
|
|
200
|
+
Returns:
|
|
201
|
+
Created lock file
|
|
202
|
+
"""
|
|
203
|
+
lock = LockFile(
|
|
204
|
+
generated_at=datetime.utcnow().isoformat() + "Z",
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
for section, version in resolution.packages.items():
|
|
208
|
+
lock.add_package(
|
|
209
|
+
section=section,
|
|
210
|
+
version=str(version),
|
|
211
|
+
content_hash="", # Will be filled on install
|
|
212
|
+
source="registry",
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
lock.to_file(self.lock_path)
|
|
216
|
+
self._lock_file = lock
|
|
217
|
+
|
|
218
|
+
return lock
|
|
219
|
+
|
|
220
|
+
def update_hash(self, section: str, content_hash: str) -> None:
|
|
221
|
+
"""Update content hash after installation."""
|
|
222
|
+
if section in self.lock_file.packages:
|
|
223
|
+
self.lock_file.packages[section].content_hash = content_hash
|
|
224
|
+
self.lock_file.to_file(self.lock_path)
|
|
225
|
+
|
|
226
|
+
def get_locked_for_install(self) -> Dict[str, str]:
|
|
227
|
+
"""
|
|
228
|
+
Get locked versions for installation.
|
|
229
|
+
|
|
230
|
+
Returns:
|
|
231
|
+
Dict of section -> version
|
|
232
|
+
"""
|
|
233
|
+
return self.lock_file.get_locked_versions()
|
|
234
|
+
|
|
235
|
+
def refresh(self, new_resolution: Resolution) -> LockFile:
|
|
236
|
+
"""
|
|
237
|
+
Refresh lock file with new resolution.
|
|
238
|
+
|
|
239
|
+
Preserves content hashes for unchanged versions.
|
|
240
|
+
|
|
241
|
+
Args:
|
|
242
|
+
new_resolution: New resolution to lock
|
|
243
|
+
|
|
244
|
+
Returns:
|
|
245
|
+
Updated lock file
|
|
246
|
+
"""
|
|
247
|
+
old_lock = self.lock_file
|
|
248
|
+
new_lock = LockFile(
|
|
249
|
+
generated_at=datetime.utcnow().isoformat() + "Z",
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
for section, version in new_resolution.packages.items():
|
|
253
|
+
# Preserve hash if version unchanged
|
|
254
|
+
old_hash = ""
|
|
255
|
+
if section in old_lock.packages:
|
|
256
|
+
old_pkg = old_lock.packages[section]
|
|
257
|
+
if old_pkg.version == str(version):
|
|
258
|
+
old_hash = old_pkg.content_hash
|
|
259
|
+
|
|
260
|
+
new_lock.add_package(
|
|
261
|
+
section=section,
|
|
262
|
+
version=str(version),
|
|
263
|
+
content_hash=old_hash,
|
|
264
|
+
source="registry",
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
new_lock.to_file(self.lock_path)
|
|
268
|
+
self._lock_file = new_lock
|
|
269
|
+
|
|
270
|
+
return new_lock
|
|
271
|
+
|
|
272
|
+
def check_outdated(self) -> List[str]:
|
|
273
|
+
"""
|
|
274
|
+
Check for packages that may be outdated.
|
|
275
|
+
|
|
276
|
+
Returns:
|
|
277
|
+
List of section numbers with potential updates
|
|
278
|
+
"""
|
|
279
|
+
from yuho.library.install import check_updates
|
|
280
|
+
|
|
281
|
+
updates = check_updates()
|
|
282
|
+
outdated = []
|
|
283
|
+
|
|
284
|
+
for update in updates:
|
|
285
|
+
section = update["section_number"]
|
|
286
|
+
if section in self.lock_file.packages:
|
|
287
|
+
outdated.append(section)
|
|
288
|
+
|
|
289
|
+
return outdated
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def load_lock_file(project_dir: Optional[Path] = None) -> LockFile:
|
|
293
|
+
"""
|
|
294
|
+
Load lock file from project directory.
|
|
295
|
+
|
|
296
|
+
Args:
|
|
297
|
+
project_dir: Project directory (default: current)
|
|
298
|
+
|
|
299
|
+
Returns:
|
|
300
|
+
Lock file instance
|
|
301
|
+
"""
|
|
302
|
+
project_dir = project_dir or Path.cwd()
|
|
303
|
+
return LockFile.from_file(project_dir / LOCK_FILE_NAME)
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def create_lock_file(
|
|
307
|
+
dependencies: List[str],
|
|
308
|
+
project_dir: Optional[Path] = None,
|
|
309
|
+
) -> LockFile:
|
|
310
|
+
"""
|
|
311
|
+
Create lock file from dependencies.
|
|
312
|
+
|
|
313
|
+
Args:
|
|
314
|
+
dependencies: List of dependency strings
|
|
315
|
+
project_dir: Project directory
|
|
316
|
+
|
|
317
|
+
Returns:
|
|
318
|
+
Created lock file
|
|
319
|
+
"""
|
|
320
|
+
from yuho.library.resolver import resolve_dependencies
|
|
321
|
+
|
|
322
|
+
project_dir = project_dir or Path.cwd()
|
|
323
|
+
|
|
324
|
+
resolution = resolve_dependencies(dependencies)
|
|
325
|
+
|
|
326
|
+
if not resolution.success:
|
|
327
|
+
raise ValueError(f"Resolution failed: {'; '.join(resolution.errors)}")
|
|
328
|
+
|
|
329
|
+
manager = LockFileManager(project_dir)
|
|
330
|
+
return manager.create_from_resolution(resolution)
|