gelang 0.1.0

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 (96) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/LICENSE +21 -0
  3. package/README.md +535 -0
  4. package/bin/ge.js +112 -0
  5. package/package.json +62 -0
  6. package/python/pyeffic/__init__.py +9 -0
  7. package/python/pyeffic/__main__.py +6 -0
  8. package/python/pyeffic/analyzer.py +464 -0
  9. package/python/pyeffic/apisurface.py +238 -0
  10. package/python/pyeffic/autoselect.py +327 -0
  11. package/python/pyeffic/backends.py +87 -0
  12. package/python/pyeffic/bench.py +233 -0
  13. package/python/pyeffic/cli.py +184 -0
  14. package/python/pyeffic/compiler.py +421 -0
  15. package/python/pyeffic/config.py +383 -0
  16. package/python/pyeffic/dartgen.py +441 -0
  17. package/python/pyeffic/deploy.py +586 -0
  18. package/python/pyeffic/diagnostics.py +194 -0
  19. package/python/pyeffic/difftest.py +424 -0
  20. package/python/pyeffic/downloader.py +307 -0
  21. package/python/pyeffic/emitters/__init__.py +11 -0
  22. package/python/pyeffic/emitters/base.py +2359 -0
  23. package/python/pyeffic/emitters/cpp.py +266 -0
  24. package/python/pyeffic/emitters/csharp.py +342 -0
  25. package/python/pyeffic/emitters/dart.py +349 -0
  26. package/python/pyeffic/emitters/go.py +388 -0
  27. package/python/pyeffic/emitters/kotlin.py +314 -0
  28. package/python/pyeffic/emitters/rust.py +314 -0
  29. package/python/pyeffic/emitters/zig.py +411 -0
  30. package/python/pyeffic/ffi.py +49 -0
  31. package/python/pyeffic/frontends/__init__.py +94 -0
  32. package/python/pyeffic/frontends/hybrid.py +709 -0
  33. package/python/pyeffic/frontends/typescript.py +965 -0
  34. package/python/pyeffic/ge_cli.py +1148 -0
  35. package/python/pyeffic/golden.py +348 -0
  36. package/python/pyeffic/idents.py +206 -0
  37. package/python/pyeffic/modules.py +220 -0
  38. package/python/pyeffic/packer.py +222 -0
  39. package/python/pyeffic/pipeline.py +797 -0
  40. package/python/pyeffic/reactgen.py +966 -0
  41. package/python/pyeffic/researcher.py +177 -0
  42. package/python/pyeffic/scaffold.py +397 -0
  43. package/python/pyeffic/stdlib.py +246 -0
  44. package/python/pyeffic/styling.py +220 -0
  45. package/python/pyeffic/templates/desktop_gui/README.md +106 -0
  46. package/python/pyeffic/templates/desktop_gui/app/__init__.py +0 -0
  47. package/python/pyeffic/templates/desktop_gui/app/core/__init__.py +0 -0
  48. package/python/pyeffic/templates/desktop_gui/app/core/add.ge.py +13 -0
  49. package/python/pyeffic/templates/desktop_gui/app/core/factorial.ge.py +20 -0
  50. package/python/pyeffic/templates/desktop_gui/app/core/fibonacci.ge.py +25 -0
  51. package/python/pyeffic/templates/desktop_gui/app/core/gcd.ge.py +19 -0
  52. package/python/pyeffic/templates/desktop_gui/app/core/is_prime.ge.py +24 -0
  53. package/python/pyeffic/templates/desktop_gui/app/core/multiply.ge.py +13 -0
  54. package/python/pyeffic/templates/desktop_gui/app/core/power.ge.py +25 -0
  55. package/python/pyeffic/templates/desktop_gui/app/main.ge.py +49 -0
  56. package/python/pyeffic/templates/desktop_gui/app/memory/__init__.py +0 -0
  57. package/python/pyeffic/templates/desktop_gui/app/memory/buffer.ge.py +26 -0
  58. package/python/pyeffic/templates/desktop_gui/app/memory/limits.ge.py +47 -0
  59. package/python/pyeffic/templates/desktop_gui/app/memory/state.ge.py +44 -0
  60. package/python/pyeffic/templates/desktop_gui/app/ui/__init__.py +0 -0
  61. package/python/pyeffic/templates/desktop_gui/app/ui/layout.ge.py +64 -0
  62. package/python/pyeffic/templates/desktop_gui/app/ui/render.ge.py +87 -0
  63. package/python/pyeffic/templates/desktop_gui/app/ui/theme.ge.py +147 -0
  64. package/python/pyeffic/templates/desktop_gui/app/ui/widgets.ge.py +105 -0
  65. package/python/pyeffic/templates/desktop_gui/desktop/__init__.py +1 -0
  66. package/python/pyeffic/templates/desktop_gui/desktop/main.ge.py +258 -0
  67. package/python/pyeffic/templates/desktop_gui/ge.toml +16 -0
  68. package/python/pyeffic/templates/desktop_gui/tests/__init__.py +0 -0
  69. package/python/pyeffic/templates/desktop_gui/tests/ge_loader.py +76 -0
  70. package/python/pyeffic/templates/desktop_gui/tests/test_app.py +173 -0
  71. package/python/pyeffic/templates/web_react/README.md +115 -0
  72. package/python/pyeffic/templates/web_react/app/__init__.py +0 -0
  73. package/python/pyeffic/templates/web_react/app/core/__init__.py +0 -0
  74. package/python/pyeffic/templates/web_react/app/core/add.ge.py +9 -0
  75. package/python/pyeffic/templates/web_react/app/core/factorial.ge.py +16 -0
  76. package/python/pyeffic/templates/web_react/app/core/fibonacci.ge.py +21 -0
  77. package/python/pyeffic/templates/web_react/app/core/is_prime.ge.py +20 -0
  78. package/python/pyeffic/templates/web_react/app/core/multiply.ge.py +9 -0
  79. package/python/pyeffic/templates/web_react/app/main.ge.py +25 -0
  80. package/python/pyeffic/templates/web_react/app/memory/__init__.py +0 -0
  81. package/python/pyeffic/templates/web_react/app/memory/buffer.ge.py +25 -0
  82. package/python/pyeffic/templates/web_react/app/memory/limits.ge.py +51 -0
  83. package/python/pyeffic/templates/web_react/ge.toml +23 -0
  84. package/python/pyeffic/templates/web_react/tests/__init__.py +0 -0
  85. package/python/pyeffic/templates/web_react/tests/ge_loader.py +68 -0
  86. package/python/pyeffic/templates/web_react/tests/test_app.py +105 -0
  87. package/python/pyeffic/templates/web_react/ui/main.ge.ui +33 -0
  88. package/python/pyeffic/templates/web_react/web/__init__.py +0 -0
  89. package/python/pyeffic/templates/web_react/web/server.ge.py +78 -0
  90. package/python/pyeffic/ts2py.py +657 -0
  91. package/python/pyeffic/typecheck.py +232 -0
  92. package/python/pyeffic/ui.py +154 -0
  93. package/python/pyeffic/ui_dsl.py +618 -0
  94. package/python/pyeffic/widgets.py +87 -0
  95. package/scripts/README.md +42 -0
  96. package/scripts/check-toolchains.py +85 -0
@@ -0,0 +1,307 @@
1
+ """GE toolchain downloader — fetches missing compilers from official sources.
2
+
3
+ When a required toolchain (Rust, Go, Zig, etc.) is not found locally, GE can
4
+ download it from official sources and cache it in the GE tools directory.
5
+
6
+ Download URLs (official sources):
7
+ Rust: https://static.rust-lang.org/rustup/dist/{target}/rustup-init[.exe]
8
+ Go: https://go.dev/dl/go{version}.{platform}-{arch}.{zip|tar.gz}
9
+ Zig: https://ziglang.org/download/{version}/zig-{platform}-{arch}-{version}.zip
10
+ C++: Uses MSVC (Visual Studio) or MinGW — not downloadable by GE
11
+ C#: Uses .NET SDK from Microsoft — detected, not downloaded
12
+ Kotlin: Uses kotlinc — detected, not downloaded
13
+ Dart/Flutter: https://storage.googleapis.com/flutter_infra_release/releases/...
14
+
15
+ Cache location: ~/.ge/tools/{name}/
16
+ """
17
+ from __future__ import annotations
18
+
19
+ import os
20
+ import platform
21
+ import shutil
22
+ import stat
23
+ import sys
24
+ import tarfile
25
+ import urllib.request
26
+ import zipfile
27
+ from pathlib import Path
28
+
29
+ # Official download URLs
30
+ DOWNLOAD_URLS = {
31
+ "rust": {
32
+ "windows-x86_64": "https://static.rust-lang.org/rustup/dist/x86_64-pc-windows-msvc/rustup-init.exe",
33
+ "linux-x86_64": "https://sh.rustup.rs",
34
+ "macos-x86_64": "https://sh.rustup.rs",
35
+ },
36
+ "go": {
37
+ "windows-x86_64": "https://go.dev/dl/go1.23.4.windows-amd64.zip",
38
+ "linux-x86_64": "https://go.dev/dl/go1.23.4.linux-amd64.tar.gz",
39
+ "macos-x86_64": "https://go.dev/dl/go1.23.4.darwin-amd64.tar.gz",
40
+ "macos-aarch64": "https://go.dev/dl/go1.23.4.darwin-arm64.tar.gz",
41
+ },
42
+ "zig": {
43
+ "windows-x86_64": "https://ziglang.org/download/0.13.0/zig-x86_64-windows-0.13.0.zip",
44
+ "linux-x86_64": "https://ziglang.org/download/0.13.0/zig-x86_64-linux-0.13.0.tar.xz",
45
+ "macos-x86_64": "https://ziglang.org/download/0.13.0/zig-x86_64-macos-0.13.0.tar.xz",
46
+ "macos-aarch64": "https://ziglang.org/download/0.13.0/zig-aarch64-macos-0.13.0.tar.xz",
47
+ },
48
+ }
49
+
50
+ # Toolchain binary names after extraction
51
+ TOOLCHAIN_BINARIES = {
52
+ "rust": "rustc",
53
+ "go": "go",
54
+ "zig": "zig",
55
+ }
56
+
57
+ # Minimum required versions (kept loose for downloaded toolchains)
58
+ MIN_VERSIONS = {
59
+ "rust": "1.70",
60
+ "go": "1.21",
61
+ "zig": "0.13",
62
+ }
63
+
64
+
65
+ def _get_platform_key() -> str:
66
+ """Return platform key like 'windows-x86_64' or 'linux-x86_64'."""
67
+ os_name = platform.system().lower()
68
+ if os_name == "windows":
69
+ os_name = "windows"
70
+ elif os_name == "darwin":
71
+ os_name = "macos"
72
+ arch = platform.machine().lower()
73
+ if arch in ("x86_64", "amd64"):
74
+ arch = "x86_64"
75
+ elif arch in ("arm64", "aarch64"):
76
+ arch = "aarch64"
77
+ return f"{os_name}-{arch}"
78
+
79
+
80
+ def get_tools_dir() -> Path:
81
+ """Get the GE tools cache directory."""
82
+ home = Path.home()
83
+ tools_dir = home / ".ge" / "tools"
84
+ tools_dir.mkdir(parents=True, exist_ok=True)
85
+ return tools_dir
86
+
87
+
88
+ def get_toolchain_dir(name: str) -> Path:
89
+ """Get the directory for a specific toolchain."""
90
+ return get_tools_dir() / name
91
+
92
+
93
+ def is_toolchain_available(name: str) -> bool:
94
+ """Check if a toolchain is available (either on PATH or in GE tools cache)."""
95
+ binary = TOOLCHAIN_BINARIES.get(name, name)
96
+ if shutil.which(binary):
97
+ return True
98
+ # Check GE tools cache
99
+ tool_dir = get_toolchain_dir(name)
100
+ if tool_dir.exists():
101
+ # Look for binary in common subdirectory patterns
102
+ for pattern in ["bin", ".", "go/bin"]:
103
+ bin_dir = tool_dir / pattern
104
+ if bin_dir.exists():
105
+ exe_name = binary + (".exe" if platform.system() == "Windows" else "")
106
+ if (bin_dir / exe_name).exists():
107
+ return True
108
+ # Check if any file matching the binary name exists
109
+ for p in tool_dir.rglob(f"{binary}*"):
110
+ if p.is_file():
111
+ return True
112
+ return False
113
+
114
+
115
+ def get_toolchain_path(name: str) -> str | None:
116
+ """Get the executable path for a toolchain, or None."""
117
+ binary = TOOLCHAIN_BINARIES.get(name, name)
118
+ # Check PATH first
119
+ path = shutil.which(binary)
120
+ if path:
121
+ return path
122
+ # Check GE tools cache
123
+ tool_dir = get_toolchain_dir(name)
124
+ if tool_dir.exists():
125
+ exe_name = binary + (".exe" if platform.system() == "Windows" else "")
126
+ for pattern in ["bin", ".", "go/bin"]:
127
+ bin_dir = tool_dir / pattern
128
+ if bin_dir.exists():
129
+ exe_path = bin_dir / exe_name
130
+ if exe_path.exists():
131
+ return str(exe_path)
132
+ # Search recursively
133
+ for p in tool_dir.rglob(f"{binary}*"):
134
+ if p.is_file():
135
+ return str(p)
136
+ return None
137
+
138
+
139
+ def _download(url: str, dest: Path) -> bool:
140
+ """Download a file from URL to dest. Returns True on success."""
141
+ try:
142
+ print(f" Downloading from {url}...")
143
+ urllib.request.urlretrieve(url, dest)
144
+ return True
145
+ except Exception as e:
146
+ print(f" Download failed: {e}")
147
+ return False
148
+
149
+
150
+ def _extract_archive(archive_path: Path, dest_dir: Path) -> bool:
151
+ """Extract a zip or tar archive. Returns True on success."""
152
+ try:
153
+ if archive_path.suffix == ".zip":
154
+ with zipfile.ZipFile(archive_path, "r") as z:
155
+ z.extractall(dest_dir)
156
+ elif archive_path.suffix in (".gz", ".xz", ".tgz"):
157
+ with tarfile.open(archive_path, "r:*") as t:
158
+ t.extractall(dest_dir)
159
+ else:
160
+ print(f" Unknown archive format: {archive_path.suffix}")
161
+ return False
162
+ return True
163
+ except Exception as e:
164
+ print(f" Extract failed: {e}")
165
+ return False
166
+
167
+
168
+ def _find_extracted_root(dest_dir: Path) -> Path:
169
+ """Find the actual root directory after extraction (handles nested dirs)."""
170
+ # If there's a single subdirectory, return it
171
+ entries = [p for p in dest_dir.iterdir() if p.is_dir()]
172
+ if len(entries) == 1:
173
+ return entries[0]
174
+ return dest_dir
175
+
176
+
177
+ def download_toolchain(name: str, force: bool = False) -> bool:
178
+ """Download and install a toolchain from official sources.
179
+
180
+ Args:
181
+ name: toolchain name (rust, go, zig)
182
+ force: re-download even if already available
183
+
184
+ Returns:
185
+ True if toolchain is available after this call.
186
+ """
187
+ if name not in DOWNLOAD_URLS:
188
+ print(f" Toolchain '{name}' is not auto-downloadable (needs manual install).")
189
+ return is_toolchain_available(name)
190
+
191
+ if not force and is_toolchain_available(name):
192
+ return True
193
+
194
+ plat_key = _get_platform_key()
195
+ urls = DOWNLOAD_URLS[name]
196
+ url = urls.get(plat_key) or urls.get(plat_key.split("-")[0] + "-x86_64")
197
+ if not url:
198
+ print(f" No download URL for {name} on {plat_key}")
199
+ return False
200
+
201
+ tool_dir = get_toolchain_dir(name)
202
+ tool_dir.mkdir(parents=True, exist_ok=True)
203
+
204
+ # Download to temp file
205
+ archive_name = url.split("/")[-1]
206
+ archive_path = tool_dir / archive_name
207
+
208
+ if not _download(url, archive_path):
209
+ return False
210
+
211
+ # Handle rustup specially (it's an installer, not an archive)
212
+ if name == "rust":
213
+ if archive_path.suffix == ".exe":
214
+ # Run rustup-init.exe with default settings
215
+ import subprocess
216
+ print(" Running rustup-init...")
217
+ try:
218
+ subprocess.run([str(archive_path), "-y"], check=True, timeout=300)
219
+ archive_path.unlink()
220
+ return is_toolchain_available("rust")
221
+ except Exception as e:
222
+ print(f" rustup-init failed: {e}")
223
+ return False
224
+ else:
225
+ # Unix: run the shell script
226
+ import subprocess
227
+ os.chmod(str(archive_path), os.stat(str(archive_path)).st_mode | stat.S_IEXEC)
228
+ print(" Running rustup-init...")
229
+ try:
230
+ subprocess.run(["sh", str(archive_path), "-y"], check=True, timeout=300)
231
+ archive_path.unlink()
232
+ return is_toolchain_available("rust")
233
+ except Exception as e:
234
+ print(f" rustup-init failed: {e}")
235
+ return False
236
+
237
+ # Extract archive
238
+ print(f" Extracting {archive_name}...")
239
+ if not _extract_archive(archive_path, tool_dir):
240
+ return False
241
+
242
+ # Clean up archive
243
+ archive_path.unlink()
244
+
245
+ # For Go, the archive extracts to a "go" subdirectory
246
+ if name == "go":
247
+ extracted = _find_extracted_root(tool_dir)
248
+ # Go archives extract to "go/" — move contents to tool_dir if needed
249
+ if extracted.name == "go" and extracted != tool_dir:
250
+ # Already in the right place (tool_dir/go/)
251
+ pass
252
+
253
+ # For Zig, archives extract to "zig-..." directory
254
+ if name == "zig":
255
+ extracted = _find_extracted_root(tool_dir)
256
+ if extracted != tool_dir:
257
+ # Move contents from zig-x86_64-.../ to tool_dir/
258
+ for item in extracted.iterdir():
259
+ shutil.move(str(item), str(tool_dir / item.name))
260
+ extracted.rmdir()
261
+
262
+ available = is_toolchain_available(name)
263
+ if available:
264
+ print(f" {name} installed successfully.")
265
+ else:
266
+ print(f" {name} downloaded but binary not found. Check {tool_dir}")
267
+ return available
268
+
269
+
270
+ def ensure_toolchains(names: list[str], auto_download: bool = True) -> dict[str, bool]:
271
+ """Ensure all specified toolchains are available.
272
+
273
+ Args:
274
+ names: list of toolchain names
275
+ auto_download: download missing toolchains automatically
276
+
277
+ Returns:
278
+ Dict mapping toolchain name to availability status.
279
+ """
280
+ result = {}
281
+ for name in names:
282
+ if is_toolchain_available(name):
283
+ result[name] = True
284
+ elif auto_download and name in DOWNLOAD_URLS:
285
+ print(f"Toolchain '{name}' not found. Downloading...")
286
+ result[name] = download_toolchain(name)
287
+ else:
288
+ result[name] = False
289
+ return result
290
+
291
+
292
+ def get_tools_status() -> dict[str, dict]:
293
+ """Get status of all known toolchains."""
294
+ from .config import detect_compilers
295
+ detected = detect_compilers()
296
+ status = {}
297
+ for name in ["rust", "cpp", "csharp", "zig", "go", "kotlin", "dart"]:
298
+ available = is_toolchain_available(name) or getattr(detected, name, None) is not None
299
+ path = get_toolchain_path(name) or getattr(detected, name, None)
300
+ downloadable = name in DOWNLOAD_URLS
301
+ status[name] = {
302
+ "available": bool(available),
303
+ "path": str(path) if path else None,
304
+ "downloadable": downloadable,
305
+ "cache_dir": str(get_toolchain_dir(name)),
306
+ }
307
+ return status
@@ -0,0 +1,11 @@
1
+ """Code emitters for the supported Python subset."""
2
+ from .rust import emit_rust
3
+ from .cpp import emit_cpp
4
+ from .dart import emit_dart_functions
5
+ from .csharp import emit_csharp
6
+ from .zig import emit_zig
7
+ from .go import emit_go
8
+ from .kotlin import emit_kotlin
9
+
10
+ __all__ = ["emit_rust", "emit_cpp", "emit_dart_functions",
11
+ "emit_csharp", "emit_zig", "emit_go", "emit_kotlin"]