oaknut-basic 12.13.1__tar.gz → 12.14.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.
Files changed (41) hide show
  1. {oaknut_basic-12.13.1 → oaknut_basic-12.14.0}/PKG-INFO +1 -1
  2. {oaknut_basic-12.13.1 → oaknut_basic-12.14.0}/src/oaknut/basic/__init__.py +1 -1
  3. {oaknut_basic-12.13.1 → oaknut_basic-12.14.0}/src/oaknut/basic/cli.py +25 -9
  4. {oaknut_basic-12.13.1 → oaknut_basic-12.14.0}/src/oaknut_basic.egg-info/PKG-INFO +1 -1
  5. {oaknut_basic-12.13.1 → oaknut_basic-12.14.0}/tests/test_cli.py +77 -3
  6. {oaknut_basic-12.13.1 → oaknut_basic-12.14.0}/LICENSE +0 -0
  7. {oaknut_basic-12.13.1 → oaknut_basic-12.14.0}/README.md +0 -0
  8. {oaknut_basic-12.13.1 → oaknut_basic-12.14.0}/pyproject.toml +0 -0
  9. {oaknut_basic-12.13.1 → oaknut_basic-12.14.0}/setup.cfg +0 -0
  10. {oaknut_basic-12.13.1 → oaknut_basic-12.14.0}/src/oaknut/basic/datafile.py +0 -0
  11. {oaknut_basic-12.13.1 → oaknut_basic-12.14.0}/src/oaknut/basic/detect.py +0 -0
  12. {oaknut_basic-12.13.1 → oaknut_basic-12.14.0}/src/oaknut/basic/detokeniser.py +0 -0
  13. {oaknut_basic-12.13.1 → oaknut_basic-12.14.0}/src/oaknut/basic/dialect.py +0 -0
  14. {oaknut_basic-12.13.1 → oaknut_basic-12.14.0}/src/oaknut/basic/exceptions.py +0 -0
  15. {oaknut_basic-12.13.1 → oaknut_basic-12.14.0}/src/oaknut/basic/float5.py +0 -0
  16. {oaknut_basic-12.13.1 → oaknut_basic-12.14.0}/src/oaknut/basic/linenumber.py +0 -0
  17. {oaknut_basic-12.13.1 → oaknut_basic-12.14.0}/src/oaknut/basic/numbering.py +0 -0
  18. {oaknut_basic-12.13.1 → oaknut_basic-12.14.0}/src/oaknut/basic/scanner.py +0 -0
  19. {oaknut_basic-12.13.1 → oaknut_basic-12.14.0}/src/oaknut/basic/tokeniser.py +0 -0
  20. {oaknut_basic-12.13.1 → oaknut_basic-12.14.0}/src/oaknut/basic/tokens.py +0 -0
  21. {oaknut_basic-12.13.1 → oaknut_basic-12.14.0}/src/oaknut_basic.egg-info/SOURCES.txt +0 -0
  22. {oaknut_basic-12.13.1 → oaknut_basic-12.14.0}/src/oaknut_basic.egg-info/dependency_links.txt +0 -0
  23. {oaknut_basic-12.13.1 → oaknut_basic-12.14.0}/src/oaknut_basic.egg-info/entry_points.txt +0 -0
  24. {oaknut_basic-12.13.1 → oaknut_basic-12.14.0}/src/oaknut_basic.egg-info/requires.txt +0 -0
  25. {oaknut_basic-12.13.1 → oaknut_basic-12.14.0}/src/oaknut_basic.egg-info/top_level.txt +0 -0
  26. {oaknut_basic-12.13.1 → oaknut_basic-12.14.0}/tests/test_basic.py +0 -0
  27. {oaknut_basic-12.13.1 → oaknut_basic-12.14.0}/tests/test_basic_v.py +0 -0
  28. {oaknut_basic-12.13.1 → oaknut_basic-12.14.0}/tests/test_crunch_rules.py +0 -0
  29. {oaknut_basic-12.13.1 → oaknut_basic-12.14.0}/tests/test_data_cli.py +0 -0
  30. {oaknut_basic-12.13.1 → oaknut_basic-12.14.0}/tests/test_datafile.py +0 -0
  31. {oaknut_basic-12.13.1 → oaknut_basic-12.14.0}/tests/test_detect.py +0 -0
  32. {oaknut_basic-12.13.1 → oaknut_basic-12.14.0}/tests/test_detokeniser.py +0 -0
  33. {oaknut_basic-12.13.1 → oaknut_basic-12.14.0}/tests/test_float5.py +0 -0
  34. {oaknut_basic-12.13.1 → oaknut_basic-12.14.0}/tests/test_keyword_coverage.py +0 -0
  35. {oaknut_basic-12.13.1 → oaknut_basic-12.14.0}/tests/test_linenumber.py +0 -0
  36. {oaknut_basic-12.13.1 → oaknut_basic-12.14.0}/tests/test_numbering.py +0 -0
  37. {oaknut_basic-12.13.1 → oaknut_basic-12.14.0}/tests/test_rom_golden.py +0 -0
  38. {oaknut_basic-12.13.1 → oaknut_basic-12.14.0}/tests/test_rom_golden_detokenise.py +0 -0
  39. {oaknut_basic-12.13.1 → oaknut_basic-12.14.0}/tests/test_scanner.py +0 -0
  40. {oaknut_basic-12.13.1 → oaknut_basic-12.14.0}/tests/test_tokeniser.py +0 -0
  41. {oaknut_basic-12.13.1 → oaknut_basic-12.14.0}/tests/test_tokens.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: oaknut-basic
3
- Version: 12.13.1
3
+ Version: 12.14.0
4
4
  Summary: BBC BASIC tools: program tokeniser/de-tokeniser and PRINT#/INPUT# data-file reader/writer
5
5
  Author-email: Robert Smallshire <robert@smallshire.org.uk>
6
6
  License-Expression: MIT
@@ -81,7 +81,7 @@ from oaknut.basic.tokens import (
81
81
  TOKEN_TO_KEYWORD,
82
82
  )
83
83
 
84
- __version__ = "12.13.1"
84
+ __version__ = "12.14.0"
85
85
 
86
86
  # Canonical load addresses for BBC BASIC programs on each host.
87
87
  # Programs saved by *SAVE on a real machine use these by default.
@@ -13,6 +13,7 @@ from __future__ import annotations
13
13
 
14
14
  import click
15
15
  from asyoulikeit.cli import report_output
16
+ from oaknut.cli.help import use_plain_help
16
17
 
17
18
  from . import __version__
18
19
 
@@ -107,12 +108,14 @@ def number(input_stream, output_stream, encoding: str, start: int, step: int) ->
107
108
 
108
109
  Reads BASIC source text from INPUT and writes the numbered program
109
110
  to OUTPUT. Both default to ``-``: INPUT to standard input, OUTPUT to
110
- standard output, so the command works file-to-file ::
111
+ standard output, so the command works file-to-file:
111
112
 
113
+ \b
112
114
  oaknut-basic number menu.bas menu-numbered.bas
113
115
 
114
- and as a pipe stage between ``disc get`` and ``disc put`` ::
116
+ and as a pipe stage between ``disc get`` and ``disc put``:
115
117
 
118
+ \b
116
119
  disc get game.ssd MENU - | oaknut-basic number --encoding acorn | disc put game.ssd MENU -
117
120
 
118
121
  Text is read and written in ``--encoding`` (``utf-8`` by default; pass
@@ -216,15 +219,17 @@ def tokenise(
216
219
 
217
220
  Reads numbered BASIC source from INPUT and writes the tokenised
218
221
  program bytes to OUTPUT. Both default to ``-`` (stdin / stdout), so it
219
- drops in alongside ``disc put`` ::
222
+ drops in alongside ``disc put``:
220
223
 
224
+ \b
221
225
  oaknut-basic tokenise menu.bas MENU
222
226
  cat menu.bas | oaknut-basic tokenise | disc put game.ssd MENU -
223
227
 
224
228
  Passing --start and/or --step auto-numbers unnumbered source, exactly
225
229
  as typing it under ``AUTO`` would; it is an error to use them on source
226
- that already carries line numbers ::
230
+ that already carries line numbers:
227
231
 
232
+ \b
228
233
  oaknut-basic tokenise --start 10 unnumbered.bas MENU
229
234
 
230
235
  INPUT is read in --encoding (``utf-8`` by default, for source authored
@@ -274,8 +279,9 @@ def detokenise(input_stream, output_stream, encoding: str, dialect: str) -> None
274
279
 
275
280
  Reads a tokenised program from INPUT and writes numbered source text
276
281
  to OUTPUT. Both default to ``-`` (stdin / stdout), so it drops in
277
- alongside ``disc get`` ::
282
+ alongside ``disc get``:
278
283
 
284
+ \b
279
285
  oaknut-basic detokenise MENU menu.bas
280
286
  disc get game.ssd MENU - | oaknut-basic detokenise
281
287
 
@@ -317,8 +323,9 @@ def detect(ctx: click.Context, inputs, quiet: bool) -> None:
317
323
  line walk the BBC BASIC ROM performs to ``LIST`` a program — without
318
324
  de-tokenising, so unlabelled files harvested from disc images can be
319
325
  filtered. With no FILE, or ``-``, reads a single blob from standard
320
- input ::
326
+ input:
321
327
 
328
+ \b
322
329
  oaknut-basic detect *
323
330
  disc get game.ssd MENU - | oaknut-basic detect
324
331
 
@@ -405,8 +412,9 @@ def decode(input_stream, encoding: str) -> None:
405
412
  to standard output, one element per record: integers and reals as JSON
406
413
  numbers, strings as JSON strings, and raw bytes as ``{"bytes": "hex"}``.
407
414
  Reals keep their full ``float`` repr (e.g. ``5.0``) so they round-trip
408
- back to reals rather than integers ::
415
+ back to reals rather than integers:
409
416
 
417
+ \b
410
418
  oaknut-basic data decode scores.dat | jq '.[0]'
411
419
 
412
420
  The output is consumed by ``oaknut-basic data encode`` to rebuild the
@@ -447,8 +455,9 @@ def encode(input_stream, output_stream, encoding: str) -> None:
447
455
  INPUT and writes the tagged data file to OUTPUT. Each element becomes
448
456
  one ``PRINT#`` record: a JSON integer becomes an integer, a JSON
449
457
  number with a fractional part a real, a JSON string a string, and
450
- ``{"bytes": "hex"}`` raw (untagged) bytes ::
458
+ ``{"bytes": "hex"}`` raw (untagged) bytes:
451
459
 
460
+ \b
452
461
  echo '[42, "HELLO", 3.5]' | oaknut-basic data encode - scores.dat
453
462
 
454
463
  A hand-authored real must carry a decimal point (``3.0``, not ``3``),
@@ -505,8 +514,9 @@ def inspect(input_stream, encoding: str):
505
514
  """Show the records in a BBC BASIC data file as a table.
506
515
 
507
516
  Reads a ``PRINT#``-tagged data file from INPUT and reports each record
508
- with its byte offset, type and value ::
517
+ with its byte offset, type and value:
509
518
 
519
+ \b
510
520
  oaknut-basic data inspect scores.dat
511
521
  oaknut-basic data inspect scores.dat --as json
512
522
 
@@ -527,5 +537,11 @@ def inspect(input_stream, encoding: str):
527
537
  return Reports(records=Report(data=table))
528
538
 
529
539
 
540
+ # With the whole tree assembled, render every command's help with RST inline
541
+ # markup stripped — the docstrings keep their markup for the Sphinx command
542
+ # reference, but a terminal sees plain text, not stray backticks.
543
+ use_plain_help(cli)
544
+
545
+
530
546
  if __name__ == "__main__": # pragma: no cover
531
547
  cli()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: oaknut-basic
3
- Version: 12.13.1
3
+ Version: 12.14.0
4
4
  Summary: BBC BASIC tools: program tokeniser/de-tokeniser and PRINT#/INPUT# data-file reader/writer
5
5
  Author-email: Robert Smallshire <robert@smallshire.org.uk>
6
6
  License-Expression: MIT
@@ -6,6 +6,7 @@ usage, the option parsing, and the byte/text (Acorn-encoding) boundary —
6
6
  rather than re-testing the numbering or tokenising logic itself.
7
7
  """
8
8
 
9
+ import inspect
9
10
  from pathlib import Path
10
11
 
11
12
  import oaknut.basic as basic
@@ -141,6 +142,81 @@ class TestTopLevel:
141
142
  assert "number" in result.output
142
143
 
143
144
 
145
+ def _walk_commands(command, path=()):
146
+ """Yield ``(argv, command)`` for every command and sub-command."""
147
+ yield list(path), command
148
+ for name, child in getattr(command, "commands", {}).items():
149
+ yield from _walk_commands(child, (*path, name))
150
+
151
+
152
+ def _no_rewrap_lines(help_text):
153
+ """Return the stripped lines of a docstring's ``\\b`` no-rewrap blocks."""
154
+ if not help_text:
155
+ return []
156
+ lines = inspect.cleandoc(help_text).splitlines()
157
+ marked = []
158
+ in_block = False
159
+ for line in lines:
160
+ if line.strip() == "\b":
161
+ in_block = True
162
+ elif not line.strip():
163
+ in_block = False
164
+ elif in_block:
165
+ # The asyoulikeit reports epilog is a \b block of prose that
166
+ # the help formatter deliberately re-wraps.
167
+ if line.strip() == "Produces reports:":
168
+ in_block = False
169
+ continue
170
+ marked.append(line.strip())
171
+ return marked
172
+
173
+
174
+ _ALL_COMMANDS = [
175
+ pytest.param(argv, id=" ".join(argv) or "oaknut-basic") for argv, _ in _walk_commands(cli)
176
+ ]
177
+
178
+
179
+ class TestHelpFormatting:
180
+ """The example blocks in command docstrings must survive Click's rewrapper.
181
+
182
+ Click reflows every paragraph of a docstring unless it is preceded by
183
+ a ``\\b`` no-rewrap marker. Without one, a block of example command
184
+ lines is run together into a single paragraph and re-broken at
185
+ arbitrary points, which reads as two commands spliced together.
186
+ """
187
+
188
+ @pytest.mark.parametrize("argv", _ALL_COMMANDS)
189
+ def test_docstring_example_lines_survive_verbatim(self, argv):
190
+ command = cli
191
+ for name in argv:
192
+ command = command.commands[name]
193
+ expected = _no_rewrap_lines(command.help)
194
+ if not expected:
195
+ pytest.skip("no example block in this command's docstring")
196
+ runner = CliRunner()
197
+ result = runner.invoke(cli, [*argv, "--help"])
198
+ assert result.exit_code == 0
199
+ rendered = {line.strip() for line in result.output.splitlines()}
200
+ for line in expected:
201
+ assert line in rendered, (
202
+ f"example line reflowed away in `{' '.join(argv)} --help`: {line!r}"
203
+ )
204
+
205
+ @pytest.mark.parametrize("argv", _ALL_COMMANDS)
206
+ def test_no_rst_inline_markup_leaks(self, argv):
207
+ runner = CliRunner()
208
+ result = runner.invoke(cli, [*argv, "--help"])
209
+ assert result.exit_code == 0
210
+ assert "`" not in result.output
211
+
212
+ @pytest.mark.parametrize("argv", _ALL_COMMANDS)
213
+ def test_no_rst_literal_block_markers_leak(self, argv):
214
+ runner = CliRunner()
215
+ result = runner.invoke(cli, [*argv, "--help"])
216
+ assert result.exit_code == 0
217
+ assert " ::" not in result.output
218
+
219
+
144
220
  class TestTokeniseCommand:
145
221
  def test_pipe_tokenises_to_stdout(self):
146
222
  runner = CliRunner()
@@ -192,9 +268,7 @@ class TestTokeniseEncoding:
192
268
  # --encoding acorn treats the input as Acorn bytes already, so a
193
269
  # 0x60 byte is the £ literal verbatim.
194
270
  runner = CliRunner()
195
- result = runner.invoke(
196
- cli, ["tokenise", "--encoding", "acorn"], input=b'10 PRINT "\x60"\n'
197
- )
271
+ result = runner.invoke(cli, ["tokenise", "--encoding", "acorn"], input=b'10 PRINT "\x60"\n')
198
272
  assert result.exit_code == 0
199
273
  assert b'"\x60"' in result.stdout_bytes
200
274
 
File without changes
File without changes
File without changes