The extension axis: working with any filesystem¶
The concrete filesystem classes — DFS,
ADFS, AFS — are the natural
choice when your code already knows which format it is reading or
writing: they expose the richest, filesystem-specific surface.
When your code does not know in advance — a bulk-processing script
that should work on any image you throw at it, a tool that adapts to
whichever filesystem packages happen to be installed, or anything that
mirrors what disc identify and the rest of the CLI do — work
through the filesystem extension axis in oaknut.filesystem.
The CLI itself is built on it; nothing it does is private.
The four workflows below cover almost every case. Each recipe lives in
scripts/api-examples/ and runs as part of the test suite, so the
code in this page is the same code the project exercises.
Identify an image by content¶
identify() reports the format(s) of an image
by reading its bytes, not by trusting its extension. It returns a list
of Identification candidates ranked
best-first; each carries a Confidence, the
evidence behind the match, the partition it describes, and any nested
sub-partitions a host disc carries in contained.
def report_identification(filepath: Path) -> None:
"""Print the identification tree for *filepath*, best-first.
A bare summary line per candidate plus an indented line per
contained sub-partition. The recipe walks one level of nesting —
enough for ADFS + AFS — but ``contained`` is a tree, so a deeper
walk just recurses.
Args:
filepath: Any disc image. Returns silently with a "nothing
recognised" message if no installed filesystem matches.
"""
candidates = identify(filepath)
if not candidates:
print(f"{filepath.name}: nothing recognised")
return
for host in candidates:
print(f"{host.confidence.name:8s} {host.filesystem}")
for nested in host.contained:
print(
f" └─ {nested.confidence.name:8s} "
f"{nested.filesystem} ({nested.partition.selector})"
)
On a combined ADFS + AFS hard disc the printed tree looks like
STRONG adfs
└─ CERTAIN afs (afs)
For an image nothing recognises, identify returns an empty list.
Open any image generically¶
Once you know what an image is, open it through the coordinator and
work against the resulting Mount. The
coordinator never imports a concrete filesystem package; it discovers
what is installed through the oaknut.filesystem entry points and
hands you a uniform interface. The same loop walks a DFS floppy, an
ADFS hard disc, an AFS partition, or a ZIP archive.
Capability protocols —
AcornMetadata,
HierarchicalDirectories,
Bootable,
Titled,
FreeSpace, and others — let the same code
opt into extra behaviour where the mount supports it and skip it
cleanly where it does not. Test for the capability, not the
filesystem type:
def walk_any_disc(filepath: Path) -> None:
"""Walk any recognised disc one directory deep, with disc-level details.
The same function works against a DFS floppy, an ADFS hard disc,
an AFS partition, or a ZIP archive — the mount's core is uniform
and the capability checks adapt the rest.
Args:
filepath: Any disc image.
"""
candidates = identify(filepath)
if not candidates:
print(f"{filepath.name}: nothing recognised")
return
choice = candidates[0]
filesystem = create_filesystem(choice.filesystem)
with reader_for(filepath) as reader:
mount = filesystem.open(reader, choice.geometry)
if isinstance(mount, Titled):
print(f"Title: {mount.title}")
if isinstance(mount, Bootable):
print(f"Boot option: {mount.boot_option.name}")
def _show(entry, indent: int) -> None:
kind = "dir " if entry.is_dir else "file"
line = f"{' ' * (indent + 1)}{entry.name:14s} {kind} size={entry.length}"
if isinstance(mount, AcornMetadata) and not entry.is_dir:
meta = mount.acorn_meta(entry.path)
if meta.load_address is not None:
line += f" load={meta.load_address:#010x}"
print(line)
for top in mount.iter_entries(mount.path_root()):
_show(top, indent=0)
if top.is_dir:
for child in mount.iter_entries(top.path):
_show(child, indent=1)
The mount’s core methods —
iter_entries(),
stat(),
read_bytes(),
exists() — have the same shape on every
filesystem. To reach a contained partition (the AFS tail of an ADFS
disc, say), open the nested Identification rather than the host:
its partition records the region, and
region_reader() gives a windowed view to open
the contained filesystem on.
Discover what is installed¶
For tools that adapt to the environment — a chooser UI, a creation front-end, an installer probe — enumerate the filesystems through the coordinator. The set grows automatically as filesystem packages are installed; no hand-wired registry.
def list_installed() -> None:
"""Print every recognised filesystem with its one-line description."""
for name in sorted(filesystem_names()):
print(f" {name:14s} {describe_filesystem(name, single_line=True)}")
To examine one filesystem’s full description (the same text
disc describe-filesystem NAME prints), call
describe_filesystem() without
single_line.
When to use which surface¶
Goal |
Use |
|---|---|
“I’m writing a DFS-aware script.” |
|
“I’m writing an ADFS-aware script.” |
|
“I have an image; I’m not sure what.” |
|
“Open this and treat it uniformly.” |
|
“Adapt to what this filesystem can do.” |
|
“What’s installed in this environment?” |
The concrete-class API and the extension axis are not alternatives; they are layers. The CLI uses the extension axis to route to the right filesystem, then the concrete classes do the work underneath. Your code can sit at whichever layer fits its job.