oaknut-file 1.0.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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Robert Smallshire
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,189 @@
1
+ Metadata-Version: 2.4
2
+ Name: oaknut-file
3
+ Version: 1.0.0
4
+ Summary: Acorn file metadata handling: INF sidecars, filename encoding, xattrs, and access flags
5
+ Author-email: Robert Smallshire <robert@smallshire.org.uk>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/rob-smallshire/oaknut
8
+ Project-URL: Repository, https://github.com/rob-smallshire/oaknut
9
+ Project-URL: Issues, https://github.com/rob-smallshire/oaknut/issues
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Topic :: System :: Filesystems
17
+ Requires-Python: >=3.10
18
+ Description-Content-Type: text/markdown
19
+ License-File: LICENSE
20
+ Requires-Dist: xattr>=1.0; sys_platform == "darwin"
21
+ Dynamic: license-file
22
+
23
+ # oaknut-file
24
+
25
+ [![PyPI version](https://img.shields.io/pypi/v/oaknut-file.svg)](https://pypi.org/project/oaknut-file/)
26
+ [![CI](https://github.com/rob-smallshire/oaknut-file/actions/workflows/tests.yml/badge.svg)](https://github.com/rob-smallshire/oaknut-file/actions/workflows/tests.yml)
27
+ [![Python versions](https://img.shields.io/pypi/pyversions/oaknut-file.svg)](https://pypi.org/project/oaknut-file/)
28
+ [![License: MIT](https://img.shields.io/pypi/l/oaknut-file.svg)](https://github.com/rob-smallshire/oaknut-file/blob/master/LICENSE)
29
+
30
+ Acorn file metadata handling for the oaknut package family.
31
+
32
+ `oaknut-file` is the shared metadata layer used by
33
+ [`oaknut-zip`](https://github.com/rob-smallshire/oaknut-zip) and
34
+ [`oaknut-dfs`](https://github.com/rob-smallshire/oaknut-dfs). It provides:
35
+
36
+ - The `Access` IntFlag enum for Acorn file attribute bytes
37
+ - The `AcornMeta` dataclass for load/exec addresses and attributes
38
+ - INF sidecar file parsing and formatting (traditional and PiEconetBridge variants)
39
+ - Filename metadata encoding (`,xxx`, `,llllllll,eeeeeeee`, `,load-exec`)
40
+ - Extended attribute read/write under `user.acorn.*` and `user.econet_*`
41
+
42
+ ## Installation
43
+
44
+ Using [uv](https://docs.astral.sh/uv/) (recommended):
45
+
46
+ ```bash
47
+ uv add oaknut-file
48
+ ```
49
+
50
+ `pip install oaknut-file` also works.
51
+
52
+ The `xattr` package is automatically installed on macOS, where it is
53
+ required for extended attribute support. Linux uses `os.setxattr` from
54
+ the standard library and needs no additional dependency. Windows does
55
+ not support extended attributes; the xattr functions will raise on use.
56
+
57
+ ## Quick start
58
+
59
+ ### Access flags
60
+
61
+ The `Access` IntFlag enum represents the standard Acorn OSFILE attribute byte.
62
+ Bit values match the filing system API convention used by PiEconetBridge and
63
+ the `user.acorn.attr` extended attribute.
64
+
65
+ ```python
66
+ from oaknut.file import Access
67
+
68
+ # Compose flags with bitwise OR
69
+ flags = Access.R | Access.W | Access.L
70
+ print(repr(flags)) # <Access.R|W|L: 11>
71
+ print(hex(flags)) # 0x0B
72
+ ```
73
+
74
+ | Flag | Value | Meaning |
75
+ |------|-------|---------|
76
+ | `Access.R` | 0x01 | Owner read |
77
+ | `Access.W` | 0x02 | Owner write |
78
+ | `Access.E` | 0x04 | Execute only |
79
+ | `Access.L` | 0x08 | Locked |
80
+ | `Access.PR` | 0x10 | Public read |
81
+ | `Access.PW` | 0x20 | Public write |
82
+
83
+ ### INF sidecar files
84
+
85
+ Two INF sidecar formats are supported. `parse_inf_line()` auto-detects which
86
+ format a line uses, while `format_trad_inf_line()` and `format_pieb_inf_line()`
87
+ let you choose explicitly when writing.
88
+
89
+ ```python
90
+ from oaknut.file import (
91
+ Access, format_trad_inf_line, format_pieb_inf_line, parse_inf_line,
92
+ )
93
+
94
+ # Traditional INF: filename load exec length [attr]
95
+ trad = format_trad_inf_line(
96
+ filename="HELLO",
97
+ load_addr=0x1900,
98
+ exec_addr=0x8023,
99
+ length=0x100,
100
+ attr=int(Access.R | Access.W),
101
+ )
102
+ print(trad)
103
+ # HELLO 00001900 00008023 00000100 03
104
+
105
+ # PiEconetBridge INF: owner load exec perm
106
+ pieb = format_pieb_inf_line(
107
+ load_addr=0xFFFFDD00,
108
+ exec_addr=0xFFFFDD00,
109
+ attr=int(Access.R | Access.W | Access.L | Access.PR),
110
+ )
111
+ print(pieb)
112
+ # 0 ffffdd00 ffffdd00 1b
113
+
114
+ # Auto-detect format on parse
115
+ source, meta = parse_inf_line(trad)
116
+ print(meta)
117
+ # AcornMeta(load_addr=0x1900, exec_addr=0x8023, attr=0x03)
118
+
119
+ source, meta = parse_inf_line(pieb)
120
+ print(meta)
121
+ # AcornMeta(load_addr=0xFFFFDD00, exec_addr=0xFFFFDD00, attr=0x1B, filetype=0xFDD)
122
+ ```
123
+
124
+ ### Filename metadata encoding
125
+
126
+ Three filename suffix conventions are supported for embedding load/exec
127
+ addresses or RISC OS filetypes in host filenames.
128
+
129
+ ```python
130
+ from oaknut.file import parse_encoded_filename
131
+
132
+ # RISC OS filetype suffix (3 hex digits)
133
+ clean, meta = parse_encoded_filename("PROG,ffb")
134
+ print(clean, meta.infer_filetype())
135
+ # ('PROG', filetype=0xFFB)
136
+
137
+ # MOS load-exec suffix (variable-width hex)
138
+ clean, meta = parse_encoded_filename("PROG,1900-801f")
139
+ print(clean, meta.load_addr, meta.exec_addr)
140
+ # ('PROG', load_addr=0x1900, exec_addr=0x801F)
141
+ ```
142
+
143
+ ### Filetype-stamped load addresses
144
+
145
+ When a load address has its top 12 bits set to `0xFFF`, the next 12 bits
146
+ encode a RISC OS filetype.
147
+
148
+ ```python
149
+ from oaknut.file import AcornMeta
150
+
151
+ meta = AcornMeta(load_addr=0xFFFF0E10)
152
+ print(meta.is_filetype_stamped, hex(meta.infer_filetype()))
153
+ # is_filetype_stamped=True, infer_filetype()=0xF0E
154
+ ```
155
+
156
+ ### Extended attributes
157
+
158
+ ```python
159
+ from oaknut.file import write_acorn_xattrs, read_acorn_xattrs
160
+
161
+ # Write to user.acorn.* namespace
162
+ write_acorn_xattrs(
163
+ "myfile.bin",
164
+ load_addr=0x1900,
165
+ exec_addr=0x8023,
166
+ attr=0x03,
167
+ )
168
+
169
+ # Read back (falls through to user.econet_* if user.acorn.* is absent)
170
+ meta = read_acorn_xattrs("myfile.bin")
171
+ print(meta.load_addr, meta.exec_addr, meta.attr)
172
+ ```
173
+
174
+ ## Public API
175
+
176
+ | Module | Exports |
177
+ |--------|---------|
178
+ | `oaknut.file.access` | `Access`, `format_access_hex`, `format_access_text` |
179
+ | `oaknut.file.meta` | `AcornMeta` |
180
+ | `oaknut.file.formats` | `MetaFormat`, `SOURCE_*` labels |
181
+ | `oaknut.file.inf` | `parse_inf_line`, `format_trad_inf_line`, `format_pieb_inf_line`, `read_inf_file`, `write_inf_file` |
182
+ | `oaknut.file.filename_encoding` | `parse_encoded_filename`, `build_filename_suffix`, `build_mos_filename_suffix` |
183
+ | `oaknut.file.xattr` | `read_acorn_xattrs`, `write_acorn_xattrs`, `read_econet_xattrs`, `write_econet_xattrs` |
184
+
185
+ All public symbols are also re-exported from the top-level `oaknut.file` package.
186
+
187
+ ## License
188
+
189
+ MIT
@@ -0,0 +1,167 @@
1
+ # oaknut-file
2
+
3
+ [![PyPI version](https://img.shields.io/pypi/v/oaknut-file.svg)](https://pypi.org/project/oaknut-file/)
4
+ [![CI](https://github.com/rob-smallshire/oaknut-file/actions/workflows/tests.yml/badge.svg)](https://github.com/rob-smallshire/oaknut-file/actions/workflows/tests.yml)
5
+ [![Python versions](https://img.shields.io/pypi/pyversions/oaknut-file.svg)](https://pypi.org/project/oaknut-file/)
6
+ [![License: MIT](https://img.shields.io/pypi/l/oaknut-file.svg)](https://github.com/rob-smallshire/oaknut-file/blob/master/LICENSE)
7
+
8
+ Acorn file metadata handling for the oaknut package family.
9
+
10
+ `oaknut-file` is the shared metadata layer used by
11
+ [`oaknut-zip`](https://github.com/rob-smallshire/oaknut-zip) and
12
+ [`oaknut-dfs`](https://github.com/rob-smallshire/oaknut-dfs). It provides:
13
+
14
+ - The `Access` IntFlag enum for Acorn file attribute bytes
15
+ - The `AcornMeta` dataclass for load/exec addresses and attributes
16
+ - INF sidecar file parsing and formatting (traditional and PiEconetBridge variants)
17
+ - Filename metadata encoding (`,xxx`, `,llllllll,eeeeeeee`, `,load-exec`)
18
+ - Extended attribute read/write under `user.acorn.*` and `user.econet_*`
19
+
20
+ ## Installation
21
+
22
+ Using [uv](https://docs.astral.sh/uv/) (recommended):
23
+
24
+ ```bash
25
+ uv add oaknut-file
26
+ ```
27
+
28
+ `pip install oaknut-file` also works.
29
+
30
+ The `xattr` package is automatically installed on macOS, where it is
31
+ required for extended attribute support. Linux uses `os.setxattr` from
32
+ the standard library and needs no additional dependency. Windows does
33
+ not support extended attributes; the xattr functions will raise on use.
34
+
35
+ ## Quick start
36
+
37
+ ### Access flags
38
+
39
+ The `Access` IntFlag enum represents the standard Acorn OSFILE attribute byte.
40
+ Bit values match the filing system API convention used by PiEconetBridge and
41
+ the `user.acorn.attr` extended attribute.
42
+
43
+ ```python
44
+ from oaknut.file import Access
45
+
46
+ # Compose flags with bitwise OR
47
+ flags = Access.R | Access.W | Access.L
48
+ print(repr(flags)) # <Access.R|W|L: 11>
49
+ print(hex(flags)) # 0x0B
50
+ ```
51
+
52
+ | Flag | Value | Meaning |
53
+ |------|-------|---------|
54
+ | `Access.R` | 0x01 | Owner read |
55
+ | `Access.W` | 0x02 | Owner write |
56
+ | `Access.E` | 0x04 | Execute only |
57
+ | `Access.L` | 0x08 | Locked |
58
+ | `Access.PR` | 0x10 | Public read |
59
+ | `Access.PW` | 0x20 | Public write |
60
+
61
+ ### INF sidecar files
62
+
63
+ Two INF sidecar formats are supported. `parse_inf_line()` auto-detects which
64
+ format a line uses, while `format_trad_inf_line()` and `format_pieb_inf_line()`
65
+ let you choose explicitly when writing.
66
+
67
+ ```python
68
+ from oaknut.file import (
69
+ Access, format_trad_inf_line, format_pieb_inf_line, parse_inf_line,
70
+ )
71
+
72
+ # Traditional INF: filename load exec length [attr]
73
+ trad = format_trad_inf_line(
74
+ filename="HELLO",
75
+ load_addr=0x1900,
76
+ exec_addr=0x8023,
77
+ length=0x100,
78
+ attr=int(Access.R | Access.W),
79
+ )
80
+ print(trad)
81
+ # HELLO 00001900 00008023 00000100 03
82
+
83
+ # PiEconetBridge INF: owner load exec perm
84
+ pieb = format_pieb_inf_line(
85
+ load_addr=0xFFFFDD00,
86
+ exec_addr=0xFFFFDD00,
87
+ attr=int(Access.R | Access.W | Access.L | Access.PR),
88
+ )
89
+ print(pieb)
90
+ # 0 ffffdd00 ffffdd00 1b
91
+
92
+ # Auto-detect format on parse
93
+ source, meta = parse_inf_line(trad)
94
+ print(meta)
95
+ # AcornMeta(load_addr=0x1900, exec_addr=0x8023, attr=0x03)
96
+
97
+ source, meta = parse_inf_line(pieb)
98
+ print(meta)
99
+ # AcornMeta(load_addr=0xFFFFDD00, exec_addr=0xFFFFDD00, attr=0x1B, filetype=0xFDD)
100
+ ```
101
+
102
+ ### Filename metadata encoding
103
+
104
+ Three filename suffix conventions are supported for embedding load/exec
105
+ addresses or RISC OS filetypes in host filenames.
106
+
107
+ ```python
108
+ from oaknut.file import parse_encoded_filename
109
+
110
+ # RISC OS filetype suffix (3 hex digits)
111
+ clean, meta = parse_encoded_filename("PROG,ffb")
112
+ print(clean, meta.infer_filetype())
113
+ # ('PROG', filetype=0xFFB)
114
+
115
+ # MOS load-exec suffix (variable-width hex)
116
+ clean, meta = parse_encoded_filename("PROG,1900-801f")
117
+ print(clean, meta.load_addr, meta.exec_addr)
118
+ # ('PROG', load_addr=0x1900, exec_addr=0x801F)
119
+ ```
120
+
121
+ ### Filetype-stamped load addresses
122
+
123
+ When a load address has its top 12 bits set to `0xFFF`, the next 12 bits
124
+ encode a RISC OS filetype.
125
+
126
+ ```python
127
+ from oaknut.file import AcornMeta
128
+
129
+ meta = AcornMeta(load_addr=0xFFFF0E10)
130
+ print(meta.is_filetype_stamped, hex(meta.infer_filetype()))
131
+ # is_filetype_stamped=True, infer_filetype()=0xF0E
132
+ ```
133
+
134
+ ### Extended attributes
135
+
136
+ ```python
137
+ from oaknut.file import write_acorn_xattrs, read_acorn_xattrs
138
+
139
+ # Write to user.acorn.* namespace
140
+ write_acorn_xattrs(
141
+ "myfile.bin",
142
+ load_addr=0x1900,
143
+ exec_addr=0x8023,
144
+ attr=0x03,
145
+ )
146
+
147
+ # Read back (falls through to user.econet_* if user.acorn.* is absent)
148
+ meta = read_acorn_xattrs("myfile.bin")
149
+ print(meta.load_addr, meta.exec_addr, meta.attr)
150
+ ```
151
+
152
+ ## Public API
153
+
154
+ | Module | Exports |
155
+ |--------|---------|
156
+ | `oaknut.file.access` | `Access`, `format_access_hex`, `format_access_text` |
157
+ | `oaknut.file.meta` | `AcornMeta` |
158
+ | `oaknut.file.formats` | `MetaFormat`, `SOURCE_*` labels |
159
+ | `oaknut.file.inf` | `parse_inf_line`, `format_trad_inf_line`, `format_pieb_inf_line`, `read_inf_file`, `write_inf_file` |
160
+ | `oaknut.file.filename_encoding` | `parse_encoded_filename`, `build_filename_suffix`, `build_mos_filename_suffix` |
161
+ | `oaknut.file.xattr` | `read_acorn_xattrs`, `write_acorn_xattrs`, `read_econet_xattrs`, `write_econet_xattrs` |
162
+
163
+ All public symbols are also re-exported from the top-level `oaknut.file` package.
164
+
165
+ ## License
166
+
167
+ MIT
@@ -0,0 +1,60 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "oaknut-file"
7
+ dynamic = ["version"]
8
+ authors = [{ name = "Robert Smallshire", email = "robert@smallshire.org.uk" }]
9
+ description = "Acorn file metadata handling: INF sidecars, filename encoding, xattrs, and access flags"
10
+ readme = "README.md"
11
+ license = "MIT"
12
+ license-files = ["LICENSE"]
13
+ requires-python = ">=3.10"
14
+ classifiers = [
15
+ "Development Status :: 3 - Alpha",
16
+ "Intended Audience :: Developers",
17
+ "Programming Language :: Python :: 3.10",
18
+ "Programming Language :: Python :: 3.11",
19
+ "Programming Language :: Python :: 3.12",
20
+ "Programming Language :: Python :: 3.13",
21
+ "Topic :: System :: Filesystems",
22
+ ]
23
+ dependencies = [
24
+ "xattr>=1.0; sys_platform == 'darwin'",
25
+ ]
26
+
27
+ [project.urls]
28
+ Homepage = "https://github.com/rob-smallshire/oaknut"
29
+ Repository = "https://github.com/rob-smallshire/oaknut"
30
+ Issues = "https://github.com/rob-smallshire/oaknut/issues"
31
+
32
+ [dependency-groups]
33
+ test = [
34
+ "pytest>=8.0",
35
+ "jinja2>=3.0",
36
+ ]
37
+ dev = [
38
+ "bump-my-version>=0.28.0",
39
+ "pre-commit>=3.0",
40
+ {include-group = "test"},
41
+ ]
42
+
43
+ [tool.setuptools.dynamic]
44
+ version = { attr = "oaknut.file.__version__" }
45
+
46
+ [tool.setuptools.packages.find]
47
+ where = ["src"]
48
+
49
+ [tool.bumpversion]
50
+ current_version = "1.0.0"
51
+ parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)"
52
+ serialize = ["{major}.{minor}.{patch}"]
53
+ tag = true
54
+ commit = true
55
+ message = "Bump version: {current_version} → {new_version}"
56
+ tag_name = "oaknut-file-v{new_version}"
57
+ tag_message = "Bump version: {current_version} → {new_version}"
58
+ files = [
59
+ { filename = "src/oaknut/file/__init__.py" },
60
+ ]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,62 @@
1
+ """Acorn file metadata handling.
2
+
3
+ Shared metadata layer for the oaknut package family: INF sidecar
4
+ parsing/formatting, filename encoding schemes, extended attributes,
5
+ and access flag management.
6
+ """
7
+
8
+ __version__ = "1.0.0"
9
+
10
+ from oaknut.file.access import Access, format_access_hex, format_access_text
11
+ from oaknut.file.filename_encoding import (
12
+ build_filename_suffix,
13
+ build_mos_filename_suffix,
14
+ parse_encoded_filename,
15
+ )
16
+ from oaknut.file.formats import (
17
+ SOURCE_DIR,
18
+ SOURCE_FILENAME,
19
+ SOURCE_INF_PIEB,
20
+ SOURCE_INF_TRAD,
21
+ SOURCE_SPARKFS,
22
+ MetaFormat,
23
+ )
24
+ from oaknut.file.inf import (
25
+ format_pieb_inf_line,
26
+ format_trad_inf_line,
27
+ parse_inf_line,
28
+ read_inf_file,
29
+ write_inf_file,
30
+ )
31
+ from oaknut.file.meta import AcornMeta
32
+ from oaknut.file.xattr import (
33
+ read_acorn_xattrs,
34
+ read_econet_xattrs,
35
+ write_acorn_xattrs,
36
+ write_econet_xattrs,
37
+ )
38
+
39
+ __all__ = [
40
+ "Access",
41
+ "AcornMeta",
42
+ "MetaFormat",
43
+ "SOURCE_DIR",
44
+ "SOURCE_FILENAME",
45
+ "SOURCE_INF_PIEB",
46
+ "SOURCE_INF_TRAD",
47
+ "SOURCE_SPARKFS",
48
+ "build_filename_suffix",
49
+ "build_mos_filename_suffix",
50
+ "format_access_hex",
51
+ "format_access_text",
52
+ "format_pieb_inf_line",
53
+ "format_trad_inf_line",
54
+ "parse_encoded_filename",
55
+ "parse_inf_line",
56
+ "read_acorn_xattrs",
57
+ "read_econet_xattrs",
58
+ "read_inf_file",
59
+ "write_acorn_xattrs",
60
+ "write_econet_xattrs",
61
+ "write_inf_file",
62
+ ]
@@ -0,0 +1,68 @@
1
+ """Acorn file access attributes.
2
+
3
+ The ``Access`` IntFlag enum represents the standard Acorn OSFILE
4
+ attribute byte. Bit values match the filing system API convention,
5
+ ensuring compatibility with PiEconetBridge ``perm`` and the
6
+ ``user.acorn.attr`` extended attribute.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from enum import IntFlag
12
+
13
+
14
+ class Access(IntFlag):
15
+ """Acorn file access attributes.
16
+
17
+ Composable with ``|``::
18
+
19
+ Access.R | Access.W | Access.L
20
+ Access.R | Access.W | Access.PR # with public read
21
+
22
+ The integer value of a combination is the standard Acorn
23
+ attribute byte, suitable for storage in xattrs or INF files::
24
+
25
+ int(Access.R | Access.W) # 0x03
26
+ """
27
+
28
+ R = 0x01 # Owner read
29
+ W = 0x02 # Owner write
30
+ E = 0x04 # Execute only
31
+ L = 0x08 # Locked (prevents delete, overwrite, rename)
32
+ PR = 0x10 # Public read
33
+ PW = 0x20 # Public write
34
+
35
+
36
+ def format_access_hex(attr: int | None) -> str:
37
+ """Format an attribute byte as a two-digit uppercase hex string.
38
+
39
+ Returns empty string for None.
40
+ """
41
+ if attr is None:
42
+ return ""
43
+ return f"{attr:02X}"
44
+
45
+
46
+ def format_access_text(attr: int | None) -> str:
47
+ """Format attributes as a human-readable access string.
48
+
49
+ Returns ``"owner/public"`` form, e.g. ``"LWR/R"``.
50
+ """
51
+ if attr is None:
52
+ return "/"
53
+
54
+ owner = ""
55
+ if attr & Access.L:
56
+ owner += "L"
57
+ if attr & Access.W:
58
+ owner += "W"
59
+ if attr & Access.R:
60
+ owner += "R"
61
+
62
+ public = ""
63
+ if attr & Access.PW:
64
+ public += "W"
65
+ if attr & Access.PR:
66
+ public += "R"
67
+
68
+ return f"{owner}/{public}"
@@ -0,0 +1,73 @@
1
+ """Filename metadata encoding schemes.
2
+
3
+ Three conventions for encoding Acorn file metadata in host filenames:
4
+
5
+ - **RISC OS filetype**: ``filename,xxx`` (3-digit hex filetype)
6
+ - **RISC OS load/exec**: ``filename,llllllll,eeeeeeee`` (8+8 digit hex)
7
+ - **MOS load/exec**: ``filename,load-exec`` (variable-width hex)
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import re
13
+
14
+ from oaknut.file.meta import AcornMeta
15
+
16
+ # Regex patterns for each encoding scheme
17
+ SUFFIX_FILETYPE_RE = re.compile(r"^(.*),([0-9a-fA-F]{3})$")
18
+ SUFFIX_LOADEXEC_RE = re.compile(r"^(.*),([0-9a-fA-F]{8}),([0-9a-fA-F]{8})$")
19
+ SUFFIX_MOS_LOADEXEC_RE = re.compile(r"^(.*),([0-9a-fA-F]{1,8})-([0-9a-fA-F]{1,8})$")
20
+
21
+
22
+ def parse_encoded_filename(filename: str) -> tuple[str, AcornMeta | None]:
23
+ """Strip metadata suffix from a filename.
24
+
25
+ Tries each encoding scheme in order: RISC OS load/exec,
26
+ MOS load/exec, filetype. Returns ``(clean_filename, metadata)``
27
+ where *metadata* is ``None`` if no encoding was found.
28
+ """
29
+ # Try RISC OS load/exec first (most specific: 8+8 digits)
30
+ m = SUFFIX_LOADEXEC_RE.match(filename)
31
+ if m:
32
+ load_addr = int(m.group(2), 16)
33
+ exec_addr = int(m.group(3), 16)
34
+ return m.group(1), AcornMeta(load_addr=load_addr, exec_addr=exec_addr)
35
+
36
+ # Try MOS load/exec (variable-width with hyphen)
37
+ m = SUFFIX_MOS_LOADEXEC_RE.match(filename)
38
+ if m:
39
+ load_addr = int(m.group(2), 16)
40
+ exec_addr = int(m.group(3), 16)
41
+ return m.group(1), AcornMeta(load_addr=load_addr, exec_addr=exec_addr)
42
+
43
+ # Try filetype (3-digit hex)
44
+ m = SUFFIX_FILETYPE_RE.match(filename)
45
+ if m:
46
+ filetype = int(m.group(2), 16)
47
+ # Synthesise a RISC OS load address from the filetype
48
+ load_addr = 0xFFF00000 | (filetype << 8)
49
+ return m.group(1), AcornMeta(
50
+ load_addr=load_addr, exec_addr=0, filetype=filetype,
51
+ )
52
+
53
+ return filename, None
54
+
55
+
56
+ def build_filename_suffix(meta: AcornMeta) -> str:
57
+ """Build a RISC OS filename encoding suffix.
58
+
59
+ Returns ``,xxx`` for filetype-stamped files, or
60
+ ``,llllllll,eeeeeeee`` for literal load/exec addresses.
61
+ """
62
+ if meta.is_filetype_stamped:
63
+ ft = meta.infer_filetype()
64
+ return f",{ft:03x}"
65
+ return f",{meta.load_addr:08x},{meta.exec_addr:08x}"
66
+
67
+
68
+ def build_mos_filename_suffix(meta: AcornMeta) -> str:
69
+ """Build a MOS filename encoding suffix.
70
+
71
+ Returns ``,load-exec`` with variable-width lowercase hex.
72
+ """
73
+ return f",{meta.load_addr:x}-{meta.exec_addr:x}"
@@ -0,0 +1,28 @@
1
+ """Metadata format and source labels.
2
+
3
+ ``MetaFormat`` enumerates the output formats for writing file metadata
4
+ to the host filesystem. The ``SOURCE_*`` constants label where
5
+ metadata was obtained from.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from enum import Enum
11
+
12
+
13
+ class MetaFormat(str, Enum):
14
+ """Supported metadata output formats."""
15
+
16
+ INF_TRAD = "inf-trad"
17
+ INF_PIEB = "inf-pieb"
18
+ XATTR_ACORN = "xattr-acorn"
19
+ XATTR_PIEB = "xattr-pieb"
20
+ FILENAME_RISCOS = "filename-riscos"
21
+ FILENAME_MOS = "filename-mos"
22
+
23
+
24
+ SOURCE_SPARKFS = "sparkfs"
25
+ SOURCE_INF_TRAD = "inf-trad"
26
+ SOURCE_INF_PIEB = "inf-pieb"
27
+ SOURCE_FILENAME = "filename"
28
+ SOURCE_DIR = "dir"