-
Notifications
You must be signed in to change notification settings - Fork 0
Phase 36 catalog plan
Companion to the Phase 36 design block in Amiga port design. That section answers what and why; this file is the step-by-step ship plan — how to chunk the work into landable PRs.
Phase 36 wraps locale.library's catalog lookup so localized apps
can read their translated strings, plus surfaces the system's
preferred language:
from amiga import catalog
with catalog.open("MyApp.catalog", version=1) as cat:
print(cat.lookup(1, "Default English string"))
print(cat.lookup(2, "Cancel"))
print(catalog.language()) # 'english' / 'german' / 'français'Step 1: _catalog C module + Catalog type + language() + vamos smoke
↓
Step 2: docs flip + closing tests
| # | Step | Output | On-target smoke |
|---|---|---|---|
| 1 |
ports/amiga/modcatalog.c registering _catalog. open(name, version=0, language=None) → Catalog. Catalog.lookup(id, default) → str. Catalog.close() / __enter__ / __exit__ / __del__. Module-level language(). Wired through Makefile + frozen amiga.py. |
New C module + facade entry. | Under vamos: import works, alias amiga.catalog is _catalog, missing catalog → OSError, language() returns a non-empty string when vamos exposes a Locale. Under Amiberry: catalog.open("workbench.catalog") round-trip with lookup against a known string id. |
| 2 | Docs flip + comprehensive tests. |
docs/amiga.md Phase 36 → ✅; docs/amiga-testing.md gains a catalog subsection; tests/ports/amiga/test_catalog_smoke.py covers the surface. |
Amiberry verification of language() + lookup against a system catalog. |
Step 1 is ~150 LOC C; Step 2 is paperwork.
-
ports/amiga/modcatalog.c(~150 LOC). Module registered as_catalogviaMP_REGISTER_MODULE(MP_QSTR__catalog, ...), matching the_intuition/_asl/_iconconvention. - Module globals:
-
open(name, version=0, language=None)→Catalog. CallsOpenCatalogA(NULL, name, [OC_Version, OC_Language?, TAG_DONE]). RaisesOSError(ENOENT)on NULL return. -
language()→str.OpenLocale(NULL)→ first non-NULLloc_PrefLanguages[0]. Falls back to"english"if no preference, returns immediately iflocale.librarycan't open. -
Catalogtype symbol re-exported on the module.
-
-
CatalogMicroPython type:- Wraps
struct Catalog *cat. NULL after.close(). -
.lookup(id, default)—GetCatalogStr(cat, id, default). Returns the catalog string if present, the default otherwise. Matches the AmigaOS contract (no exception on miss). -
.close()—CloseCatalog. Idempotent. -
__del__/__exit__forward to.close(). -
__enter__returns self forwithuse.
- Wraps
-
ports/amiga/Makefile:SRC_C += modcatalog.cSRC_QSTR += modcatalog.c
-
ports/amiga/modules/amiga.py:import _catalog as catalog
-
LocaleBaseis opened lazily on first call (OpenLibrary ("locale.library", 38)). No explicit close —locale.libraryis a system-wide library that AmigaOS reaps at process exit; this matches the intuition / icon / asl pattern. -
OpenCatalogAis the tag-list form; build a small stack array:struct TagItem tags[4]; int n = 0; tags[n].ti_Tag = OC_Version; tags[n].ti_Data = version; n++; if (language != NULL) { tags[n].ti_Tag = OC_Language; tags[n].ti_Data = (ULONG)language; n++; } tags[n].ti_Tag = TAG_DONE;
-
GetCatalogStraccepts a NULL catalog and returns the default in that case; we don't need to guard. - The default-string lifetime contract:
GetCatalogStrreturns either the catalog's own string (held bylocale.library) or the caller's default pointer. The catalog-string case is safe to copy viamp_obj_new_strbecause the lifetime tracks the Catalog object the Python caller owns.
Vamos smoke (tests/ports/amiga/test_catalog_smoke.py):
import _catalog
import amiga
assert _catalog is amiga.catalog
assert callable(_catalog.open)
assert callable(_catalog.language)
# language() should always return a non-empty str.
lang = _catalog.language()
assert isinstance(lang, str) and len(lang) > 0
# A catalog that definitely doesn't exist -> OSError.
try:
_catalog.open("nonexistent_phase36.catalog")
except OSError:
pass
else:
raise AssertionError("expected OSError")
print("OK")Amiberry interactive verification:
>>> from amiga import catalog
>>> catalog.language()
'english'
>>> with catalog.open("workbench.catalog", version=44) as cat:
... print(cat.lookup(1, "fallback"))
...
<some workbench string>-
docs/amiga.mdPhase 36 status → ✅; section gains a full surface matrix (open/lookup/close/language). -
docs/amiga-testing.mdgains acatalogsubsection with Amiberry REPL examples coveringlanguage()andlookup. -
tests/ports/amiga/test_catalog_smoke.pyexpanded:- Module + alias +
Catalogtype re-export. - Missing-catalog OSError path.
-
language()returns str. - With-statement /
__enter__/__exit__round trip. - Closed-object
.lookupreturns the default (GetCatalogStrforwards a NULL catalog to the caller's default; no exception — matches the AmigaOS contract).
- Module + alias +
Step 1 added a built_in_language= kwarg beyond the original
scope so on-target round-trip testing works on a stock English
Workbench. AmigaOS OpenCatalog refuses to open when the
requested language matches the catalog's built-in language
(nothing to load); the kwarg surfaces OC_BuiltInLanguage so
callers can force a translation file lookup anyway.
-
locale.libraryversion. v38 (OS 2.1) is the baseline. Earlier Kickstarts don't ship it;open()raisesOSError(EIO)cleanly in that case. -
String encoding. Catalog strings are byte sequences in
whatever code set the catalog declares. We surface them as
Python
str(latin-1 trip-safe), matching the rest of the port's string handling. A future change could exposecat_CodeSetif anyone needs strict UTF-8 conversion. -
Vamos coverage. vamos doesn't ship a real
locale.library; the smoke test only confirms the surface doesn't crash, not actual catalog lookups. Amiberry covers the round trip.
- Writing catalogs (
flexcat-style compilation). That's a build-time tool, not a runtime API. - Conversion of locale-specific date / number / currency formats
— separate phase if needed; would wrap
FormatStringetc. - Multi-catalog merging / fallback chains —
OpenCatalogalready picks the right language automatically. - Exposing
Localedirectly (it's the system Locale, not a per-application object;language()covers the one field callers actually want).