hoodscript 1.3.0__tar.gz → 1.3.1__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.
- {hoodscript-1.3.0 → hoodscript-1.3.1}/CHANGELOG.md +25 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/PKG-INFO +1 -1
- {hoodscript-1.3.0 → hoodscript-1.3.1}/pyproject.toml +3 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/src/hoodarcade/cli.py +17 -4
- {hoodscript-1.3.0 → hoodscript-1.3.1}/src/hoodfarm/cli.py +18 -5
- {hoodscript-1.3.0 → hoodscript-1.3.1}/src/hoodscript/_version.py +2 -2
- {hoodscript-1.3.0 → hoodscript-1.3.1}/src/hoodscript/cli.py +18 -10
- {hoodscript-1.3.0 → hoodscript-1.3.1}/src/hoodscript/kernel/kernel.py +4 -1
- {hoodscript-1.3.0 → hoodscript-1.3.1}/tests/test_cli.py +14 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/tests/test_kernel.py +14 -0
- hoodscript-1.3.1/tests/test_wheel_contents.py +34 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/.gitignore +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/CODE_OF_CONDUCT.md +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/CONTRIBUTING.md +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/LICENSE +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/README.md +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/SECURITY.md +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/SPRINT_PLAN.md +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/docs/adr/0001-token-pipeline-over-parser.md +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/docs/adr/0002-keyword-reconciliation.md +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/docs/adr/0003-inclusive-ranges.md +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/docs/adr/0004-no-regional-dialect-overlays.md +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/docs/adr/README.md +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/docs/archive/HOODSCRIPT_COMPLETE_MASTER_SYSTEM.md +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/docs/archive/HOODSCRIPT_FULL_ENGINEERING_SUITE.md +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/docs/archive/HOODSCRIPT_UNSPRED_FEATURES_ROADMAP.md +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/docs/curriculum.md +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/docs/grammar.ebnf +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/docs/keywords.md +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/docs/onboarding.md +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/examples/quickstart.ipynb +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/examples/solutions/beatlab/stage1.hs +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/examples/solutions/beatlab/stage2.hs +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/examples/solutions/beatlab/stage3.hs +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/examples/solutions/beatlab/stage4.hs +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/examples/solutions/beatlab/stage5.hs +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/examples/solutions/cookout/stage1.hs +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/examples/solutions/cookout/stage2.hs +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/examples/solutions/cookout/stage3.hs +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/examples/solutions/cookout/stage4.hs +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/examples/solutions/cookout/stage5.hs +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/examples/solutions/dropday/stage1.hs +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/examples/solutions/dropday/stage2.hs +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/examples/solutions/dropday/stage3.hs +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/examples/solutions/dropday/stage4.hs +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/examples/solutions/dropday/stage5.hs +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/examples/solutions/level1.hs +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/examples/solutions/level2.hs +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/examples/solutions/level3.hs +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/examples/solutions/level4.hs +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/examples/solutions/level5.hs +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/examples/solutions/level6.hs +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/examples/solutions/level7.hs +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/examples/solutions/level8.hs +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/out.txt +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/playground/app.js +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/playground/bundle.js +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/playground/examples.js +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/playground/hoodscript-latest-py3-none-any.whl +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/playground/index.html +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/playground/style.css +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/playground/worker.js +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/scripts/build_playground.py +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/scripts/gen_ebnf.py +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/scripts/gen_keywords.py +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/scripts/hooks/pre-push +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/scripts/install-hooks.sh +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/src/hoodarcade/__init__.py +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/src/hoodarcade/core/runtime.py +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/src/hoodarcade/core/ui.py +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/src/hoodarcade/games/beatlab.py +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/src/hoodarcade/games/cookout.py +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/src/hoodarcade/games/dropday.py +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/src/hoodfarm/__init__.py +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/src/hoodfarm/drone.py +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/src/hoodfarm/grid.py +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/src/hoodfarm/levels.py +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/src/hoodfarm/renderer.py +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/src/hoodfarm/runner.py +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/src/hoodlib/__init__.py +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/src/hoodlib/disk.py +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/src/hoodscript/__init__.py +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/src/hoodscript/__main__.py +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/src/hoodscript/cache.py +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/src/hoodscript/constants.py +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/src/hoodscript/debugger.py +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/src/hoodscript/errors/__init__.py +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/src/hoodscript/errors/catalog.py +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/src/hoodscript/errors/renderer.py +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/src/hoodscript/errors/translator.py +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/src/hoodscript/formatter.py +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/src/hoodscript/importer.py +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/src/hoodscript/kernel/__init__.py +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/src/hoodscript/kernel/__main__.py +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/src/hoodscript/kernel/installer.py +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/src/hoodscript/kernel/magics.py +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/src/hoodscript/linter.py +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/src/hoodscript/lsp.py +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/src/hoodscript/migrator.py +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/src/hoodscript/patterns.py +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/src/hoodscript/repl.py +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/src/hoodscript/sandbox.py +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/src/hoodscript/stubs.py +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/src/hoodscript/traceback_handler.py +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/src/hoodscript/transpiler.py +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/src/hoodscript/tutor.py +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/src/hoodscript/zen.py +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/src/vibe.py +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/tests/security/conftest.py +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/tests/security/corpus/deep_nesting.hs +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/tests/security/corpus/fork_bomb.hs +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/tests/security/corpus/huge_literal.hs +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/tests/security/corpus/import_escapes.hs +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/tests/security/corpus/infinite_recursion.hs +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/tests/security/corpus/open_file.hs +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/tests/security/corpus/pathological_indent.hs +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/tests/security/corpus/runaway_output.hs +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/tests/security/corpus/runaway_time.hs +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/tests/security/test_corpus.py +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/tests/security/test_exec_sites.py +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/tests/security/test_fuzz.py +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/tests/security/test_sandbox.py +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/tests/test_arcade.py +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/tests/test_arcade_gates.py +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/tests/test_conformance.py +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/tests/test_debugger.py +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/tests/test_docs.py +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/tests/test_errors.py +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/tests/test_fmt.py +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/tests/test_fuzz_ebnf.py +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/tests/test_game_engine.py +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/tests/test_grammar_canonical.py +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/tests/test_grammar_ebnf.py +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/tests/test_hoodlib_disk.py +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/tests/test_importer.py +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/tests/test_kernel_zmq.py +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/tests/test_keywords_doc.py +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/tests/test_learn.py +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/tests/test_lint.py +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/tests/test_lsp_features.py +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/tests/test_patterns.py +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/tests/test_playground.py +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/tests/test_release.py +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/tests/test_repl.py +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/tests/test_transpiler.py +0 -0
- {hoodscript-1.3.0 → hoodscript-1.3.1}/tests/test_vscode_extension.py +0 -0
|
@@ -5,6 +5,31 @@ Versioning: the keyword set is the public API; changing it is at least a minor b
|
|
|
5
5
|
|
|
6
6
|
## [Unreleased]
|
|
7
7
|
|
|
8
|
+
## [1.3.1] — 2026-09-12
|
|
9
|
+
|
|
10
|
+
Patch release: what the fresh-machine check of 1.3.0 found, plus two audit
|
|
11
|
+
leftovers. No keyword or API changes.
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- Fresh-machine check of v1.3.0 (`pip install "hoodscript[all]"` in a clean
|
|
16
|
+
venv, `docs/onboarding.md` §2–§11 verbatim): everything passed except
|
|
17
|
+
`hoodfarm test` and `hoodarcade test`, which reported "No Sol" for every
|
|
18
|
+
level and **exited 0** — the reference solutions were not in the wheel and a
|
|
19
|
+
missing solution counted as a pass. The solutions now ship
|
|
20
|
+
(`hoodscript/data/solutions`, found by both CLIs after the repo checkout), a
|
|
21
|
+
missing one is a red `MISSING` row and a non-zero exit, and
|
|
22
|
+
`tests/test_wheel_contents.py` builds the wheel and asserts the runtime
|
|
23
|
+
data (curriculum, `vibe.py`, solutions) is inside it.
|
|
24
|
+
|
|
25
|
+
- `hoodscript --sandbox run f.hs` now works (the flags may come before or after
|
|
26
|
+
`run`; `run` is recognised as the first positional, not only as `argv[0]`).
|
|
27
|
+
- Jupyter: `%hood_tb` is honoured by the error card — the Python traceback
|
|
28
|
+
section opens by default and the plain-text fallback carries it too.
|
|
29
|
+
- MANUAL §16 documents that `hoodscript fmt` writes `ain't nobody x` back as
|
|
30
|
+
`ain't x` (same Python, one canonical spelling).
|
|
31
|
+
- `cli.py` had the `arcade` dispatch block twice; the copy is gone.
|
|
32
|
+
|
|
8
33
|
## [1.3.0] — 2026-09-12
|
|
9
34
|
|
|
10
35
|
Three features that were on `main` but not on PyPI: the two games and the
|
|
@@ -61,6 +61,9 @@ packages = ["src/hoodscript", "src/hoodlib", "src/hoodfarm", "src/hoodarcade"]
|
|
|
61
61
|
"docs/curriculum.md" = "hoodscript/data/curriculum.md"
|
|
62
62
|
# `import vibe` — the Zen of HoodScript, like `import this`.
|
|
63
63
|
"src/vibe.py" = "vibe.py"
|
|
64
|
+
# `hoodfarm test` / `hoodarcade test` benchmark the reference solutions; a pip
|
|
65
|
+
# install has no examples/ tree, so ship them (found via hoodscript/data/solutions).
|
|
66
|
+
"examples/solutions" = "hoodscript/data/solutions"
|
|
64
67
|
|
|
65
68
|
[tool.hatch.build.targets.sdist]
|
|
66
69
|
# The nested hoodscript/ prototype is reference material, not part of the distribution.
|
|
@@ -191,11 +191,23 @@ def cmd_run(args: argparse.Namespace, console: Console, ui: ArcadeUI, progress:
|
|
|
191
191
|
return run_stage_session(game_id, stage_id, file_path, console, ui, progress, once=True)
|
|
192
192
|
|
|
193
193
|
|
|
194
|
+
def solutions_dir() -> Path:
|
|
195
|
+
"""Where the reference solutions live: the repo's examples/solutions when run
|
|
196
|
+
from a checkout, else the copy shipped in the wheel (hoodscript/data/solutions)."""
|
|
197
|
+
import hoodscript
|
|
198
|
+
for cand in (
|
|
199
|
+
Path("examples/solutions"),
|
|
200
|
+
Path(__file__).resolve().parents[2] / "examples" / "solutions",
|
|
201
|
+
Path(hoodscript.__file__).resolve().parent / "data" / "solutions",
|
|
202
|
+
):
|
|
203
|
+
if cand.is_dir():
|
|
204
|
+
return cand
|
|
205
|
+
return Path("examples/solutions")
|
|
206
|
+
|
|
207
|
+
|
|
194
208
|
def cmd_test(args: argparse.Namespace, console: Console) -> int:
|
|
195
209
|
"""Run automated benchmarks against reference solutions."""
|
|
196
|
-
solutions_root =
|
|
197
|
-
if not solutions_root.exists():
|
|
198
|
-
solutions_root = Path(__file__).resolve().parents[2] / "examples" / "solutions"
|
|
210
|
+
solutions_root = solutions_dir()
|
|
199
211
|
|
|
200
212
|
target_games = [args.game.lower()] if args.game else list(GAMES_MAP.keys())
|
|
201
213
|
failures = 0
|
|
@@ -220,12 +232,13 @@ def cmd_test(args: argparse.Namespace, console: Console) -> int:
|
|
|
220
232
|
sol_file = solutions_root / game_id / f"stage{stg_id}.hs"
|
|
221
233
|
|
|
222
234
|
if not sol_file.exists():
|
|
235
|
+
failures += 1 # a missing solution is not a pass
|
|
223
236
|
table.add_row(
|
|
224
237
|
game_id.capitalize(),
|
|
225
238
|
str(stg_id),
|
|
226
239
|
stage.title,
|
|
227
240
|
", ".join(stage.concepts),
|
|
228
|
-
"[
|
|
241
|
+
f"[bold red]MISSING[/bold red] {sol_file}",
|
|
229
242
|
)
|
|
230
243
|
continue
|
|
231
244
|
|
|
@@ -128,11 +128,23 @@ def cmd_run(args: argparse.Namespace, console: Console) -> int:
|
|
|
128
128
|
return 0 if won else 1
|
|
129
129
|
|
|
130
130
|
|
|
131
|
+
def solutions_dir() -> Path:
|
|
132
|
+
"""Where the reference solutions live: the repo's examples/solutions when run
|
|
133
|
+
from a checkout, else the copy shipped in the wheel (hoodscript/data/solutions)."""
|
|
134
|
+
import hoodscript
|
|
135
|
+
for cand in (
|
|
136
|
+
Path("examples/solutions"),
|
|
137
|
+
Path(__file__).resolve().parents[2] / "examples" / "solutions",
|
|
138
|
+
Path(hoodscript.__file__).resolve().parent / "data" / "solutions",
|
|
139
|
+
):
|
|
140
|
+
if cand.is_dir():
|
|
141
|
+
return cand
|
|
142
|
+
return Path("examples/solutions")
|
|
143
|
+
|
|
144
|
+
|
|
131
145
|
def cmd_test(args: argparse.Namespace, console: Console) -> int:
|
|
132
146
|
"""Run automated benchmarks against solutions."""
|
|
133
|
-
|
|
134
|
-
if not solutions_dir.exists():
|
|
135
|
-
solutions_dir = Path(__file__).resolve().parents[2] / "examples" / "solutions"
|
|
147
|
+
solutions = solutions_dir()
|
|
136
148
|
|
|
137
149
|
target_levels = [args.level] if args.level else sorted(ALL_LEVELS.keys())
|
|
138
150
|
failures = 0
|
|
@@ -152,9 +164,10 @@ def cmd_test(args: argparse.Namespace, console: Console) -> int:
|
|
|
152
164
|
|
|
153
165
|
for lvl_id in target_levels:
|
|
154
166
|
level = ALL_LEVELS[lvl_id]
|
|
155
|
-
sol_file =
|
|
167
|
+
sol_file = solutions / f"level{lvl_id}.hs"
|
|
156
168
|
if not sol_file.exists():
|
|
157
|
-
|
|
169
|
+
failures += 1 # a missing solution is not a pass
|
|
170
|
+
table.add_row(str(lvl_id), level.title, "-", "-", "-", f"[bold red]MISSING[/bold red] {sol_file}")
|
|
158
171
|
continue
|
|
159
172
|
|
|
160
173
|
code = sol_file.read_text(encoding="utf-8")
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '1.3.
|
|
22
|
-
__version_tuple__ = version_tuple = (1, 3,
|
|
21
|
+
__version__ = version = '1.3.1'
|
|
22
|
+
__version_tuple__ = version_tuple = (1, 3, 1)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -380,6 +380,20 @@ def kernel_command(args: list[str]) -> int:
|
|
|
380
380
|
return 0
|
|
381
381
|
|
|
382
382
|
|
|
383
|
+
_VALUED = ("--max-seconds", "--max-output", "--max-depth")
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
def _strip_run(argv):
|
|
387
|
+
i = 0
|
|
388
|
+
while i < len(argv):
|
|
389
|
+
a = argv[i]
|
|
390
|
+
if a in _VALUED: i += 2; continue
|
|
391
|
+
if a.startswith("-") and a != "-": i += 1; continue
|
|
392
|
+
if a == "run": return argv[:i] + argv[i + 1:]
|
|
393
|
+
return argv
|
|
394
|
+
return argv
|
|
395
|
+
|
|
396
|
+
|
|
383
397
|
def main(argv=None) -> int:
|
|
384
398
|
if argv is None:
|
|
385
399
|
argv = sys.argv[1:]
|
|
@@ -449,13 +463,6 @@ def main(argv=None) -> int:
|
|
|
449
463
|
sys.stderr.write("The arcade needs rich. Install it: pip install \"hoodscript[arcade]\"\n")
|
|
450
464
|
return 2
|
|
451
465
|
return arcade_main(argv[1:])
|
|
452
|
-
if cmd == "arcade":
|
|
453
|
-
try:
|
|
454
|
-
from hoodarcade.cli import main as arcade_main
|
|
455
|
-
except ImportError:
|
|
456
|
-
sys.stderr.write("The arcade needs rich. Install it: pip install \"hoodscript[arcade]\"\n")
|
|
457
|
-
return 2
|
|
458
|
-
return arcade_main(argv[1:])
|
|
459
466
|
if cmd == "build":
|
|
460
467
|
# build <file> transpiles to stdout (alias for -c)
|
|
461
468
|
if len(argv) < 2:
|
|
@@ -467,9 +474,10 @@ def main(argv=None) -> int:
|
|
|
467
474
|
sys.stdout.write(transpile(src, filepath) + "\n")
|
|
468
475
|
return 0
|
|
469
476
|
|
|
470
|
-
#
|
|
471
|
-
|
|
472
|
-
|
|
477
|
+
# `hoodscript run f.hs` and `hoodscript --sandbox run f.hs` both work: `run` is
|
|
478
|
+
# the first positional, wherever the flags landed. `_VALUED` are the options
|
|
479
|
+
# that take an argument, so their value is not mistaken for the positional.
|
|
480
|
+
argv = _strip_run(argv)
|
|
473
481
|
|
|
474
482
|
# Fallback to standard execution parser (supports `hoodscript <file>`, `-c`, `--sandbox`, etc.)
|
|
475
483
|
parser = argparse.ArgumentParser(prog="hoodscript")
|
|
@@ -109,8 +109,9 @@ class HoodScriptKernel(Kernel):
|
|
|
109
109
|
)
|
|
110
110
|
|
|
111
111
|
if raw_tb_escaped:
|
|
112
|
+
opened = " open" if self.show_raw_traceback else "" # %hood_tb expands the traceback by default
|
|
112
113
|
card += f"""
|
|
113
|
-
<details style="margin-top: 8px; font-size: 12px; color: #9ca3af;">
|
|
114
|
+
<details{opened} style="margin-top: 8px; font-size: 12px; color: #9ca3af;">
|
|
114
115
|
<summary style="cursor: pointer; user-select: none;">Show Python Traceback</summary>
|
|
115
116
|
<pre style="background: rgba(0,0,0,0.4); padding: 8px; border-radius: 4px;
|
|
116
117
|
color: #f87171; overflow-x: auto; font-family: monospace;
|
|
@@ -126,6 +127,8 @@ class HoodScriptKernel(Kernel):
|
|
|
126
127
|
return
|
|
127
128
|
|
|
128
129
|
plain_text = render_plain(diag) if diag else str(exc)
|
|
130
|
+
if self.show_raw_traceback and raw_tb:
|
|
131
|
+
plain_text += "\n\n--- Python traceback (%hood_tb) ---\n" + raw_tb
|
|
129
132
|
html_card = self._render_html_card(diag, raw_tb)
|
|
130
133
|
|
|
131
134
|
content = {
|
|
@@ -86,6 +86,20 @@ def test_cli_run(tmp_path):
|
|
|
86
86
|
assert "from run" in res2.stdout.strip()
|
|
87
87
|
|
|
88
88
|
|
|
89
|
+
def test_cli_run_accepts_flags_before_the_word_run(tmp_path):
|
|
90
|
+
"""`hoodscript --sandbox run f.hs` and `hoodscript run --sandbox f.hs` are the same command."""
|
|
91
|
+
from hoodscript.cli import _strip_run
|
|
92
|
+
assert _strip_run(["run", "f.hs"]) == ["f.hs"]
|
|
93
|
+
assert _strip_run(["--sandbox", "run", "f.hs"]) == ["--sandbox", "f.hs"]
|
|
94
|
+
assert _strip_run(["--max-seconds", "5", "run", "f.hs"]) == ["--max-seconds", "5", "f.hs"]
|
|
95
|
+
assert _strip_run(["run.hs"]) == ["run.hs"] # a file called run.hs is a file
|
|
96
|
+
assert _strip_run(["f.hs", "run"]) == ["f.hs", "run"] # only the first positional
|
|
97
|
+
src = tmp_path / "prog.hs"
|
|
98
|
+
src.write_text('f = open("/dev/null")\n', encoding="utf-8")
|
|
99
|
+
res = run_cli("--sandbox", "run", str(src))
|
|
100
|
+
assert res.returncode != 0 and "NoAccessTrip" in res.stderr
|
|
101
|
+
|
|
102
|
+
|
|
89
103
|
def test_cli_fmt_and_lint(tmp_path):
|
|
90
104
|
src = tmp_path / "test.hs"
|
|
91
105
|
src.write_text("bet f( ):\n unused = 1\n dip 2\n", encoding="utf-8")
|
|
@@ -105,6 +105,20 @@ def test_kernel_magic_traceback_toggle(mock_kernel):
|
|
|
105
105
|
res = mock_kernel.do_execute("%hood_tb", silent=False)
|
|
106
106
|
assert res["status"] == "ok"
|
|
107
107
|
assert mock_kernel.show_raw_traceback is True
|
|
108
|
+
# The toggle is honoured by the card: the traceback section opens by default
|
|
109
|
+
# and the plain-text fallback carries the raw traceback too.
|
|
110
|
+
mock_kernel.send_response.reset_mock()
|
|
111
|
+
mock_kernel.do_execute("throw BadValueTrip('x')", silent=False)
|
|
112
|
+
shown = [c for c in mock_kernel.send_response.call_args_list if c.args[1] == "display_data"]
|
|
113
|
+
assert shown, "no diagnostic was displayed"
|
|
114
|
+
data = shown[-1].args[2]["data"]
|
|
115
|
+
assert "<details open" in data["text/html"]
|
|
116
|
+
assert "--- Python traceback (%hood_tb) ---" in data["text/plain"]
|
|
117
|
+
mock_kernel.do_execute("%hood_tb", silent=False) # back off
|
|
118
|
+
mock_kernel.send_response.reset_mock()
|
|
119
|
+
mock_kernel.do_execute("throw BadValueTrip('x')", silent=False)
|
|
120
|
+
shown = [c for c in mock_kernel.send_response.call_args_list if c.args[1] == "display_data"]
|
|
121
|
+
assert "<details open" not in shown[-1].args[2]["data"]["text/html"]
|
|
108
122
|
|
|
109
123
|
|
|
110
124
|
def test_kernel_do_complete(mock_kernel):
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""The wheel is what users install; the repo is what tests run against. Things the
|
|
2
|
+
CLI reads at runtime (curriculum, `import vibe`, the games' reference solutions)
|
|
3
|
+
are force-included by pyproject — this test builds the wheel and checks they are
|
|
4
|
+
in it, because a fresh-machine check of v1.3.0 found `hoodfarm test` reporting
|
|
5
|
+
"No Sol" for every level and exiting 0."""
|
|
6
|
+
|
|
7
|
+
import subprocess
|
|
8
|
+
import sys
|
|
9
|
+
import zipfile
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
import pytest
|
|
13
|
+
|
|
14
|
+
ROOT = Path(__file__).resolve().parents[1]
|
|
15
|
+
pytest.importorskip("hatchling")
|
|
16
|
+
|
|
17
|
+
REQUIRED = [
|
|
18
|
+
"hoodscript/data/curriculum.md",
|
|
19
|
+
"vibe.py",
|
|
20
|
+
"hoodscript/data/solutions/level1.hs",
|
|
21
|
+
"hoodscript/data/solutions/level8.hs",
|
|
22
|
+
"hoodscript/data/solutions/cookout/stage1.hs",
|
|
23
|
+
"hoodscript/data/solutions/beatlab/stage5.hs",
|
|
24
|
+
"hoodscript/data/solutions/dropday/stage3.hs",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def test_wheel_ships_the_runtime_data(tmp_path):
|
|
29
|
+
subprocess.run([sys.executable, "-m", "hatchling", "build", "-t", "wheel", "-d", str(tmp_path)],
|
|
30
|
+
cwd=ROOT, check=True, capture_output=True)
|
|
31
|
+
wheel = next(tmp_path.glob("*.whl"))
|
|
32
|
+
names = set(zipfile.ZipFile(wheel).namelist())
|
|
33
|
+
missing = [n for n in REQUIRED if n not in names]
|
|
34
|
+
assert not missing, f"not in the wheel: {missing}"
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|