inscript-lang 2.8.0__tar.gz → 2.11.0__tar.gz
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.
- {inscript_lang-2.8.0 → inscript_lang-2.11.0}/PKG-INFO +1 -1
- inscript_lang-2.11.0/export_pipeline.py +647 -0
- {inscript_lang-2.8.0 → inscript_lang-2.11.0}/inscript.py +26 -1
- {inscript_lang-2.8.0 → inscript_lang-2.11.0}/inscript_lang.egg-info/PKG-INFO +1 -1
- {inscript_lang-2.8.0 → inscript_lang-2.11.0}/inscript_lang.egg-info/SOURCES.txt +1 -0
- {inscript_lang-2.8.0 → inscript_lang-2.11.0}/inscript_lang.egg-info/top_level.txt +1 -0
- {inscript_lang-2.8.0 → inscript_lang-2.11.0}/pyproject.toml +1 -1
- {inscript_lang-2.8.0 → inscript_lang-2.11.0}/setup.py +1 -1
- {inscript_lang-2.8.0 → inscript_lang-2.11.0}/README.md +0 -0
- {inscript_lang-2.8.0 → inscript_lang-2.11.0}/analyzer.py +0 -0
- {inscript_lang-2.8.0 → inscript_lang-2.11.0}/ast_nodes.py +0 -0
- {inscript_lang-2.8.0 → inscript_lang-2.11.0}/compiler.py +0 -0
- {inscript_lang-2.8.0 → inscript_lang-2.11.0}/environment.py +0 -0
- {inscript_lang-2.8.0 → inscript_lang-2.11.0}/errors.py +0 -0
- {inscript_lang-2.8.0 → inscript_lang-2.11.0}/hot_reload.py +0 -0
- {inscript_lang-2.8.0 → inscript_lang-2.11.0}/inscript_dap.py +0 -0
- {inscript_lang-2.8.0 → inscript_lang-2.11.0}/inscript_fmt.py +0 -0
- {inscript_lang-2.8.0 → inscript_lang-2.11.0}/inscript_lang.egg-info/dependency_links.txt +0 -0
- {inscript_lang-2.8.0 → inscript_lang-2.11.0}/inscript_lang.egg-info/entry_points.txt +0 -0
- {inscript_lang-2.8.0 → inscript_lang-2.11.0}/inscript_lang.egg-info/requires.txt +0 -0
- {inscript_lang-2.8.0 → inscript_lang-2.11.0}/inscript_test.py +0 -0
- {inscript_lang-2.8.0 → inscript_lang-2.11.0}/interpreter.py +0 -0
- {inscript_lang-2.8.0 → inscript_lang-2.11.0}/lexer.py +0 -0
- {inscript_lang-2.8.0 → inscript_lang-2.11.0}/parser.py +0 -0
- {inscript_lang-2.8.0 → inscript_lang-2.11.0}/pygame_backend.py +0 -0
- {inscript_lang-2.8.0 → inscript_lang-2.11.0}/repl.py +0 -0
- {inscript_lang-2.8.0 → inscript_lang-2.11.0}/scene_tree.py +0 -0
- {inscript_lang-2.8.0 → inscript_lang-2.11.0}/setup.cfg +0 -0
- {inscript_lang-2.8.0 → inscript_lang-2.11.0}/stdlib.py +0 -0
- {inscript_lang-2.8.0 → inscript_lang-2.11.0}/stdlib_assets.py +0 -0
- {inscript_lang-2.8.0 → inscript_lang-2.11.0}/stdlib_extended.py +0 -0
- {inscript_lang-2.8.0 → inscript_lang-2.11.0}/stdlib_extended_2.py +0 -0
- {inscript_lang-2.8.0 → inscript_lang-2.11.0}/stdlib_game.py +0 -0
- {inscript_lang-2.8.0 → inscript_lang-2.11.0}/stdlib_values.py +0 -0
- {inscript_lang-2.8.0 → inscript_lang-2.11.0}/vm.py +0 -0
|
@@ -0,0 +1,647 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
export_pipeline.py — InScript v2.11.0 Export Pipeline
|
|
4
|
+
================================================================
|
|
5
|
+
`inscript build --target <target> [project_dir]`
|
|
6
|
+
|
|
7
|
+
Supported targets
|
|
8
|
+
─────────────────
|
|
9
|
+
desktop → self-contained directory + launch script
|
|
10
|
+
(embeds Python + inscript runtime + all assets)
|
|
11
|
+
web → index.html + Pyodide WASM bundle + assets
|
|
12
|
+
android → APK via BeeWare Briefcase (requires: pip install briefcase)
|
|
13
|
+
ibc → ahead-of-time bytecode compilation (pre-existing, improved)
|
|
14
|
+
|
|
15
|
+
`inscript new <name>` → scaffold a standard project layout:
|
|
16
|
+
<name>/
|
|
17
|
+
inscript.toml project manifest
|
|
18
|
+
src/
|
|
19
|
+
main.ins entry-point scene
|
|
20
|
+
assets/
|
|
21
|
+
sprites/ sfx/ fonts/ maps/
|
|
22
|
+
scenes/
|
|
23
|
+
main.inscene root scene file
|
|
24
|
+
build/ (gitignored output dir)
|
|
25
|
+
|
|
26
|
+
Build manifest
|
|
27
|
+
──────────────
|
|
28
|
+
`build/build.toml` is written after every successful build, recording:
|
|
29
|
+
target, version, entry_point, asset_hashes, timestamp.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
from __future__ import annotations
|
|
33
|
+
import os, sys, shutil, json, hashlib, datetime, textwrap, zipfile
|
|
34
|
+
from typing import Any, Dict, List, Optional
|
|
35
|
+
|
|
36
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
37
|
+
# Constants
|
|
38
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
39
|
+
|
|
40
|
+
BUILD_DIR = "build"
|
|
41
|
+
MANIFEST_FILE = "inscript.toml"
|
|
42
|
+
ENTRY_DEFAULT = os.path.join("src", "main.ins")
|
|
43
|
+
BUILD_MANIFEST = "build.toml"
|
|
44
|
+
|
|
45
|
+
VALID_TARGETS = ("desktop", "web", "android", "ibc")
|
|
46
|
+
|
|
47
|
+
# Pyodide CDN base used in web builds
|
|
48
|
+
PYODIDE_CDN = "https://cdn.jsdelivr.net/pyodide/v0.27.0/full/"
|
|
49
|
+
|
|
50
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
51
|
+
# BuildResult
|
|
52
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
53
|
+
|
|
54
|
+
class BuildResult:
|
|
55
|
+
def __init__(self, target: str, success: bool, output_dir: str,
|
|
56
|
+
artifacts: List[str], errors: List[str], elapsed_ms: float):
|
|
57
|
+
self.target = target
|
|
58
|
+
self.success = success
|
|
59
|
+
self.output_dir = output_dir
|
|
60
|
+
self.artifacts = artifacts # paths of produced files
|
|
61
|
+
self.errors = errors
|
|
62
|
+
self.elapsed_ms = elapsed_ms
|
|
63
|
+
|
|
64
|
+
def __repr__(self):
|
|
65
|
+
status = "OK" if self.success else "ERR"
|
|
66
|
+
return (f"<BuildResult {self.target} {status} "
|
|
67
|
+
f"artifacts={len(self.artifacts)} {self.elapsed_ms:.0f}ms>")
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
71
|
+
# Project layout helpers
|
|
72
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
73
|
+
|
|
74
|
+
def _read_manifest(project_dir: str) -> dict:
|
|
75
|
+
path = os.path.join(project_dir, MANIFEST_FILE)
|
|
76
|
+
if not os.path.isfile(path):
|
|
77
|
+
return {}
|
|
78
|
+
try:
|
|
79
|
+
from inscript import _parse_toml_simple
|
|
80
|
+
with open(path, encoding="utf-8") as f:
|
|
81
|
+
return _parse_toml_simple(f.read())
|
|
82
|
+
except Exception:
|
|
83
|
+
return {}
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _entry_point(project_dir: str, manifest: dict) -> str:
|
|
87
|
+
"""Resolve the .ins entry-point from manifest or default."""
|
|
88
|
+
ep = manifest.get("package", {}).get("entry", ENTRY_DEFAULT)
|
|
89
|
+
return os.path.join(project_dir, ep)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _collect_assets(project_dir: str) -> List[str]:
|
|
93
|
+
"""Walk assets/ and scenes/ and return all file paths (relative to project_dir)."""
|
|
94
|
+
assets = []
|
|
95
|
+
for sub in ("assets", "scenes"):
|
|
96
|
+
base = os.path.join(project_dir, sub)
|
|
97
|
+
if not os.path.isdir(base):
|
|
98
|
+
continue
|
|
99
|
+
for root, _, files in os.walk(base):
|
|
100
|
+
for fn in files:
|
|
101
|
+
full = os.path.join(root, fn)
|
|
102
|
+
assets.append(os.path.relpath(full, project_dir))
|
|
103
|
+
return sorted(assets)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _collect_sources(project_dir: str) -> List[str]:
|
|
107
|
+
"""Return all .ins files (relative to project_dir)."""
|
|
108
|
+
sources = []
|
|
109
|
+
for root, _, files in os.walk(project_dir):
|
|
110
|
+
for fn in files:
|
|
111
|
+
if fn.endswith(".ins"):
|
|
112
|
+
full = os.path.join(root, fn)
|
|
113
|
+
rel = os.path.relpath(full, project_dir)
|
|
114
|
+
if not rel.startswith("build" + os.sep):
|
|
115
|
+
sources.append(rel)
|
|
116
|
+
return sorted(sources)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _sha256_file(path: str) -> str:
|
|
120
|
+
if not os.path.isfile(path):
|
|
121
|
+
return ""
|
|
122
|
+
h = hashlib.sha256()
|
|
123
|
+
with open(path, "rb") as f:
|
|
124
|
+
for chunk in iter(lambda: f.read(65536), b""):
|
|
125
|
+
h.update(chunk)
|
|
126
|
+
return h.hexdigest()
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _write_build_manifest(output_dir: str, meta: dict) -> str:
|
|
130
|
+
"""Write build/build.toml and return its path."""
|
|
131
|
+
os.makedirs(output_dir, exist_ok=True)
|
|
132
|
+
path = os.path.join(output_dir, BUILD_MANIFEST)
|
|
133
|
+
lines = [
|
|
134
|
+
"# InScript Build Manifest — auto-generated\n",
|
|
135
|
+
f'generated = "{datetime.datetime.utcnow().isoformat()}Z"\n',
|
|
136
|
+
f'target = "{meta.get("target", "unknown")}"\n',
|
|
137
|
+
f'version = "{meta.get("version", "0.0.0")}"\n',
|
|
138
|
+
f'entry = "{meta.get("entry", "")}"\n',
|
|
139
|
+
f'inscript = "{meta.get("inscript_version", "")}"\n\n',
|
|
140
|
+
"[artifacts]\n",
|
|
141
|
+
]
|
|
142
|
+
for k, v in meta.get("artifacts", {}).items():
|
|
143
|
+
lines.append(f'{k} = "{v}"\n')
|
|
144
|
+
lines.append("\n[asset_hashes]\n")
|
|
145
|
+
for k, v in meta.get("asset_hashes", {}).items():
|
|
146
|
+
safe_k = k.replace("\\", "/").replace('"', "")
|
|
147
|
+
lines.append(f'"{safe_k}" = "{v}"\n')
|
|
148
|
+
with open(path, "w", encoding="utf-8") as f:
|
|
149
|
+
f.writelines(lines)
|
|
150
|
+
return path
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
154
|
+
# Target: desktop
|
|
155
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
156
|
+
|
|
157
|
+
def build_desktop(project_dir: str = ".") -> BuildResult:
|
|
158
|
+
"""
|
|
159
|
+
Build a self-contained desktop distribution.
|
|
160
|
+
|
|
161
|
+
Output layout:
|
|
162
|
+
build/desktop/
|
|
163
|
+
run.py — launcher that invokes inscript on main.ins
|
|
164
|
+
run.sh — Unix launcher
|
|
165
|
+
run.bat — Windows launcher
|
|
166
|
+
inscript_runtime/ — copy of the InScript runtime (.py files)
|
|
167
|
+
assets/ — copy of project assets/
|
|
168
|
+
scenes/ — copy of project scenes/
|
|
169
|
+
src/ — copy of .ins source files
|
|
170
|
+
build.toml — build manifest
|
|
171
|
+
"""
|
|
172
|
+
import time
|
|
173
|
+
t0 = time.perf_counter()
|
|
174
|
+
errors: List[str] = []
|
|
175
|
+
artifacts: List[str] = []
|
|
176
|
+
|
|
177
|
+
manifest = _read_manifest(project_dir)
|
|
178
|
+
pkg = manifest.get("package", {})
|
|
179
|
+
version = pkg.get("version", "0.0.0")
|
|
180
|
+
name = pkg.get("name", os.path.basename(os.path.abspath(project_dir)))
|
|
181
|
+
entry_rel = pkg.get("entry", ENTRY_DEFAULT)
|
|
182
|
+
entry_abs = os.path.join(project_dir, entry_rel)
|
|
183
|
+
|
|
184
|
+
out_dir = os.path.join(project_dir, BUILD_DIR, "desktop")
|
|
185
|
+
os.makedirs(out_dir, exist_ok=True)
|
|
186
|
+
|
|
187
|
+
# ── Copy runtime .py files ───────────────────────────────────────────────
|
|
188
|
+
runtime_src = os.path.dirname(os.path.abspath(__file__))
|
|
189
|
+
runtime_dst = os.path.join(out_dir, "inscript_runtime")
|
|
190
|
+
os.makedirs(runtime_dst, exist_ok=True)
|
|
191
|
+
runtime_modules = [
|
|
192
|
+
"inscript.py", "lexer.py", "parser.py", "interpreter.py",
|
|
193
|
+
"compiler.py", "vm.py", "analyzer.py", "errors.py",
|
|
194
|
+
"environment.py", "ast_nodes.py",
|
|
195
|
+
"stdlib.py", "stdlib_extended.py", "stdlib_extended_2.py",
|
|
196
|
+
"stdlib_game.py", "stdlib_values.py", "stdlib_assets.py",
|
|
197
|
+
"scene_tree.py", "hot_reload.py", "pygame_backend.py",
|
|
198
|
+
]
|
|
199
|
+
for mod in runtime_modules:
|
|
200
|
+
src = os.path.join(runtime_src, mod)
|
|
201
|
+
if os.path.isfile(src):
|
|
202
|
+
shutil.copy2(src, os.path.join(runtime_dst, mod))
|
|
203
|
+
|
|
204
|
+
# ── Copy source + assets ─────────────────────────────────────────────────
|
|
205
|
+
for sub in ("src", "assets", "scenes"):
|
|
206
|
+
src = os.path.join(project_dir, sub)
|
|
207
|
+
dst = os.path.join(out_dir, sub)
|
|
208
|
+
if os.path.isdir(src):
|
|
209
|
+
if os.path.exists(dst):
|
|
210
|
+
shutil.rmtree(dst)
|
|
211
|
+
shutil.copytree(src, dst)
|
|
212
|
+
|
|
213
|
+
# ── Write launchers ──────────────────────────────────────────────────────
|
|
214
|
+
run_py = os.path.join(out_dir, "run.py")
|
|
215
|
+
with open(run_py, "w") as f:
|
|
216
|
+
f.write(textwrap.dedent(f"""\
|
|
217
|
+
#!/usr/bin/env python3
|
|
218
|
+
# InScript Desktop Launcher — {name} v{version}
|
|
219
|
+
import sys, os
|
|
220
|
+
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "inscript_runtime"))
|
|
221
|
+
import inscript
|
|
222
|
+
sys.exit(inscript.main(["{entry_rel}"]))
|
|
223
|
+
"""))
|
|
224
|
+
artifacts.append(run_py)
|
|
225
|
+
|
|
226
|
+
run_sh = os.path.join(out_dir, "run.sh")
|
|
227
|
+
with open(run_sh, "w") as f:
|
|
228
|
+
f.write(f"#!/bin/sh\ncd \"$(dirname \"$0\")\"\npython3 run.py \"$@\"\n")
|
|
229
|
+
try:
|
|
230
|
+
os.chmod(run_sh, 0o755)
|
|
231
|
+
except Exception:
|
|
232
|
+
pass
|
|
233
|
+
artifacts.append(run_sh)
|
|
234
|
+
|
|
235
|
+
run_bat = os.path.join(out_dir, "run.bat")
|
|
236
|
+
with open(run_bat, "w") as f:
|
|
237
|
+
f.write(f"@echo off\ncd /d \"%~dp0\"\npython run.py %*\n")
|
|
238
|
+
artifacts.append(run_bat)
|
|
239
|
+
|
|
240
|
+
# ── Compute asset hashes ─────────────────────────────────────────────────
|
|
241
|
+
asset_hashes = {}
|
|
242
|
+
for rel in _collect_assets(project_dir):
|
|
243
|
+
full = os.path.join(project_dir, rel)
|
|
244
|
+
asset_hashes[rel] = _sha256_file(full)
|
|
245
|
+
|
|
246
|
+
# ── Write build manifest ─────────────────────────────────────────────────
|
|
247
|
+
from inscript import VERSION as _IV
|
|
248
|
+
bm = _write_build_manifest(out_dir, {
|
|
249
|
+
"target": "desktop", "version": version, "entry": entry_rel,
|
|
250
|
+
"inscript_version": _IV,
|
|
251
|
+
"artifacts": {"run_py": "run.py", "run_sh": "run.sh", "run_bat": "run.bat"},
|
|
252
|
+
"asset_hashes": asset_hashes,
|
|
253
|
+
})
|
|
254
|
+
artifacts.append(bm)
|
|
255
|
+
|
|
256
|
+
elapsed = (time.perf_counter() - t0) * 1000
|
|
257
|
+
print(f"[build:desktop] ✅ {name} v{version} → {out_dir} ({elapsed:.0f}ms)")
|
|
258
|
+
return BuildResult("desktop", True, out_dir, artifacts, errors, elapsed)
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
262
|
+
# Target: web
|
|
263
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
264
|
+
|
|
265
|
+
_WEB_TEMPLATE = """\
|
|
266
|
+
<!DOCTYPE html>
|
|
267
|
+
<html lang="en">
|
|
268
|
+
<head>
|
|
269
|
+
<meta charset="UTF-8">
|
|
270
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
271
|
+
<title>{name}</title>
|
|
272
|
+
<style>
|
|
273
|
+
body {{ margin:0; background:#111; display:flex; flex-direction:column;
|
|
274
|
+
align-items:center; justify-content:center; height:100vh;
|
|
275
|
+
font-family:monospace; color:#ccc; }}
|
|
276
|
+
#canvas {{ border:2px solid #333; }}
|
|
277
|
+
#status {{ margin-top:8px; font-size:12px; }}
|
|
278
|
+
</style>
|
|
279
|
+
</head>
|
|
280
|
+
<body>
|
|
281
|
+
<canvas id="canvas" width="{width}" height="{height}"></canvas>
|
|
282
|
+
<div id="status">Loading InScript runtime…</div>
|
|
283
|
+
<script src="{pyodide_cdn}pyodide.js"></script>
|
|
284
|
+
<script type="module">
|
|
285
|
+
const status = document.getElementById("status");
|
|
286
|
+
|
|
287
|
+
async function main() {{
|
|
288
|
+
status.textContent = "Loading Pyodide…";
|
|
289
|
+
const pyodide = await loadPyodide({{ indexURL: "{pyodide_cdn}" }});
|
|
290
|
+
status.textContent = "Loading InScript runtime…";
|
|
291
|
+
|
|
292
|
+
// Fetch and exec inscript runtime
|
|
293
|
+
const r = await fetch("inscript_runtime/inscript.py");
|
|
294
|
+
const src = await r.text();
|
|
295
|
+
pyodide.runPython(src);
|
|
296
|
+
|
|
297
|
+
// Fetch and run entry point
|
|
298
|
+
const e = await fetch("{entry}");
|
|
299
|
+
const game = await e.text();
|
|
300
|
+
status.textContent = "Running {name}…";
|
|
301
|
+
pyodide.runPython(game);
|
|
302
|
+
}}
|
|
303
|
+
|
|
304
|
+
main().catch(err => {{
|
|
305
|
+
status.textContent = "Error: " + err.message;
|
|
306
|
+
console.error(err);
|
|
307
|
+
}});
|
|
308
|
+
</script>
|
|
309
|
+
</body>
|
|
310
|
+
</html>
|
|
311
|
+
"""
|
|
312
|
+
|
|
313
|
+
def build_web(project_dir: str = ".") -> BuildResult:
|
|
314
|
+
"""
|
|
315
|
+
Build a Pyodide-based web export.
|
|
316
|
+
|
|
317
|
+
Output layout:
|
|
318
|
+
build/web/
|
|
319
|
+
index.html — shell page with Pyodide loader
|
|
320
|
+
inscript_runtime/ — minimal runtime .py files
|
|
321
|
+
assets/ — copied assets
|
|
322
|
+
src/ — .ins source files
|
|
323
|
+
build.toml
|
|
324
|
+
"""
|
|
325
|
+
import time
|
|
326
|
+
t0 = time.perf_counter()
|
|
327
|
+
errors: List[str] = []
|
|
328
|
+
artifacts: List[str] = []
|
|
329
|
+
|
|
330
|
+
manifest = _read_manifest(project_dir)
|
|
331
|
+
pkg = manifest.get("package", {})
|
|
332
|
+
version = pkg.get("version", "0.0.0")
|
|
333
|
+
name = pkg.get("name", os.path.basename(os.path.abspath(project_dir)))
|
|
334
|
+
entry = pkg.get("entry", ENTRY_DEFAULT)
|
|
335
|
+
width = int(pkg.get("window_width", 800))
|
|
336
|
+
height = int(pkg.get("window_height", 600))
|
|
337
|
+
|
|
338
|
+
out_dir = os.path.join(project_dir, BUILD_DIR, "web")
|
|
339
|
+
os.makedirs(out_dir, exist_ok=True)
|
|
340
|
+
|
|
341
|
+
# Write index.html
|
|
342
|
+
html_path = os.path.join(out_dir, "index.html")
|
|
343
|
+
with open(html_path, "w", encoding="utf-8") as f:
|
|
344
|
+
f.write(_WEB_TEMPLATE.format(
|
|
345
|
+
name=name, width=width, height=height,
|
|
346
|
+
pyodide_cdn=PYODIDE_CDN, entry=entry,
|
|
347
|
+
))
|
|
348
|
+
artifacts.append(html_path)
|
|
349
|
+
|
|
350
|
+
# Copy minimal runtime
|
|
351
|
+
runtime_src = os.path.dirname(os.path.abspath(__file__))
|
|
352
|
+
runtime_dst = os.path.join(out_dir, "inscript_runtime")
|
|
353
|
+
os.makedirs(runtime_dst, exist_ok=True)
|
|
354
|
+
web_runtime_modules = [
|
|
355
|
+
"inscript.py", "lexer.py", "parser.py", "interpreter.py",
|
|
356
|
+
"compiler.py", "vm.py", "analyzer.py", "errors.py",
|
|
357
|
+
"environment.py", "ast_nodes.py",
|
|
358
|
+
"stdlib.py", "stdlib_extended.py", "stdlib_extended_2.py",
|
|
359
|
+
"stdlib_game.py", "stdlib_values.py", "stdlib_assets.py",
|
|
360
|
+
"scene_tree.py", "hot_reload.py",
|
|
361
|
+
]
|
|
362
|
+
for mod in web_runtime_modules:
|
|
363
|
+
src = os.path.join(runtime_src, mod)
|
|
364
|
+
if os.path.isfile(src):
|
|
365
|
+
shutil.copy2(src, os.path.join(runtime_dst, mod))
|
|
366
|
+
|
|
367
|
+
# Copy assets and source
|
|
368
|
+
for sub in ("assets", "src", "scenes"):
|
|
369
|
+
s = os.path.join(project_dir, sub)
|
|
370
|
+
d = os.path.join(out_dir, sub)
|
|
371
|
+
if os.path.isdir(s):
|
|
372
|
+
if os.path.exists(d):
|
|
373
|
+
shutil.rmtree(d)
|
|
374
|
+
shutil.copytree(s, d)
|
|
375
|
+
|
|
376
|
+
# Compute asset hashes
|
|
377
|
+
asset_hashes = {r: _sha256_file(os.path.join(project_dir, r))
|
|
378
|
+
for r in _collect_assets(project_dir)}
|
|
379
|
+
|
|
380
|
+
from inscript import VERSION as _IV
|
|
381
|
+
bm = _write_build_manifest(out_dir, {
|
|
382
|
+
"target": "web", "version": version, "entry": entry,
|
|
383
|
+
"inscript_version": _IV, "pyodide_cdn": PYODIDE_CDN,
|
|
384
|
+
"artifacts": {"index": "index.html"},
|
|
385
|
+
"asset_hashes": asset_hashes,
|
|
386
|
+
})
|
|
387
|
+
artifacts.append(bm)
|
|
388
|
+
|
|
389
|
+
elapsed = (time.perf_counter() - t0) * 1000
|
|
390
|
+
print(f"[build:web] ✅ {name} v{version} → {out_dir} ({elapsed:.0f}ms)")
|
|
391
|
+
print(f"[build:web] Serve with: python -m http.server 8080 --directory {out_dir}")
|
|
392
|
+
return BuildResult("web", True, out_dir, artifacts, errors, elapsed)
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
396
|
+
# Target: android
|
|
397
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
398
|
+
|
|
399
|
+
def build_android(project_dir: str = ".") -> BuildResult:
|
|
400
|
+
"""
|
|
401
|
+
Build an Android APK via BeeWare Briefcase.
|
|
402
|
+
Requires: pip install briefcase
|
|
403
|
+
|
|
404
|
+
Steps:
|
|
405
|
+
1. Generate a Briefcase project layout in build/android/
|
|
406
|
+
2. Run `briefcase create android` + `briefcase build android`
|
|
407
|
+
3. APK lands in build/android/android/<name>-<ver>/
|
|
408
|
+
"""
|
|
409
|
+
import time, subprocess
|
|
410
|
+
t0 = time.perf_counter()
|
|
411
|
+
errors: List[str] = []
|
|
412
|
+
artifacts: List[str] = []
|
|
413
|
+
|
|
414
|
+
# Check briefcase is available
|
|
415
|
+
briefcase_ok = shutil.which("briefcase") is not None
|
|
416
|
+
if not briefcase_ok:
|
|
417
|
+
try:
|
|
418
|
+
subprocess.run([sys.executable, "-m", "briefcase", "--version"],
|
|
419
|
+
check=True, capture_output=True)
|
|
420
|
+
briefcase_ok = True
|
|
421
|
+
except Exception:
|
|
422
|
+
pass
|
|
423
|
+
|
|
424
|
+
manifest = _read_manifest(project_dir)
|
|
425
|
+
pkg = manifest.get("package", {})
|
|
426
|
+
version = pkg.get("version", "0.0.0")
|
|
427
|
+
name = pkg.get("name", "mygame")
|
|
428
|
+
entry = pkg.get("entry", ENTRY_DEFAULT)
|
|
429
|
+
bundle = pkg.get("bundle", f"dev.inscript.{name}")
|
|
430
|
+
|
|
431
|
+
out_dir = os.path.join(project_dir, BUILD_DIR, "android")
|
|
432
|
+
os.makedirs(out_dir, exist_ok=True)
|
|
433
|
+
|
|
434
|
+
# Write pyproject.toml for Briefcase
|
|
435
|
+
bp_path = os.path.join(out_dir, "pyproject.toml")
|
|
436
|
+
with open(bp_path, "w", encoding="utf-8") as f:
|
|
437
|
+
f.write(textwrap.dedent(f"""\
|
|
438
|
+
[tool.briefcase]
|
|
439
|
+
project_name = "{name}"
|
|
440
|
+
bundle = "{bundle}"
|
|
441
|
+
version = "{version}"
|
|
442
|
+
description = "Built with InScript"
|
|
443
|
+
license = "MIT"
|
|
444
|
+
url = "https://github.com/authorss81/inscript"
|
|
445
|
+
|
|
446
|
+
[tool.briefcase.app.{name}]
|
|
447
|
+
formal_name = "{name}"
|
|
448
|
+
description = "Built with InScript"
|
|
449
|
+
sources = ["."]
|
|
450
|
+
requires = ["inscript-lang"]
|
|
451
|
+
|
|
452
|
+
[tool.briefcase.app.{name}.android]
|
|
453
|
+
requires = ["toga-android"]
|
|
454
|
+
"""))
|
|
455
|
+
artifacts.append(bp_path)
|
|
456
|
+
|
|
457
|
+
# Copy source into build/android/
|
|
458
|
+
for sub in ("src", "assets", "scenes"):
|
|
459
|
+
s = os.path.join(project_dir, sub)
|
|
460
|
+
d = os.path.join(out_dir, sub)
|
|
461
|
+
if os.path.isdir(s):
|
|
462
|
+
if os.path.exists(d): shutil.rmtree(d)
|
|
463
|
+
shutil.copytree(s, d)
|
|
464
|
+
|
|
465
|
+
# Main app entry
|
|
466
|
+
app_py = os.path.join(out_dir, "app.py")
|
|
467
|
+
with open(app_py, "w") as f:
|
|
468
|
+
f.write(textwrap.dedent(f"""\
|
|
469
|
+
# InScript Android entry — generated by inscript build --target android
|
|
470
|
+
import sys, os
|
|
471
|
+
try:
|
|
472
|
+
import inscript
|
|
473
|
+
inscript.main(["{entry}"])
|
|
474
|
+
except Exception as e:
|
|
475
|
+
print(f"InScript launch error: {{e}}", file=sys.stderr)
|
|
476
|
+
"""))
|
|
477
|
+
artifacts.append(app_py)
|
|
478
|
+
|
|
479
|
+
from inscript import VERSION as _IV
|
|
480
|
+
bm = _write_build_manifest(out_dir, {
|
|
481
|
+
"target": "android", "version": version, "entry": entry,
|
|
482
|
+
"inscript_version": _IV, "bundle": bundle,
|
|
483
|
+
"artifacts": {"pyproject": "pyproject.toml", "app": "app.py"},
|
|
484
|
+
"asset_hashes": {},
|
|
485
|
+
})
|
|
486
|
+
artifacts.append(bm)
|
|
487
|
+
|
|
488
|
+
if not briefcase_ok:
|
|
489
|
+
msg = ("BeeWare Briefcase not installed. "
|
|
490
|
+
"Install with: pip install briefcase "
|
|
491
|
+
"Then run from build/android/: briefcase create android && briefcase build android")
|
|
492
|
+
print(f"[build:android] ⚠ {msg}")
|
|
493
|
+
errors.append(msg)
|
|
494
|
+
elapsed = (time.perf_counter() - t0) * 1000
|
|
495
|
+
return BuildResult("android", False, out_dir, artifacts, errors, elapsed)
|
|
496
|
+
|
|
497
|
+
# Run briefcase
|
|
498
|
+
try:
|
|
499
|
+
subprocess.run(
|
|
500
|
+
[sys.executable, "-m", "briefcase", "create", "android"],
|
|
501
|
+
cwd=out_dir, check=True
|
|
502
|
+
)
|
|
503
|
+
subprocess.run(
|
|
504
|
+
[sys.executable, "-m", "briefcase", "build", "android"],
|
|
505
|
+
cwd=out_dir, check=True
|
|
506
|
+
)
|
|
507
|
+
print(f"[build:android] ✅ APK built → {out_dir}")
|
|
508
|
+
except subprocess.CalledProcessError as e:
|
|
509
|
+
errors.append(f"briefcase failed: {e}")
|
|
510
|
+
print(f"[build:android] ❌ briefcase error: {e}", file=sys.stderr)
|
|
511
|
+
|
|
512
|
+
elapsed = (time.perf_counter() - t0) * 1000
|
|
513
|
+
return BuildResult("android", len(errors) == 0, out_dir, artifacts, errors, elapsed)
|
|
514
|
+
|
|
515
|
+
|
|
516
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
517
|
+
# Project scaffold: `inscript new <name>`
|
|
518
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
519
|
+
|
|
520
|
+
_MAIN_INS_TEMPLATE = """\
|
|
521
|
+
# {name} — main scene
|
|
522
|
+
# Created by InScript v{inscript_version}
|
|
523
|
+
|
|
524
|
+
import "asset" as asset
|
|
525
|
+
|
|
526
|
+
node MainScene {{
|
|
527
|
+
let title: str = "{name}"
|
|
528
|
+
|
|
529
|
+
_ready() {{
|
|
530
|
+
print("Welcome to " + title)
|
|
531
|
+
}}
|
|
532
|
+
|
|
533
|
+
_update(dt) {{
|
|
534
|
+
# game logic here
|
|
535
|
+
}}
|
|
536
|
+
|
|
537
|
+
_draw() {{
|
|
538
|
+
# rendering here
|
|
539
|
+
}}
|
|
540
|
+
}}
|
|
541
|
+
"""
|
|
542
|
+
|
|
543
|
+
_INSCRIPT_TOML_TEMPLATE = """\
|
|
544
|
+
[package]
|
|
545
|
+
name = "{name}"
|
|
546
|
+
version = "0.1.0"
|
|
547
|
+
description = "A game built with InScript"
|
|
548
|
+
entry = "src/main.ins"
|
|
549
|
+
stdlib = ">={inscript_version}"
|
|
550
|
+
|
|
551
|
+
[dependencies]
|
|
552
|
+
# add packages here: math-utils = "^1.0"
|
|
553
|
+
|
|
554
|
+
[build]
|
|
555
|
+
window_width = 800
|
|
556
|
+
window_height = 600
|
|
557
|
+
"""
|
|
558
|
+
|
|
559
|
+
def scaffold_project(name: str, dest_dir: str = ".") -> str:
|
|
560
|
+
"""
|
|
561
|
+
Create a standard InScript project layout.
|
|
562
|
+
Returns the project root path.
|
|
563
|
+
"""
|
|
564
|
+
from inscript import VERSION as _IV
|
|
565
|
+
|
|
566
|
+
root = os.path.join(dest_dir, name)
|
|
567
|
+
if os.path.exists(root):
|
|
568
|
+
raise FileExistsError(f"Directory already exists: {root}")
|
|
569
|
+
|
|
570
|
+
# Create directory structure
|
|
571
|
+
for sub in ("src", "assets/sprites", "assets/sfx",
|
|
572
|
+
"assets/fonts", "assets/maps", "scenes", "build"):
|
|
573
|
+
os.makedirs(os.path.join(root, sub), exist_ok=True)
|
|
574
|
+
|
|
575
|
+
# inscript.toml
|
|
576
|
+
with open(os.path.join(root, MANIFEST_FILE), "w") as f:
|
|
577
|
+
f.write(_INSCRIPT_TOML_TEMPLATE.format(name=name, inscript_version=_IV))
|
|
578
|
+
|
|
579
|
+
# src/main.ins
|
|
580
|
+
with open(os.path.join(root, "src", "main.ins"), "w") as f:
|
|
581
|
+
f.write(_MAIN_INS_TEMPLATE.format(name=name, inscript_version=_IV))
|
|
582
|
+
|
|
583
|
+
# scenes/main.inscene
|
|
584
|
+
with open(os.path.join(root, "scenes", "main.inscene"), "w") as f:
|
|
585
|
+
f.write(f'# InScript Scene File\n\n[scene]\nname = "main"\nroot = "MainScene"\n')
|
|
586
|
+
|
|
587
|
+
# .gitignore
|
|
588
|
+
with open(os.path.join(root, ".gitignore"), "w") as f:
|
|
589
|
+
f.write("build/\n*.ibc\n__pycache__/\n*.pyc\n.inscript/\n")
|
|
590
|
+
|
|
591
|
+
# README.md
|
|
592
|
+
with open(os.path.join(root, "README.md"), "w") as f:
|
|
593
|
+
f.write(textwrap.dedent(f"""\
|
|
594
|
+
# {name}
|
|
595
|
+
|
|
596
|
+
Built with [InScript](https://github.com/authorss81/inscript) v{_IV}.
|
|
597
|
+
|
|
598
|
+
## Run
|
|
599
|
+
```
|
|
600
|
+
inscript src/main.ins
|
|
601
|
+
```
|
|
602
|
+
|
|
603
|
+
## Build
|
|
604
|
+
```
|
|
605
|
+
inscript build --target desktop
|
|
606
|
+
inscript build --target web
|
|
607
|
+
```
|
|
608
|
+
"""))
|
|
609
|
+
|
|
610
|
+
print(f"[inscript new] ✅ Created project '{name}' at {root}")
|
|
611
|
+
print(f"[inscript new] Run: cd {name} && inscript src/main.ins")
|
|
612
|
+
return root
|
|
613
|
+
|
|
614
|
+
|
|
615
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
616
|
+
# Top-level dispatcher called from inscript.py
|
|
617
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
618
|
+
|
|
619
|
+
def run_build(target: str, project_dir: str = ".") -> int:
|
|
620
|
+
"""
|
|
621
|
+
Entry point called by --build flag in inscript.py.
|
|
622
|
+
Returns exit code (0 = success).
|
|
623
|
+
"""
|
|
624
|
+
target = target.lower()
|
|
625
|
+
if target not in VALID_TARGETS:
|
|
626
|
+
print(f"[build] Unknown target '{target}'. Valid: {', '.join(VALID_TARGETS)}",
|
|
627
|
+
file=sys.stderr)
|
|
628
|
+
return 1
|
|
629
|
+
|
|
630
|
+
if target == "desktop":
|
|
631
|
+
r = build_desktop(project_dir)
|
|
632
|
+
elif target == "web":
|
|
633
|
+
r = build_web(project_dir)
|
|
634
|
+
elif target == "android":
|
|
635
|
+
r = build_android(project_dir)
|
|
636
|
+
elif target == "ibc":
|
|
637
|
+
# Delegate to existing compile pipeline
|
|
638
|
+
from inscript import cmd_compile
|
|
639
|
+
entry = _entry_point(project_dir, _read_manifest(project_dir))
|
|
640
|
+
return cmd_compile(entry) if os.path.isfile(entry) else 1
|
|
641
|
+
else:
|
|
642
|
+
return 1
|
|
643
|
+
|
|
644
|
+
if r.errors:
|
|
645
|
+
for e in r.errors:
|
|
646
|
+
print(f"[build] {e}", file=sys.stderr)
|
|
647
|
+
return 0 if r.success else 1
|
|
@@ -24,7 +24,7 @@ from errors import (InScriptError, LexerError, ParseError,
|
|
|
24
24
|
SemanticError, InScriptRuntimeError,
|
|
25
25
|
MultiError, InScriptWarning)
|
|
26
26
|
|
|
27
|
-
VERSION = "2.
|
|
27
|
+
VERSION = "2.11.0"
|
|
28
28
|
|
|
29
29
|
MANIFEST_FILENAME = "inscript.toml"
|
|
30
30
|
LOCK_FILENAME = "inscript.lock"
|
|
@@ -2721,6 +2721,13 @@ Examples:
|
|
|
2721
2721
|
help="v2.8.0: Generate assets.toml manifest for DIR (default: current dir)")
|
|
2722
2722
|
parser.add_argument("--bundle", metavar="DIR", nargs="?", const=".",
|
|
2723
2723
|
help="v2.8.0: Copy all referenced assets into <DIR>/dist/assets")
|
|
2724
|
+
# v2.11.0: export pipeline
|
|
2725
|
+
parser.add_argument("--build", metavar="TARGET", nargs="?", const="desktop",
|
|
2726
|
+
help="v2.11.0: Build for target: desktop, web, android, ibc (default: desktop)")
|
|
2727
|
+
parser.add_argument("--project-dir", metavar="DIR", default=".",
|
|
2728
|
+
help="v2.11.0: Project root for --build (default: current dir)")
|
|
2729
|
+
parser.add_argument("--new", metavar="NAME",
|
|
2730
|
+
help="v2.11.0: Scaffold a new InScript project: inscript --new mygame")
|
|
2724
2731
|
parser.add_argument("--lsp", action="store_true",
|
|
2725
2732
|
help="Start the Language Server (requires: pip install pygls)")
|
|
2726
2733
|
parser.add_argument("--game", action="store_true",
|
|
@@ -2987,6 +2994,24 @@ Examples:
|
|
|
2987
2994
|
print(f"[bundle] {_be}", file=sys.stderr); return 1
|
|
2988
2995
|
return 0
|
|
2989
2996
|
|
|
2997
|
+
# ── v2.11.0 export pipeline ────────────────────────────────────────────
|
|
2998
|
+
if getattr(args, 'new', None) is not None:
|
|
2999
|
+
try:
|
|
3000
|
+
from export_pipeline import scaffold_project
|
|
3001
|
+
scaffold_project(args.new, dest_dir=getattr(args, 'project_dir', '.'))
|
|
3002
|
+
except FileExistsError as _fe:
|
|
3003
|
+
print(f"[inscript new] {_fe}", file=sys.stderr); return 1
|
|
3004
|
+
except Exception as _ne:
|
|
3005
|
+
print(f"[inscript new] {_ne}", file=sys.stderr); return 1
|
|
3006
|
+
return 0
|
|
3007
|
+
|
|
3008
|
+
if getattr(args, 'build', None) is not None:
|
|
3009
|
+
try:
|
|
3010
|
+
from export_pipeline import run_build
|
|
3011
|
+
return run_build(args.build, getattr(args, 'project_dir', '.'))
|
|
3012
|
+
except Exception as _bld:
|
|
3013
|
+
print(f"[build] {_bld}", file=sys.stderr); return 1
|
|
3014
|
+
|
|
2990
3015
|
# v1.9.1: --no-typecheck is deprecated — emit a warning and honour it
|
|
2991
3016
|
if getattr(args, 'no_typecheck', False):
|
|
2992
3017
|
print(
|
|
@@ -61,7 +61,7 @@ py-modules = [
|
|
|
61
61
|
# ── Backends ──────────────────────────────────────────────────────────────
|
|
62
62
|
"pygame_backend",
|
|
63
63
|
# ── v2.7.0+ runtime modules ───────────────────────────────────────────────
|
|
64
|
-
"scene_tree", "hot_reload"
|
|
64
|
+
"scene_tree", "hot_reload", "export_pipeline"
|
|
65
65
|
]
|
|
66
66
|
|
|
67
67
|
[tool.setuptools.package-data]
|
|
@@ -55,7 +55,7 @@ setup(
|
|
|
55
55
|
# ── Backends ──────────────────────────────────────────────────────────
|
|
56
56
|
"pygame_backend",
|
|
57
57
|
# ── v2.7.0+ runtime modules ───────────────────────────────────────────
|
|
58
|
-
"scene_tree", "hot_reload",
|
|
58
|
+
"scene_tree", "hot_reload", "export_pipeline",
|
|
59
59
|
],
|
|
60
60
|
package_data = {"": ["examples/*.ins", "lsp/*.py", "*.md"]},
|
|
61
61
|
install_requires = [],
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|