oaknut-cli 12.8.2__tar.gz → 12.10.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: oaknut-cli
3
- Version: 12.8.2
3
+ Version: 12.10.0
4
4
  Summary: Shared CLI toolkit for the oaknut family: the contributed-command axis and report-rendering helpers a disc command needs, below the filesystem packages.
5
5
  Author-email: Robert Smallshire <robert@smallshire.org.uk>
6
6
  License-Expression: MIT
@@ -23,7 +23,7 @@ Requires-Dist: oaknut-exception>=10.0
23
23
  Requires-Dist: oaknut-extension>=10.0
24
24
  Requires-Dist: oaknut-file>=10.0
25
25
  Requires-Dist: click>=8.1.7
26
- Requires-Dist: asyoulikeit>=1.3.0
26
+ Requires-Dist: asyoulikeit>=1.4.0
27
27
  Dynamic: license-file
28
28
 
29
29
  # oaknut-cli
@@ -27,7 +27,7 @@ dependencies = [
27
27
  "oaknut-extension>=10.0",
28
28
  "oaknut-file>=10.0",
29
29
  "click>=8.1.7",
30
- "asyoulikeit>=1.3.0",
30
+ "asyoulikeit>=1.4.0",
31
31
  ]
32
32
 
33
33
  [project.urls]
@@ -33,12 +33,14 @@ from oaknut.cli.reports import (
33
33
  address_cell,
34
34
  bytes_cell,
35
35
  control_pictures,
36
+ datestamp_cell,
37
+ filetype_cell,
36
38
  kv_table,
37
39
  size_cell,
38
40
  text_cell,
39
41
  )
40
42
 
41
- __version__ = "12.8.2"
43
+ __version__ = "12.10.0"
42
44
 
43
45
  __all__ = [
44
46
  "COMMAND_KIND",
@@ -51,6 +53,8 @@ __all__ = [
51
53
  "address_cell",
52
54
  "bytes_cell",
53
55
  "control_pictures",
56
+ "datestamp_cell",
57
+ "filetype_cell",
54
58
  "kv_table",
55
59
  "size_cell",
56
60
  "text_cell",
@@ -9,8 +9,11 @@ command can render output without depending on ``oaknut-disc``.
9
9
 
10
10
  from __future__ import annotations
11
11
 
12
+ from datetime import datetime, timedelta
13
+
12
14
  from asyoulikeit import ByAudience
13
15
  from oaknut.file.capacity import format_capacity
16
+ from oaknut.file.filetypes import filetype_name
14
17
 
15
18
  #: Acorn discs are addressed in 256-byte sectors throughout.
16
19
  SECTOR_SIZE = 256
@@ -71,14 +74,49 @@ def bytes_cell(num_bytes: int) -> ByAudience:
71
74
  return ByAudience(machine=num_bytes, human=format_capacity(num_bytes))
72
75
 
73
76
 
74
- def address_cell(address: int) -> ByAudience:
75
- """A 32-bit Acorn address as an audience-aware cell.
77
+ def address_cell(address: int, *, conceal: bool = False) -> ByAudience:
78
+ """An Acorn load/exec address as an audience-aware cell.
79
+
80
+ Humans read the ``0x``-prefixed hex form, trimmed of leading zeros
81
+ to a whole number of bytes (an even count of hex digits) with a
82
+ minimum of six — the width Acorn MOS uses for a DFS address, so a
83
+ table stays narrow without diverging from ``*EX`` / ``*INFO``. A
84
+ larger address (an ADFS 32-bit value) grows in whole bytes. Machine
85
+ formatters (JSON, TSV) get the raw integer, so a consumer never has
86
+ to parse a base back out of a string.
87
+
88
+ When *conceal* is set the human form is blank but the machine form
89
+ keeps the raw integer: a filetype-stamped file's load/exec hold an
90
+ encoded filetype and datestamp shown in their own columns, yet a
91
+ machine consumer still gets the faithful bytes.
92
+ """
93
+ if conceal:
94
+ return ByAudience(machine=address, human="")
95
+ digits = max(6, len(f"{address:X}"))
96
+ width = digits + (digits & 1) # round up to a whole number of bytes
97
+ return ByAudience(machine=address, human=f"0x{address:0{width}X}")
98
+
99
+
100
+ def filetype_cell(filetype: int) -> ByAudience:
101
+ """A RISC OS filetype as an audience-aware cell.
102
+
103
+ Humans read the registered name (or the ``&XXX`` hex form);
104
+ machine formatters get the raw 12-bit number.
105
+ """
106
+ return ByAudience(machine=filetype, human=filetype_name(filetype))
107
+
108
+
109
+ def datestamp_cell(when: datetime, resolution: timedelta) -> str:
110
+ """A datestamp as an ISO 8601 string at the filesystem's resolution.
76
111
 
77
- Humans read the conventional ``0x``-prefixed 8-hex-digit form;
78
- machine formatters (JSON, TSV) get the raw integer, so a consumer
79
- never has to parse a base back out of a string.
112
+ A filesystem that records only a calendar day (AFS) yields a bare
113
+ date; one that records time of day (ADFS, centiseconds) yields a
114
+ full naive-local timestamp. The value is the same for humans and
115
+ machines, so a single string serves every formatter.
80
116
  """
81
- return ByAudience(machine=address, human=f"0x{address:08X}")
117
+ if resolution >= timedelta(days=1):
118
+ return when.date().isoformat()
119
+ return when.isoformat(sep="T", timespec="milliseconds")
82
120
 
83
121
 
84
122
  def kv_table(title: str, pairs: list[tuple[str, str, object]]):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: oaknut-cli
3
- Version: 12.8.2
3
+ Version: 12.10.0
4
4
  Summary: Shared CLI toolkit for the oaknut family: the contributed-command axis and report-rendering helpers a disc command needs, below the filesystem packages.
5
5
  Author-email: Robert Smallshire <robert@smallshire.org.uk>
6
6
  License-Expression: MIT
@@ -23,7 +23,7 @@ Requires-Dist: oaknut-exception>=10.0
23
23
  Requires-Dist: oaknut-extension>=10.0
24
24
  Requires-Dist: oaknut-file>=10.0
25
25
  Requires-Dist: click>=8.1.7
26
- Requires-Dist: asyoulikeit>=1.3.0
26
+ Requires-Dist: asyoulikeit>=1.4.0
27
27
  Dynamic: license-file
28
28
 
29
29
  # oaknut-cli
@@ -2,4 +2,4 @@ oaknut-exception>=10.0
2
2
  oaknut-extension>=10.0
3
3
  oaknut-file>=10.0
4
4
  click>=8.1.7
5
- asyoulikeit>=1.3.0
5
+ asyoulikeit>=1.4.0
@@ -3,7 +3,7 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  from asyoulikeit import ByAudience
6
- from oaknut.cli import control_pictures, text_cell
6
+ from oaknut.cli import address_cell, control_pictures, text_cell
7
7
 
8
8
 
9
9
  class TestControlPictures:
@@ -36,3 +36,27 @@ class TestTextCell:
36
36
  # Machines keep the raw bytes; humans get control pictures.
37
37
  assert cell.machine == "\x0cPascal\n\r"
38
38
  assert cell.human == "␌Pascal␊␍"
39
+
40
+
41
+ class TestAddressCell:
42
+ def test_machine_keeps_raw_int(self):
43
+ cell = address_cell(0x1900)
44
+ assert isinstance(cell, ByAudience)
45
+ assert cell.machine == 0x1900
46
+
47
+ def test_human_keeps_0x_prefix(self):
48
+ assert address_cell(0x1900).human.startswith("0x")
49
+
50
+ def test_human_trimmed_to_minimum_six_hexits(self):
51
+ # Acorn MOS shows DFS addresses in six hex digits; leading zeros
52
+ # beyond that serve no purpose, so the narrowest form is six.
53
+ assert address_cell(0x1900).human == "0x001900"
54
+ assert address_cell(0x838F).human == "0x00838F"
55
+ assert address_cell(0x0).human == "0x000000"
56
+
57
+ def test_human_grows_in_whole_bytes(self):
58
+ # Above six hexits the width rounds up to an even number of hexits
59
+ # (a whole number of bytes), never an odd count.
60
+ assert address_cell(0xFFFFFF).human == "0xFFFFFF"
61
+ assert address_cell(0x1FF0000).human == "0x01FF0000"
62
+ assert address_cell(0xFFFF0E00).human == "0xFFFF0E00"
File without changes
File without changes
File without changes