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