1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
//! Format registry -- read/write/check a [`crate::document::Doc`] by format
//! *name* at runtime, plus register your own format plugins. Ported from
//! `~/dev/omnist/omnist/registry.py` (issue #31; see also the TypeScript
//! port's `registry.ts` for the same architecture-freedom call made there).
//!
//! Python's registry is a plain `dict[str, Format]` of arbitrary callables --
//! genuine runtime plugin registration, exercised by
//! `tests/test_canonical.py::TestRegistry`: a caller can `register_format`
//! an arbitrary `(name, read, write, check?)` tuple at runtime and every
//! `Doc`-level API that takes a format name (`from_format`/`to_format`/
//! `check_format`) transparently picks it up. A closed `enum` dispatch (the
//! `omnist-cli` `Fmt` enum's approach, or a `match` over the five builtins)
//! can't express "register a new format at runtime under an arbitrary
//! name," so this module reaches for the same dynamic-dispatch idiom Rust
//! uses in place of Python's first-class functions: `Arc<dyn Fn(...) + Send + Sync>` trait objects, keyed by name in an `IndexMap` behind an
//! `RwLock` inside a `OnceLock` (this crate's only piece of global mutable
//! state). `Arc` (not `Box`) so [`get_format`] can hand back an owned,
//! independently usable [`Format`] without holding the registry lock across
//! the caller's use of it -- mirroring Python's `_LOCK`-guarded dict lookup,
//! which also releases the lock before the caller touches the returned
//! `Format`.
//!
//! ## Uniform signatures across five differently-shaped codecs
//!
//! [`crate::formats::json::write_json`] takes an extra `indent: Option<
//! usize>` the other three format writers don't, and [`crate::oml`]'s
//! `read_oml`/`write_oml` operate on [`crate::document::RawNode`] rather
//! than `Doc` directly (see `oml.rs`'s own module doc on why). The
//! [`ReadFn`]/[`WriteFn`] the registry stores are `Doc`-in/`Doc`-out with no
//! format-specific options, matching Python's registry entries as actually
//! invoked from this port's zero-arg call sites (Python's `Doc.to_format`
//! forwards `**o` through, but nothing in the Python test suite or
//! `docs/api.md` exercises that with the builtins, so this port keeps the
//! simpler no-options signature and documents the gap here rather than
//! silently reproducing untested surface). The five builtins are registered
//! as thin wrapper closures around the existing per-format functions with
//! their default options (`indent: None`, `strict: false`, no report
//! requested for writers; `Doc::from_raw`/`to_raw` bridging OML's `RawNode`
//! shape) -- `get_format("json").read`/`.write` are *not* literally
//! `read_json`/`write_json` (Rust can't express "the same fn item" through
//! an `Arc<dyn Fn>` the way Python's `is` can point at the same function
//! object), but they call straight through with no other logic, matching
//! Python's actual invariant in spirit: no behavior is added or changed at
//! the registry boundary.
//!
//! ## OML's `check_oml`
//!
//! Rust's port had no `check_oml` before this issue -- OML is lossless for
//! every `Doc` (see `oml.rs`'s module doc: "no adjustment ever needed"), so
//! nothing needed to call it. Python's `check_oml` exists purely to satisfy
//! the registry `Format` tuple's fourth slot and always returns an empty
//! `WriteReport`; this issue adds the same trivial function to `oml.rs` for
//! the same reason (used only via the `"oml"` registry entry's `check`).
use ;
use IndexMap;
use crateDoc;
use crate;
use crateWriteReport;
/// `text -> Doc` reader callable.
pub type ReadFn = dyn Fn + Send + Sync;
/// `Doc -> text` writer callable.
pub type WriteFn = dyn Fn + Send + Sync;
/// `Doc -> WriteReport` check callable; simulates a write without producing
/// output.
pub type CheckFn = dyn Fn + Send + Sync;
/// A registered format: a name plus `read`/`write` callables and an
/// optional `check`. Mirrors Python's `Format` `NamedTuple` (`name, read,
/// write, check`); a plugin registered with [`Format::new`] alone has no
/// `check`, and [`crate::document::Doc::check_format`] errors cleanly (not
/// a panic) if invoked on it -- matching
/// `test_plugin_without_check_raises_on_check_format`.
/// Register (or replace) a format plugin, usable everywhere a format name is
/// accepted, including [`crate::document::Doc::from_format`]/`to_format`/
/// `check_format`.
/// The registered [`Format`] for `name`. An [`OmnistError::Format`] if
/// unknown, naming every currently-registered format name, sorted --
/// mirrors Python's `get_format`'s `f"unknown format {name!r}; registered:
/// {known}"` message. Unlike Python, there is no `"(none)"` fallback for an
/// empty registry: [`register_format`] only ever adds entries and the five
/// builtins always register on first access (see `builtins`), so the
/// registry can never actually be empty here -- an untestable dead branch
/// for that case was deliberately not carried over (playbook's "unreachable
/// dead code" gap classification), rather than kept under an unreachable
/// coverage-ignore.
/// The names of all registered formats, sorted.