Metadata-Version: 2.4
Name: oaknut-file
Version: 12.8.2
Summary: Acorn file metadata handling: INF sidecars, filename encoding, xattrs, and access flags
Author-email: Robert Smallshire <robert@smallshire.org.uk>
License-Expression: MIT
Project-URL: Homepage, https://github.com/rob-smallshire/oaknut/tree/master/packages/oaknut-file
Project-URL: Documentation, https://rob-smallshire.github.io/oaknut/disc/api/reference/file.html
Project-URL: Repository, https://github.com/rob-smallshire/oaknut
Project-URL: Issues, https://github.com/rob-smallshire/oaknut/issues
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: System :: Filesystems
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: oaknut-exception>=10.7
Requires-Dist: oaknut-codecs>=12.7
Requires-Dist: xattr>=1.0; sys_platform == "darwin"
Dynamic: license-file

# oaknut-file

[![PyPI version](https://img.shields.io/pypi/v/oaknut-file)](https://pypi.org/project/oaknut-file/)
[![CI](https://github.com/rob-smallshire/oaknut/actions/workflows/ci.yml/badge.svg)](https://github.com/rob-smallshire/oaknut/actions/workflows/ci.yml)
[![Python versions](https://img.shields.io/pypi/pyversions/oaknut-file)](https://pypi.org/project/oaknut-file/)
[![License: MIT](https://img.shields.io/pypi/l/oaknut-file)](https://github.com/rob-smallshire/oaknut/blob/master/packages/oaknut-file/LICENSE)

Acorn file metadata handling for the oaknut package family.

`oaknut-file` is the shared metadata layer used across the
[oaknut](https://github.com/rob-smallshire/oaknut) family — by
`oaknut-dfs`, `oaknut-adfs`, `oaknut-afs`, `oaknut-zip`, and the `disc`
CLI. It provides:

- The `Access` IntFlag enum for Acorn file attribute bytes
- The `AcornMeta` dataclass for load/exec addresses and attributes
- INF sidecar file parsing and formatting (traditional and PiEconetBridge variants)
- Filename metadata encoding (`,xxx`, `,llllllll,eeeeeeee`, `,load-exec`)
- Extended attribute read/write under `user.acorn.*` and `user.econet_*`

## Installation

Using [uv](https://docs.astral.sh/uv/) (recommended):

```bash
uv add oaknut-file
```

`pip install oaknut-file` also works.

The `xattr` package is automatically installed on macOS, where it is
required for extended attribute support. Linux uses `os.setxattr` from
the standard library and needs no additional dependency. Windows does
not support extended attributes; the xattr functions will raise on use.

## Quick start

### Access flags

The `Access` IntFlag enum represents the standard Acorn OSFILE attribute byte.
Bit values match the filing system API convention used by PiEconetBridge and
the `user.acorn.attr` extended attribute.

```python
from oaknut.file import Access

# Compose flags with bitwise OR
flags = Access.R | Access.W | Access.L
print(repr(flags))   # <Access.LWR: 11>
print(hex(flags))    # 0xb
```

| Flag | Value | Meaning |
|------|-------|---------|
| `Access.R`  | 0x01 | Owner read |
| `Access.W`  | 0x02 | Owner write |
| `Access.E`  | 0x04 | Execute only |
| `Access.L`  | 0x08 | Locked — prevents delete, overwrite, and rename on the disc filing systems |
| `Access.PR` | 0x10 | Public read |
| `Access.PW` | 0x20 | Public write |
| `Access.X`  | 0x40 | `*RUN`-only — may be `*RUN` but not `*LOAD`ed (CFS/ROMFS copy protection) |

`Access.X` (`*RUN`-only) is a **distinct axis** from `Access.L` (locked):
`X` is the cassette/ROM copy-protection bit and does not map to or from
the disc filing systems' delete-lock. `Access.WR` and `Access.LWR` are
provided as convenience composites for the common owner-read+write and
locked-owner-read+write cases.

### INF sidecar files

Two INF sidecar formats are supported. `parse_inf_line()` auto-detects which
format a line uses, while `format_trad_inf_line()` and `format_pieb_inf_line()`
let you choose explicitly when writing.

```python
from oaknut.file import (
    Access, format_trad_inf_line, format_pieb_inf_line, parse_inf_line,
)

# Traditional INF: filename load exec length [attr]
trad = format_trad_inf_line(
    filename="HELLO",
    load_address=0x1900,
    exec_address=0x8023,
    length=0x100,
    attr=int(Access.R | Access.W),
)
print(trad)
# HELLO       00001900 00008023 00000100 03

# PiEconetBridge INF: owner load exec perm
pieb = format_pieb_inf_line(
    load_address=0xFFFFDD00,
    exec_address=0xFFFFDD00,
    attr=int(Access.R | Access.W | Access.L | Access.PR),
)
print(pieb)
# 0 ffffdd00 ffffdd00 1b

# Auto-detect format on parse (returns (source_label, AcornMeta))
source, meta = parse_inf_line(trad)
print(meta.load_address, meta.exec_address, meta.access)
# 6400 32803 3

source, meta = parse_inf_line(pieb)
print(hex(meta.load_address), hex(meta.access), hex(meta.infer_filetype()))
# 0xffffdd00 0x1b 0xfdd
```

### Filename metadata encoding

Three filename suffix conventions are supported for embedding load/exec
addresses or RISC OS filetypes in host filenames.

```python
from oaknut.file import parse_encoded_filename

# RISC OS filetype suffix (3 hex digits)
clean, meta = parse_encoded_filename("PROG,ffb")
print(clean, meta.infer_filetype())
# ('PROG', filetype=0xFFB)

# MOS load-exec suffix (variable-width hex)
clean, meta = parse_encoded_filename("PROG,1900-801f")
print(clean, hex(meta.load_address), hex(meta.exec_address))
# PROG 0x1900 0x801f
```

### Filetype-stamped load addresses

When a load address has its top 12 bits set to `0xFFF`, the next 12 bits
encode a RISC OS filetype.

```python
from oaknut.file import AcornMeta

meta = AcornMeta(load_address=0xFFFF0E10)
print(meta.is_filetype_stamped, hex(meta.infer_filetype()))
# True 0xf0e
```

### Extended attributes

```python
from oaknut.file import write_acorn_xattrs, read_acorn_xattrs

# Write to user.acorn.* namespace
write_acorn_xattrs(
    "myfile.bin",
    load_address=0x1900,
    exec_address=0x8023,
    attr=0x03,
)

# Read back (falls through to user.econet_* if user.acorn.* is absent)
meta = read_acorn_xattrs("myfile.bin")
print(meta.load_address, meta.exec_address, meta.access)
```

## Public API

| Module | Exports |
|--------|---------|
| `oaknut.file.access` | `Access`, `format_access_hex`, `format_access_text` |
| `oaknut.file.meta` | `AcornMeta` |
| `oaknut.file.formats` | `MetaFormat`, `SOURCE_*` labels |
| `oaknut.file.inf` | `parse_inf_line`, `format_trad_inf_line`, `format_pieb_inf_line`, `read_inf_file`, `write_inf_file` |
| `oaknut.file.filename_encoding` | `parse_encoded_filename`, `build_filename_suffix`, `build_mos_filename_suffix` |
| `oaknut.file.xattr` | `read_acorn_xattrs`, `write_acorn_xattrs`, `read_econet_xattrs`, `write_econet_xattrs` |

All public symbols are also re-exported from the top-level `oaknut.file` package.

## License

MIT
