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 bz2: bool,
125 ) -> Result<(), DmapError> {
126 let bytes = [< $type:camel Record >]::try_into_bytes(recs)?;
127 crate::io::bytes_to_file(bytes, outfile, bz2).map_err(DmapError::from)
128 }
129 }
130 }
131}
132
133write_rust!(iqdat);
134write_rust!(rawacf);
135write_rust!(fitacf);
136write_rust!(grid);
137write_rust!(map);
138write_rust!(snd);
139write_rust!(dmap);
140
141macro_rules! read_py {
152 (
153 $name:ident,
154 $py_name:literal,
155 $lax_name:literal,
156 $bytes_name:literal,
157 $lax_bytes_name:literal,
158 $sniff_name:literal,
159 $metadata_name:literal
160 ) => {
161 paste! {
162 #[doc = "Reads a `" $name:upper "` file, returning a list of dictionaries containing the fields." ]
163 #[pyfunction]
164 #[pyo3(name = $py_name)]
165 #[pyo3(text_signature = "(infile: str, /)")]
166 fn [< read_ $name _py >](infile: PathBuf) -> PyResult<Vec<IndexMap<String, DmapField>>> {
167 Ok([< $name:camel Record >]::read_file(&infile)
168 .map_err(PyErr::from)?
169 .into_iter()
170 .map(|rec| rec.inner())
171 .collect()
172 )
173 }
174
175 #[doc = "Reads a `" $name:upper "` file, returning a tuple of" ]
176 #[doc = "(list of dictionaries containing the fields, byte where first corrupted record starts). "]
177 #[pyfunction]
178 #[pyo3(name = $lax_name)]
179 #[pyo3(text_signature = "(infile: str, /)")]
180 fn [< read_ $name _lax_py >](
181 infile: PathBuf,
182 ) -> PyResult<(Vec<IndexMap<String, DmapField>>, Option<usize>)> {
183 let result = [< $name:camel Record >]::read_file_lax(&infile).map_err(PyErr::from)?;
184 Ok((
185 result.0.into_iter().map(|rec| rec.inner()).collect(),
186 result.1,
187 ))
188 }
189
190 #[doc = "Read in `" $name:upper "` records from bytes, returning `List[Dict]` of the records." ]
191 #[pyfunction]
192 #[pyo3(name = $bytes_name)]
193 #[pyo3(text_signature = "(buf: bytes, /)")]
194 fn [< read_ $name _bytes_py >](bytes: &[u8]) -> PyResult<Vec<IndexMap<String, DmapField>>> {
195 Ok([< $name:camel Record >]::read_records(bytes)?
196 .into_iter()
197 .map(|rec| rec.inner())
198 .collect()
199 )
200 }
201
202 #[doc = "Reads a `" $name:upper "` file, returning a tuple of" ]
203 #[doc = "(list of dictionaries containing the fields, byte where first corrupted record starts). "]
204 #[pyfunction]
205 #[pyo3(name = $lax_bytes_name)]
206 #[pyo3(text_signature = "(buf: bytes, /)")]
207 fn [< read_ $name _bytes_lax_py >](
208 bytes: &[u8],
209 ) -> PyResult<(Vec<IndexMap<String, DmapField>>, Option<usize>)> {
210 let result = [< $name:camel Record >]::read_records_lax(bytes).map_err(PyErr::from)?;
211 Ok((
212 result.0.into_iter().map(|rec| rec.inner()).collect(),
213 result.1,
214 ))
215 }
216
217 #[doc = "Reads a `" $name:upper "` file, returning the first record." ]
218 #[pyfunction]
219 #[pyo3(name = $sniff_name)]
220 #[pyo3(text_signature = "(infile: str, /)")]
221 fn [< sniff_ $name _py >](infile: PathBuf) -> PyResult<IndexMap<String, DmapField>> {
222 Ok([< $name:camel Record >]::sniff_file(&infile)
223 .map_err(PyErr::from)?
224 .inner()
225 )
226 }
227
228 #[doc = "Reads a `" $name:upper "` file, returning a list of dictionaries containing the only the metadata fields." ]
229 #[pyfunction]
230 #[pyo3(name = $metadata_name)]
231 #[pyo3(text_signature = "(infile: str, /)")]
232 fn [< read_ $name _metadata_py >](infile: PathBuf) -> PyResult<Vec<IndexMap<String, DmapField>>> {
233 Ok([< $name:camel Record >]::read_file_metadata(&infile)
234 .map_err(PyErr::from)?
235 )
236 }
237 }
238 }
239}
240
241read_py!(
242 iqdat,
243 "read_iqdat",
244 "read_iqdat_lax",
245 "read_iqdat_bytes",
246 "read_iqdat_bytes_lax",
247 "sniff_iqdat",
248 "read_iqdat_metadata"
249);
250read_py!(
251 rawacf,
252 "read_rawacf",
253 "read_rawacf_lax",
254 "read_rawacf_bytes",
255 "read_rawacf_bytes_lax",
256 "sniff_rawacf",
257 "read_rawacf_metadata"
258);
259read_py!(
260 fitacf,
261 "read_fitacf",
262 "read_fitacf_lax",
263 "read_fitacf_bytes",
264 "read_fitacf_bytes_lax",
265 "sniff_fitacf",
266 "read_fitacf_metadata"
267);
268read_py!(
269 grid,
270 "read_grid",
271 "read_grid_lax",
272 "read_grid_bytes",
273 "read_grid_bytes_lax",
274 "sniff_grid",
275 "read_grid_metadata"
276);
277read_py!(
278 map,
279 "read_map",
280 "read_map_lax",
281 "read_map_bytes",
282 "read_map_bytes_lax",
283 "sniff_map",
284 "read_map_metadata"
285);
286read_py!(
287 snd,
288 "read_snd",
289 "read_snd_lax",
290 "read_snd_bytes",
291 "read_snd_bytes_lax",
292 "sniff_snd",
293 "read_snd_metadata"
294);
295read_py!(
296 dmap,
297 "read_dmap",
298 "read_dmap_lax",
299 "read_dmap_bytes",
300 "read_dmap_bytes_lax",
301 "sniff_dmap",
302 "read_dmap_metadata"
303);
304
305#[pyfunction]
311#[pyo3(name = "write_dmap")]
312#[pyo3(signature = (recs, outfile, /, bz2))]
313#[pyo3(text_signature = "(recs: list[dict], outfile: str, /, bz2: bool = False)")]
314fn write_dmap_py(
315 recs: Vec<IndexMap<String, DmapField>>,
316 outfile: PathBuf,
317 bz2: bool,
318) -> PyResult<()> {
319 try_write_dmap(recs, &outfile, bz2).map_err(PyErr::from)
320}
321
322#[pyfunction]
329#[pyo3(name = "write_dmap_bytes")]
330#[pyo3(signature = (recs, /, bz2))]
331#[pyo3(text_signature = "(recs: list[dict], /, bz2: bool = False)")]
332fn write_dmap_bytes_py(
333 py: Python,
334 recs: Vec<IndexMap<String, DmapField>>,
335 bz2: bool,
336) -> PyResult<Py<PyAny>> {
337 let mut bytes = DmapRecord::try_into_bytes(recs).map_err(PyErr::from)?;
338 if bz2 {
339 bytes = compression::compress_bz2(&bytes).map_err(PyErr::from)?;
340 }
341 Ok(PyBytes::new(py, &bytes).into())
342}
343
344macro_rules! write_py {
346 ($name:ident, $fn_name:literal, $bytes_name:literal) => {
347 paste! {
348 #[doc = "Checks that a list of dictionaries contains valid `" $name:upper "` records, then appends to outfile." ]
349 #[pyfunction]
350 #[pyo3(name = $fn_name)]
351 #[pyo3(signature = (recs, outfile, /, bz2))]
352 #[pyo3(text_signature = "(recs: list[dict], outfile: str, /, bz2: bool = False)")]
353 fn [< write_ $name _py >](recs: Vec<IndexMap<String, DmapField>>, outfile: PathBuf, bz2: bool) -> PyResult<()> {
354 [< try_write_ $name >](recs, &outfile, bz2).map_err(PyErr::from)
355 }
356
357 #[doc = "Checks that a list of dictionaries contains valid `" $name:upper "` records, then converts them to bytes." ]
358 #[doc = "Returns `list[bytes]`, one entry per record." ]
359 #[pyfunction]
360 #[pyo3(name = $bytes_name)]
361 #[pyo3(signature = (recs, /, bz2))]
362 #[pyo3(text_signature = "(recs: list[dict], /, bz2: bool = False)")]
363 fn [< write_ $name _bytes_py >](py: Python, recs: Vec<IndexMap<String, DmapField>>, bz2: bool) -> PyResult<Py<PyAny>> {
364 let mut bytes = [< $name:camel Record >]::try_into_bytes(recs).map_err(PyErr::from)?;
365 if bz2 {
366 bytes = compression::compress_bz2(&bytes).map_err(PyErr::from)?;
367 }
368 Ok(PyBytes::new(py, &bytes).into())
369 }
370 }
371 }
372}
373
374write_py!(iqdat, "write_iqdat", "write_iqdat_bytes");
376write_py!(rawacf, "write_rawacf", "write_rawacf_bytes");
377write_py!(fitacf, "write_fitacf", "write_fitacf_bytes");
378write_py!(grid, "write_grid", "write_grid_bytes");
379write_py!(map, "write_map", "write_map_bytes");
380write_py!(snd, "write_snd", "write_snd_bytes");
381
382#[pymodule]
384fn dmap_rs(m: &Bound<'_, PyModule>) -> PyResult<()> {
385 m.add_function(wrap_pyfunction!(read_dmap_py, m)?)?;
387 m.add_function(wrap_pyfunction!(read_iqdat_py, m)?)?;
388 m.add_function(wrap_pyfunction!(read_rawacf_py, m)?)?;
389 m.add_function(wrap_pyfunction!(read_fitacf_py, m)?)?;
390 m.add_function(wrap_pyfunction!(read_snd_py, m)?)?;
391 m.add_function(wrap_pyfunction!(read_grid_py, m)?)?;
392 m.add_function(wrap_pyfunction!(read_map_py, m)?)?;
393
394 m.add_function(wrap_pyfunction!(read_dmap_lax_py, m)?)?;
396 m.add_function(wrap_pyfunction!(read_iqdat_lax_py, m)?)?;
397 m.add_function(wrap_pyfunction!(read_rawacf_lax_py, m)?)?;
398 m.add_function(wrap_pyfunction!(read_fitacf_lax_py, m)?)?;
399 m.add_function(wrap_pyfunction!(read_snd_lax_py, m)?)?;
400 m.add_function(wrap_pyfunction!(read_grid_lax_py, m)?)?;
401 m.add_function(wrap_pyfunction!(read_map_lax_py, m)?)?;
402
403 m.add_function(wrap_pyfunction!(read_dmap_bytes_py, m)?)?;
405 m.add_function(wrap_pyfunction!(read_iqdat_bytes_py, m)?)?;
406 m.add_function(wrap_pyfunction!(read_rawacf_bytes_py, m)?)?;
407 m.add_function(wrap_pyfunction!(read_fitacf_bytes_py, m)?)?;
408 m.add_function(wrap_pyfunction!(read_snd_bytes_py, m)?)?;
409 m.add_function(wrap_pyfunction!(read_grid_bytes_py, m)?)?;
410 m.add_function(wrap_pyfunction!(read_map_bytes_py, m)?)?;
411
412 m.add_function(wrap_pyfunction!(read_dmap_bytes_lax_py, m)?)?;
414 m.add_function(wrap_pyfunction!(read_iqdat_bytes_lax_py, m)?)?;
415 m.add_function(wrap_pyfunction!(read_rawacf_bytes_lax_py, m)?)?;
416 m.add_function(wrap_pyfunction!(read_fitacf_bytes_lax_py, m)?)?;
417 m.add_function(wrap_pyfunction!(read_snd_bytes_lax_py, m)?)?;
418 m.add_function(wrap_pyfunction!(read_grid_bytes_lax_py, m)?)?;
419 m.add_function(wrap_pyfunction!(read_map_bytes_lax_py, m)?)?;
420
421 m.add_function(wrap_pyfunction!(write_dmap_py, m)?)?;
423 m.add_function(wrap_pyfunction!(write_iqdat_py, m)?)?;
424 m.add_function(wrap_pyfunction!(write_rawacf_py, m)?)?;
425 m.add_function(wrap_pyfunction!(write_fitacf_py, m)?)?;
426 m.add_function(wrap_pyfunction!(write_grid_py, m)?)?;
427 m.add_function(wrap_pyfunction!(write_map_py, m)?)?;
428 m.add_function(wrap_pyfunction!(write_snd_py, m)?)?;
429
430 m.add_function(wrap_pyfunction!(write_dmap_bytes_py, m)?)?;
432 m.add_function(wrap_pyfunction!(write_iqdat_bytes_py, m)?)?;
433 m.add_function(wrap_pyfunction!(write_rawacf_bytes_py, m)?)?;
434 m.add_function(wrap_pyfunction!(write_fitacf_bytes_py, m)?)?;
435 m.add_function(wrap_pyfunction!(write_snd_bytes_py, m)?)?;
436 m.add_function(wrap_pyfunction!(write_grid_bytes_py, m)?)?;
437 m.add_function(wrap_pyfunction!(write_map_bytes_py, m)?)?;
438
439 m.add_function(wrap_pyfunction!(sniff_dmap_py, m)?)?;
441 m.add_function(wrap_pyfunction!(sniff_iqdat_py, m)?)?;
442 m.add_function(wrap_pyfunction!(sniff_rawacf_py, m)?)?;
443 m.add_function(wrap_pyfunction!(sniff_fitacf_py, m)?)?;
444 m.add_function(wrap_pyfunction!(sniff_snd_py, m)?)?;
445 m.add_function(wrap_pyfunction!(sniff_grid_py, m)?)?;
446 m.add_function(wrap_pyfunction!(sniff_map_py, m)?)?;
447
448 m.add_function(wrap_pyfunction!(read_dmap_metadata_py, m)?)?;
450 m.add_function(wrap_pyfunction!(read_iqdat_metadata_py, m)?)?;
451 m.add_function(wrap_pyfunction!(read_rawacf_metadata_py, m)?)?;
452 m.add_function(wrap_pyfunction!(read_fitacf_metadata_py, m)?)?;
453 m.add_function(wrap_pyfunction!(read_snd_metadata_py, m)?)?;
454 m.add_function(wrap_pyfunction!(read_grid_metadata_py, m)?)?;
455 m.add_function(wrap_pyfunction!(read_map_metadata_py, m)?)?;
456
457 Ok(())
458}