1pub(crate) mod compression;
91pub mod error;
92pub mod formats;
93pub(crate) mod io;
94pub mod record;
95pub mod types;
96
97pub use crate::error::DmapError;
98pub use crate::formats::dmap::DmapRecord;
99pub use crate::formats::fitacf::FitacfRecord;
100pub use crate::formats::grid::GridRecord;
101pub use crate::formats::iqdat::IqdatRecord;
102pub use crate::formats::map::MapRecord;
103pub use crate::formats::rawacf::RawacfRecord;
104pub use crate::formats::snd::SndRecord;
105pub use crate::record::Record;
106use crate::types::DmapField;
107use indexmap::IndexMap;
108use paste::paste;
109use pyo3::prelude::*;
110use pyo3::types::PyBytes;
111use std::path::{Path, PathBuf};
112
113macro_rules! write_rust {
115 ($type:ident) => {
116 paste! {
117 #[doc = "Attempts to convert `recs` to `" $type:camel Record "` then append to `outfile`."]
118 #[doc = ""]
119 #[doc = "# Errors"]
120 #[doc = "if any of the `IndexMap`s are unable to be interpreted as a `" $type:camel Record "`, or there is an issue writing to file."]
121 pub fn [< try_write_ $type >]<P: AsRef<Path>>(
122 recs: Vec<IndexMap<String, DmapField>>,
123 outfile: P,
124 ) -> Result<(), DmapError> {
125 let bytes = [< $type:camel Record >]::try_into_bytes(recs)?;
126 crate::io::bytes_to_file(bytes, outfile).map_err(DmapError::from)
127 }
128 }
129 }
130}
131
132write_rust!(iqdat);
133write_rust!(rawacf);
134write_rust!(fitacf);
135write_rust!(grid);
136write_rust!(map);
137write_rust!(snd);
138write_rust!(dmap);
139
140macro_rules! read_py {
145 ($name:ident, $py_name:literal, $lax_name:literal, $bytes_name:literal, $lax_bytes_name:literal, $sniff_name:literal) => {
146 paste! {
147 #[doc = "Reads a `" $name:upper "` file, returning a list of dictionaries containing the fields." ]
148 #[pyfunction]
149 #[pyo3(name = $py_name)]
150 #[pyo3(text_signature = "(infile: str, /)")]
151 fn [< read_ $name _py >](infile: PathBuf) -> PyResult<Vec<IndexMap<String, DmapField>>> {
152 Ok([< $name:camel Record >]::read_file(&infile)
153 .map_err(PyErr::from)?
154 .into_iter()
155 .map(|rec| rec.inner())
156 .collect()
157 )
158 }
159
160 #[doc = "Reads a `" $name:upper "` file, returning a tuple of" ]
161 #[doc = "(list of dictionaries containing the fields, byte where first corrupted record starts). "]
162 #[pyfunction]
163 #[pyo3(name = $lax_name)]
164 #[pyo3(text_signature = "(infile: str, /)")]
165 fn [< read_ $name _lax_py >](
166 infile: PathBuf,
167 ) -> PyResult<(Vec<IndexMap<String, DmapField>>, Option<usize>)> {
168 let result = [< $name:camel Record >]::read_file_lax(&infile).map_err(PyErr::from)?;
169 Ok((
170 result.0.into_iter().map(|rec| rec.inner()).collect(),
171 result.1,
172 ))
173 }
174
175 #[doc = "Read in `" $name:upper "` records from bytes, returning `List[Dict]` of the records." ]
176 #[pyfunction]
177 #[pyo3(name = $bytes_name)]
178 #[pyo3(text_signature = "(buf: bytes, /)")]
179 fn [< read_ $name _bytes_py >](bytes: &[u8]) -> PyResult<Vec<IndexMap<String, DmapField>>> {
180 Ok([< $name:camel Record >]::read_records(bytes)?
181 .into_iter()
182 .map(|rec| rec.inner())
183 .collect()
184 )
185 }
186
187 #[doc = "Reads a `" $name:upper "` file, returning a tuple of" ]
188 #[doc = "(list of dictionaries containing the fields, byte where first corrupted record starts). "]
189 #[pyfunction]
190 #[pyo3(name = $lax_bytes_name)]
191 #[pyo3(text_signature = "(buf: bytes, /)")]
192 fn [< read_ $name _bytes_lax_py >](
193 bytes: &[u8],
194 ) -> PyResult<(Vec<IndexMap<String, DmapField>>, Option<usize>)> {
195 let result = [< $name:camel Record >]::read_records_lax(bytes).map_err(PyErr::from)?;
196 Ok((
197 result.0.into_iter().map(|rec| rec.inner()).collect(),
198 result.1,
199 ))
200 }
201
202 #[doc = "Reads a `" $name:upper "` file, returning the first record." ]
203 #[pyfunction]
204 #[pyo3(name = $sniff_name)]
205 #[pyo3(text_signature = "(infile: str, /)")]
206 fn [< sniff_ $name _py >](infile: PathBuf) -> PyResult<IndexMap<String, DmapField>> {
207 Ok([< $name:camel Record >]::sniff_file(&infile)
208 .map_err(PyErr::from)?
209 .inner()
210 )
211 }
212 }
213 }
214}
215
216read_py!(
217 iqdat,
218 "read_iqdat",
219 "read_iqdat_lax",
220 "read_iqdat_bytes",
221 "read_iqdat_bytes_lax",
222 "sniff_iqdat"
223);
224read_py!(
225 rawacf,
226 "read_rawacf",
227 "read_rawacf_lax",
228 "read_rawacf_bytes",
229 "read_rawacf_bytes_lax",
230 "sniff_rawacf"
231);
232read_py!(
233 fitacf,
234 "read_fitacf",
235 "read_fitacf_lax",
236 "read_fitacf_bytes",
237 "read_fitacf_bytes_lax",
238 "sniff_fitacf"
239);
240read_py!(
241 grid,
242 "read_grid",
243 "read_grid_lax",
244 "read_grid_bytes",
245 "read_grid_bytes_lax",
246 "sniff_grid"
247);
248read_py!(
249 map,
250 "read_map",
251 "read_map_lax",
252 "read_map_bytes",
253 "read_map_bytes_lax",
254 "sniff_map"
255);
256read_py!(
257 snd,
258 "read_snd",
259 "read_snd_lax",
260 "read_snd_bytes",
261 "read_snd_bytes_lax",
262 "sniff_snd"
263);
264read_py!(
265 dmap,
266 "read_dmap",
267 "read_dmap_lax",
268 "read_dmap_bytes",
269 "read_dmap_bytes_lax",
270 "sniff_dmap"
271);
272
273#[pyfunction]
279#[pyo3(name = "write_dmap")]
280#[pyo3(text_signature = "(recs: list[dict], outfile: str, /)")]
281fn write_dmap_py(recs: Vec<IndexMap<String, DmapField>>, outfile: PathBuf) -> PyResult<()> {
282 try_write_dmap(recs, &outfile).map_err(PyErr::from)
283}
284
285#[pyfunction]
292#[pyo3(name = "write_dmap_bytes")]
293#[pyo3(text_signature = "(recs: list[dict], /)")]
294fn write_dmap_bytes_py(py: Python, recs: Vec<IndexMap<String, DmapField>>) -> PyResult<Py<PyAny>> {
295 let bytes = DmapRecord::try_into_bytes(recs).map_err(PyErr::from)?;
296 Ok(PyBytes::new(py, &bytes).into())
297}
298
299macro_rules! write_py {
301 ($name:ident, $fn_name:literal, $bytes_name:literal) => {
302 paste! {
303 #[doc = "Checks that a list of dictionaries contains valid `" $name:upper "` records, then appends to outfile." ]
304 #[pyfunction]
305 #[pyo3(name = $fn_name)]
306 #[pyo3(text_signature = "(recs: list[dict], outfile: str, /)")]
307 fn [< write_ $name _py >](recs: Vec<IndexMap<String, DmapField>>, outfile: PathBuf) -> PyResult<()> {
308 [< try_write_ $name >](recs, &outfile).map_err(PyErr::from)
309 }
310
311 #[doc = "Checks that a list of dictionaries contains valid `" $name:upper "` records, then converts them to bytes." ]
312 #[doc = "Returns `list[bytes]`, one entry per record." ]
313 #[pyfunction]
314 #[pyo3(name = $bytes_name)]
315 #[pyo3(text_signature = "(recs: list[dict], /)")]
316 fn [< write_ $name _bytes_py >](py: Python, recs: Vec<IndexMap<String, DmapField>>) -> PyResult<Py<PyAny>> {
317 let bytes = [< $name:camel Record >]::try_into_bytes(recs).map_err(PyErr::from)?;
318 Ok(PyBytes::new(py, &bytes).into())
319 }
320 }
321 }
322}
323
324write_py!(iqdat, "write_iqdat", "write_iqdat_bytes");
326write_py!(rawacf, "write_rawacf", "write_rawacf_bytes");
327write_py!(fitacf, "write_fitacf", "write_fitacf_bytes");
328write_py!(grid, "write_grid", "write_grid_bytes");
329write_py!(map, "write_map", "write_map_bytes");
330write_py!(snd, "write_snd", "write_snd_bytes");
331
332#[pymodule]
334fn dmap_rs(m: &Bound<'_, PyModule>) -> PyResult<()> {
335 m.add_function(wrap_pyfunction!(read_dmap_py, m)?)?;
337 m.add_function(wrap_pyfunction!(read_iqdat_py, m)?)?;
338 m.add_function(wrap_pyfunction!(read_rawacf_py, m)?)?;
339 m.add_function(wrap_pyfunction!(read_fitacf_py, m)?)?;
340 m.add_function(wrap_pyfunction!(read_snd_py, m)?)?;
341 m.add_function(wrap_pyfunction!(read_grid_py, m)?)?;
342 m.add_function(wrap_pyfunction!(read_map_py, m)?)?;
343
344 m.add_function(wrap_pyfunction!(read_dmap_lax_py, m)?)?;
346 m.add_function(wrap_pyfunction!(read_iqdat_lax_py, m)?)?;
347 m.add_function(wrap_pyfunction!(read_rawacf_lax_py, m)?)?;
348 m.add_function(wrap_pyfunction!(read_fitacf_lax_py, m)?)?;
349 m.add_function(wrap_pyfunction!(read_snd_lax_py, m)?)?;
350 m.add_function(wrap_pyfunction!(read_grid_lax_py, m)?)?;
351 m.add_function(wrap_pyfunction!(read_map_lax_py, m)?)?;
352
353 m.add_function(wrap_pyfunction!(read_dmap_bytes_py, m)?)?;
355 m.add_function(wrap_pyfunction!(read_iqdat_bytes_py, m)?)?;
356 m.add_function(wrap_pyfunction!(read_rawacf_bytes_py, m)?)?;
357 m.add_function(wrap_pyfunction!(read_fitacf_bytes_py, m)?)?;
358 m.add_function(wrap_pyfunction!(read_snd_bytes_py, m)?)?;
359 m.add_function(wrap_pyfunction!(read_grid_bytes_py, m)?)?;
360 m.add_function(wrap_pyfunction!(read_map_bytes_py, m)?)?;
361
362 m.add_function(wrap_pyfunction!(read_dmap_bytes_lax_py, m)?)?;
364 m.add_function(wrap_pyfunction!(read_iqdat_bytes_lax_py, m)?)?;
365 m.add_function(wrap_pyfunction!(read_rawacf_bytes_lax_py, m)?)?;
366 m.add_function(wrap_pyfunction!(read_fitacf_bytes_lax_py, m)?)?;
367 m.add_function(wrap_pyfunction!(read_snd_bytes_lax_py, m)?)?;
368 m.add_function(wrap_pyfunction!(read_grid_bytes_lax_py, m)?)?;
369 m.add_function(wrap_pyfunction!(read_map_bytes_lax_py, m)?)?;
370
371 m.add_function(wrap_pyfunction!(write_dmap_py, m)?)?;
373 m.add_function(wrap_pyfunction!(write_iqdat_py, m)?)?;
374 m.add_function(wrap_pyfunction!(write_rawacf_py, m)?)?;
375 m.add_function(wrap_pyfunction!(write_fitacf_py, m)?)?;
376 m.add_function(wrap_pyfunction!(write_grid_py, m)?)?;
377 m.add_function(wrap_pyfunction!(write_map_py, m)?)?;
378 m.add_function(wrap_pyfunction!(write_snd_py, m)?)?;
379
380 m.add_function(wrap_pyfunction!(write_dmap_bytes_py, m)?)?;
382 m.add_function(wrap_pyfunction!(write_iqdat_bytes_py, m)?)?;
383 m.add_function(wrap_pyfunction!(write_rawacf_bytes_py, m)?)?;
384 m.add_function(wrap_pyfunction!(write_fitacf_bytes_py, m)?)?;
385 m.add_function(wrap_pyfunction!(write_snd_bytes_py, m)?)?;
386 m.add_function(wrap_pyfunction!(write_grid_bytes_py, m)?)?;
387 m.add_function(wrap_pyfunction!(write_map_bytes_py, m)?)?;
388
389 m.add_function(wrap_pyfunction!(sniff_dmap_py, m)?)?;
391 m.add_function(wrap_pyfunction!(sniff_iqdat_py, m)?)?;
392 m.add_function(wrap_pyfunction!(sniff_rawacf_py, m)?)?;
393 m.add_function(wrap_pyfunction!(sniff_fitacf_py, m)?)?;
394 m.add_function(wrap_pyfunction!(sniff_snd_py, m)?)?;
395 m.add_function(wrap_pyfunction!(sniff_grid_py, m)?)?;
396 m.add_function(wrap_pyfunction!(sniff_map_py, m)?)?;
397
398 Ok(())
399}