1pub(crate) mod compression;
91pub(crate) mod convenience;
92pub mod error;
93pub mod formats;
94pub(crate) mod io;
95pub mod record;
96pub mod types;
97mod parser;
98
99pub use crate::error::DmapError;
100pub use crate::formats::dmap::DmapRecord;
101pub use crate::formats::fitacf::FitacfRecord;
102pub use crate::formats::grid::GridRecord;
103pub use crate::formats::iqdat::IqdatRecord;
104pub use crate::formats::map::MapRecord;
105pub use crate::formats::rawacf::RawacfRecord;
106pub use crate::formats::snd::SndRecord;
107pub use crate::record::Record;
108use crate::types::DmapField;
109use indexmap::IndexMap;
110use paste::paste;
111use pyo3::prelude::*;
112use pyo3::types::PyBytes;
113use std::path::{Path, PathBuf};
114
115macro_rules! write_rust {
117 ($type:ident) => {
118 paste! {
119 #[doc = "Attempts to convert `recs` to `" $type:camel Record "` then append to `outfile`."]
120 #[doc = ""]
121 #[doc = "# Errors"]
122 #[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."]
123 pub fn [< try_write_ $type >]<P: AsRef<Path>>(
124 recs: Vec<IndexMap<String, DmapField>>,
125 outfile: P,
126 bz2: bool,
127 ) -> Result<(), DmapError> {
128 let bytes = [< $type:camel Record >]::try_into_bytes(recs)?;
129 crate::io::bytes_to_file(bytes, outfile, bz2).map_err(DmapError::from)
130 }
131 }
132 }
133}
134
135write_rust!(iqdat);
136write_rust!(rawacf);
137write_rust!(fitacf);
138write_rust!(grid);
139write_rust!(map);
140write_rust!(snd);
141write_rust!(dmap);
142
143macro_rules! read_py {
155 (
156 $name:ident,
157 $py_name:literal,
158 $lax_name:literal,
159 $bytes_name:literal,
160 $lax_bytes_name:literal,
161 $sniff_name:literal,
162 $sniff_name_lax:literal,
163 $sniff_bytes:literal,
164 $sniff_bytes_lax:literal,
165 $metadata_name:literal,
166 $sniff_metadata:literal
167 ) => {
168 paste! {
169 #[doc = "Reads a `" $name:upper "` file, returning a list of dictionaries containing the fields." ]
170 #[pyfunction]
171 #[pyo3(name = $py_name)]
172 #[pyo3(text_signature = "(infile: str, /)")]
173 fn [< read_ $name _py >](infile: PathBuf) -> PyResult<Vec<IndexMap<String, DmapField>>> {
174 Ok([< $name:camel Record >]::read_file(&infile)
175 .map_err(PyErr::from)?
176 .into_iter()
177 .map(|rec| rec.inner())
178 .collect()
179 )
180 }
181
182 #[doc = "Reads a `" $name:upper "` file, returning a tuple of" ]
183 #[doc = "(list of dictionaries containing the fields, byte where first corrupted record starts). "]
184 #[pyfunction]
185 #[pyo3(name = $lax_name)]
186 #[pyo3(text_signature = "(infile: str, /)")]
187 fn [< read_ $name _lax_py >](
188 infile: PathBuf,
189 ) -> PyResult<(Vec<IndexMap<String, DmapField>>, Option<usize>)> {
190 let result = [< $name:camel Record >]::read_file_lax(&infile).map_err(PyErr::from)?;
191 Ok((
192 result.0.into_iter().map(|rec| rec.inner()).collect(),
193 result.1,
194 ))
195 }
196
197 #[doc = "Read in `" $name:upper "` records from bytes, returning `List[Dict]` of the records." ]
198 #[pyfunction]
199 #[pyo3(name = $bytes_name)]
200 #[pyo3(text_signature = "(buf: bytes, /)")]
201 fn [< read_ $name _bytes_py >](bytes: &[u8]) -> PyResult<Vec<IndexMap<String, DmapField>>> {
202 Ok([< $name:camel Record >]::read_records(bytes)?
203 .into_iter()
204 .map(|rec| rec.inner())
205 .collect()
206 )
207 }
208
209 #[doc = "Reads a `" $name:upper "` file, returning a tuple of" ]
210 #[doc = "(list of dictionaries containing the fields, byte where first corrupted record starts). "]
211 #[pyfunction]
212 #[pyo3(name = $lax_bytes_name)]
213 #[pyo3(text_signature = "(buf: bytes, /)")]
214 fn [< read_ $name _bytes_lax_py >](
215 bytes: &[u8],
216 ) -> PyResult<(Vec<IndexMap<String, DmapField>>, Option<usize>)> {
217 let result = [< $name:camel Record >]::read_records_lax(bytes).map_err(PyErr::from)?;
218 Ok((
219 result.0.into_iter().map(|rec| rec.inner()).collect(),
220 result.1,
221 ))
222 }
223
224 #[doc = "Reads a `" $name:upper "` file, returning the `nth` record(s)." ]
225 #[pyfunction]
226 #[pyo3(name = $sniff_name)]
227 #[pyo3(text_signature = "(infile: str, indices: tuple[int], /)")]
228 fn [< read_ $name _by_indices_py >](infile: PathBuf, indices: Vec<i32>) -> PyResult<Vec<IndexMap<String, DmapField>>> {
229 Ok([< $name:camel Record >]::read_file_by_indices(&infile, &indices)
230 .map_err(PyErr::from)?
231 .into_iter()
232 .map(|rec| rec.inner())
233 .collect()
234 )
235 }
236
237 #[doc = "Reads a `" $name:upper "` file, returning the `nth` record(s), and the byte index where corruption starts, if applicable" ]
238 #[pyfunction]
239 #[pyo3(name = $sniff_name_lax)]
240 #[pyo3(text_signature = "(infile: str, indices: tuple[int], /)")]
241 fn [< read_ $name _by_indices_lax_py >](
242 infile: PathBuf, indices: Vec<i32>
243 ) -> PyResult<(Vec<IndexMap<String, DmapField>>, Option<usize>)> {
244 let result = [< $name:camel Record >]::read_file_by_indices_lax(infile, &indices).map_err(PyErr::from)?;
245 Ok((
246 result.0.into_iter().map(|rec| rec.inner()).collect(),
247 result.1,
248 ))
249 }
250
251 #[doc = "Reads a `" $name:upper "` buffer, returning the `nth` record(s)." ]
252 #[pyfunction]
253 #[pyo3(name = $sniff_bytes)]
254 #[pyo3(text_signature = "(buf: bytes, indices: tuple[int], /)")]
255 fn [< read_ $name _bytes_by_indices_py >](buf: &[u8], indices: Vec<i32>) -> PyResult<Vec<IndexMap<String, DmapField>>> {
256 Ok([< $name:camel Record >]::read_nth_records(buf, &indices)
257 .map_err(PyErr::from)?
258 .into_iter()
259 .map(|rec| rec.inner())
260 .collect()
261 )
262 }
263
264 #[doc = "Reads a `" $name:upper "` buffer, returning the `nth` record(s) and the byte index where record corruption starts, if applicable." ]
265 #[pyfunction]
266 #[pyo3(name = $sniff_bytes_lax)]
267 #[pyo3(text_signature = "(buf: bytes, indices: tuple[int], /)")]
268 fn [< read_ $name _bytes_by_indices_lax_py >](
269 buf: &[u8], indices: Vec<i32>
270 ) -> PyResult<(Vec<IndexMap<String, DmapField>>, Option<usize>)> {
271 let result = [< $name:camel Record >]::read_nth_records_lax(buf, &indices).map_err(PyErr::from)?;
272 Ok((
273 result.0.into_iter().map(|rec| rec.inner()).collect(),
274 result.1,
275 ))
276 }
277
278 #[doc = "Reads a `" $name:upper "` file, returning a list of dictionaries containing the only the metadata fields." ]
279 #[pyfunction]
280 #[pyo3(name = $metadata_name)]
281 #[pyo3(text_signature = "(infile: str, /)")]
282 fn [< read_ $name _metadata_py >](infile: PathBuf) -> PyResult<Vec<IndexMap<String, DmapField>>> {
283 Ok([< $name:camel Record >]::read_file_metadata(&infile)
284 .map_err(PyErr::from)?
285 )
286 }
287
288 #[doc = "Reads a `" $name:upper "` file, returning the `nth` records' metadata fields." ]
289 #[pyfunction]
290 #[pyo3(name = $sniff_metadata)]
291 #[pyo3(text_signature = "(infile: str, indices: tuple[int], /)")]
292 fn [< read_ $name _metadata_by_indices_py >](infile: PathBuf, indices: Vec<i32>) -> PyResult<Vec<IndexMap<String, DmapField>>> {
293 Ok([< $name:camel Record >]::read_file_metadata_by_indices(&infile, &indices)
294 .map_err(PyErr::from)?
295 )
296 }
297 }
298 }
299}
300
301read_py!(
302 iqdat,
303 "read_iqdat",
304 "read_iqdat_lax",
305 "read_iqdat_bytes",
306 "read_iqdat_bytes_lax",
307 "read_iqdat_by_indices",
308 "read_iqdat_by_indices_lax",
309 "read_iqdat_by_indices_bytes",
310 "read_iqdat_by_indices_bytes_lax",
311 "read_iqdat_metadata",
312 "read_iqdat_metadata_by_indices"
313);
314read_py!(
315 rawacf,
316 "read_rawacf",
317 "read_rawacf_lax",
318 "read_rawacf_bytes",
319 "read_rawacf_bytes_lax",
320 "read_rawacf_by_indices",
321 "read_rawacf_by_indices_lax",
322 "read_rawacf_by_indices_bytes",
323 "read_rawacf_by_indices_bytes_lax",
324 "read_rawacf_metadata",
325 "read_rawacf_metadata_by_indices"
326);
327read_py!(
328 fitacf,
329 "read_fitacf",
330 "read_fitacf_lax",
331 "read_fitacf_bytes",
332 "read_fitacf_bytes_lax",
333 "read_fitacf_by_indices",
334 "read_fitacf_by_indices_lax",
335 "read_fitacf_by_indices_bytes",
336 "read_fitacf_by_indices_bytes_lax",
337 "read_fitacf_metadata",
338 "read_fitacf_metadata_by_indices"
339);
340read_py!(
341 grid,
342 "read_grid",
343 "read_grid_lax",
344 "read_grid_bytes",
345 "read_grid_bytes_lax",
346 "read_grid_by_indices",
347 "read_grid_by_indices_lax",
348 "read_grid_by_indices_bytes",
349 "read_grid_by_indices_bytes_lax",
350 "read_grid_metadata",
351 "read_grid_metadata_by_indices"
352);
353read_py!(
354 map,
355 "read_map",
356 "read_map_lax",
357 "read_map_bytes",
358 "read_map_bytes_lax",
359 "read_map_by_indices",
360 "read_map_by_indices_lax",
361 "read_map_by_indices_bytes",
362 "read_map_by_indices_bytes_lax",
363 "read_map_metadata",
364 "read_map_metadata_by_indices"
365);
366read_py!(
367 snd,
368 "read_snd",
369 "read_snd_lax",
370 "read_snd_bytes",
371 "read_snd_bytes_lax",
372 "read_snd_by_indices",
373 "read_snd_by_indices_lax",
374 "read_snd_by_indices_bytes",
375 "read_snd_by_indices_bytes_lax",
376 "read_snd_metadata",
377 "read_snd_metadata_by_indices"
378);
379read_py!(
380 dmap,
381 "read_dmap",
382 "read_dmap_lax",
383 "read_dmap_bytes",
384 "read_dmap_bytes_lax",
385 "read_dmap_by_indices",
386 "read_dmap_by_indices_lax",
387 "read_dmap_by_indices_bytes",
388 "read_dmap_by_indices_bytes_lax",
389 "read_dmap_metadata",
390 "read_dmap_metadata_by_indices"
391);
392
393#[pyfunction]
399#[pyo3(name = "write_dmap")]
400#[pyo3(signature = (recs, outfile, /, bz2))]
401#[pyo3(text_signature = "(recs: list[dict], outfile: str, /, bz2: bool = False)")]
402fn write_dmap_py(
403 recs: Vec<IndexMap<String, DmapField>>,
404 outfile: PathBuf,
405 bz2: bool,
406) -> PyResult<()> {
407 try_write_dmap(recs, &outfile, bz2).map_err(PyErr::from)
408}
409
410#[pyfunction]
417#[pyo3(name = "write_dmap_bytes")]
418#[pyo3(signature = (recs, /, bz2))]
419#[pyo3(text_signature = "(recs: list[dict], /, bz2: bool = False)")]
420fn write_dmap_bytes_py(
421 py: Python,
422 recs: Vec<IndexMap<String, DmapField>>,
423 bz2: bool,
424) -> PyResult<Py<PyAny>> {
425 let mut bytes = DmapRecord::try_into_bytes(recs).map_err(PyErr::from)?;
426 if bz2 {
427 bytes = compression::compress_bz2(&bytes).map_err(PyErr::from)?;
428 }
429 Ok(PyBytes::new(py, &bytes).into())
430}
431
432macro_rules! write_py {
434 ($name:ident, $fn_name:literal, $bytes_name:literal) => {
435 paste! {
436 #[doc = "Checks that a list of dictionaries contains valid `" $name:upper "` records, then appends to outfile." ]
437 #[pyfunction]
438 #[pyo3(name = $fn_name)]
439 #[pyo3(signature = (recs, outfile, /, bz2))]
440 #[pyo3(text_signature = "(recs: list[dict], outfile: str, /, bz2: bool = False)")]
441 fn [< write_ $name _py >](recs: Vec<IndexMap<String, DmapField>>, outfile: PathBuf, bz2: bool) -> PyResult<()> {
442 [< try_write_ $name >](recs, &outfile, bz2).map_err(PyErr::from)
443 }
444
445 #[doc = "Checks that a list of dictionaries contains valid `" $name:upper "` records, then converts them to bytes." ]
446 #[doc = "Returns `list[bytes]`, one entry per record." ]
447 #[pyfunction]
448 #[pyo3(name = $bytes_name)]
449 #[pyo3(signature = (recs, /, bz2))]
450 #[pyo3(text_signature = "(recs: list[dict], /, bz2: bool = False)")]
451 fn [< write_ $name _bytes_py >](py: Python, recs: Vec<IndexMap<String, DmapField>>, bz2: bool) -> PyResult<Py<PyAny>> {
452 let mut bytes = [< $name:camel Record >]::try_into_bytes(recs).map_err(PyErr::from)?;
453 if bz2 {
454 bytes = compression::compress_bz2(&bytes).map_err(PyErr::from)?;
455 }
456 Ok(PyBytes::new(py, &bytes).into())
457 }
458 }
459 }
460}
461
462write_py!(iqdat, "write_iqdat", "write_iqdat_bytes");
464write_py!(rawacf, "write_rawacf", "write_rawacf_bytes");
465write_py!(fitacf, "write_fitacf", "write_fitacf_bytes");
466write_py!(grid, "write_grid", "write_grid_bytes");
467write_py!(map, "write_map", "write_map_bytes");
468write_py!(snd, "write_snd", "write_snd_bytes");
469
470#[pymodule]
472fn dmap_rs(m: &Bound<'_, PyModule>) -> PyResult<()> {
473 m.add_function(wrap_pyfunction!(read_dmap_py, m)?)?;
475 m.add_function(wrap_pyfunction!(read_iqdat_py, m)?)?;
476 m.add_function(wrap_pyfunction!(read_rawacf_py, m)?)?;
477 m.add_function(wrap_pyfunction!(read_fitacf_py, m)?)?;
478 m.add_function(wrap_pyfunction!(read_snd_py, m)?)?;
479 m.add_function(wrap_pyfunction!(read_grid_py, m)?)?;
480 m.add_function(wrap_pyfunction!(read_map_py, m)?)?;
481
482 m.add_function(wrap_pyfunction!(read_dmap_lax_py, m)?)?;
484 m.add_function(wrap_pyfunction!(read_iqdat_lax_py, m)?)?;
485 m.add_function(wrap_pyfunction!(read_rawacf_lax_py, m)?)?;
486 m.add_function(wrap_pyfunction!(read_fitacf_lax_py, m)?)?;
487 m.add_function(wrap_pyfunction!(read_snd_lax_py, m)?)?;
488 m.add_function(wrap_pyfunction!(read_grid_lax_py, m)?)?;
489 m.add_function(wrap_pyfunction!(read_map_lax_py, m)?)?;
490
491 m.add_function(wrap_pyfunction!(read_dmap_bytes_py, m)?)?;
493 m.add_function(wrap_pyfunction!(read_iqdat_bytes_py, m)?)?;
494 m.add_function(wrap_pyfunction!(read_rawacf_bytes_py, m)?)?;
495 m.add_function(wrap_pyfunction!(read_fitacf_bytes_py, m)?)?;
496 m.add_function(wrap_pyfunction!(read_snd_bytes_py, m)?)?;
497 m.add_function(wrap_pyfunction!(read_grid_bytes_py, m)?)?;
498 m.add_function(wrap_pyfunction!(read_map_bytes_py, m)?)?;
499
500 m.add_function(wrap_pyfunction!(read_dmap_bytes_by_indices_py, m)?)?;
502 m.add_function(wrap_pyfunction!(read_iqdat_bytes_by_indices_py, m)?)?;
503 m.add_function(wrap_pyfunction!(read_rawacf_bytes_by_indices_py, m)?)?;
504 m.add_function(wrap_pyfunction!(read_fitacf_bytes_by_indices_py, m)?)?;
505 m.add_function(wrap_pyfunction!(read_snd_bytes_by_indices_py, m)?)?;
506 m.add_function(wrap_pyfunction!(read_grid_bytes_by_indices_py, m)?)?;
507 m.add_function(wrap_pyfunction!(read_map_bytes_by_indices_py, m)?)?;
508
509 m.add_function(wrap_pyfunction!(read_dmap_bytes_by_indices_lax_py, m)?)?;
511 m.add_function(wrap_pyfunction!(read_iqdat_bytes_by_indices_lax_py, m)?)?;
512 m.add_function(wrap_pyfunction!(read_rawacf_bytes_by_indices_lax_py, m)?)?;
513 m.add_function(wrap_pyfunction!(read_fitacf_bytes_by_indices_lax_py, m)?)?;
514 m.add_function(wrap_pyfunction!(read_snd_bytes_by_indices_lax_py, m)?)?;
515 m.add_function(wrap_pyfunction!(read_grid_bytes_by_indices_lax_py, m)?)?;
516 m.add_function(wrap_pyfunction!(read_map_bytes_by_indices_lax_py, m)?)?;
517
518 m.add_function(wrap_pyfunction!(read_dmap_bytes_lax_py, m)?)?;
520 m.add_function(wrap_pyfunction!(read_iqdat_bytes_lax_py, m)?)?;
521 m.add_function(wrap_pyfunction!(read_rawacf_bytes_lax_py, m)?)?;
522 m.add_function(wrap_pyfunction!(read_fitacf_bytes_lax_py, m)?)?;
523 m.add_function(wrap_pyfunction!(read_snd_bytes_lax_py, m)?)?;
524 m.add_function(wrap_pyfunction!(read_grid_bytes_lax_py, m)?)?;
525 m.add_function(wrap_pyfunction!(read_map_bytes_lax_py, m)?)?;
526
527 m.add_function(wrap_pyfunction!(write_dmap_py, m)?)?;
529 m.add_function(wrap_pyfunction!(write_iqdat_py, m)?)?;
530 m.add_function(wrap_pyfunction!(write_rawacf_py, m)?)?;
531 m.add_function(wrap_pyfunction!(write_fitacf_py, m)?)?;
532 m.add_function(wrap_pyfunction!(write_grid_py, m)?)?;
533 m.add_function(wrap_pyfunction!(write_map_py, m)?)?;
534 m.add_function(wrap_pyfunction!(write_snd_py, m)?)?;
535
536 m.add_function(wrap_pyfunction!(write_dmap_bytes_py, m)?)?;
538 m.add_function(wrap_pyfunction!(write_iqdat_bytes_py, m)?)?;
539 m.add_function(wrap_pyfunction!(write_rawacf_bytes_py, m)?)?;
540 m.add_function(wrap_pyfunction!(write_fitacf_bytes_py, m)?)?;
541 m.add_function(wrap_pyfunction!(write_snd_bytes_py, m)?)?;
542 m.add_function(wrap_pyfunction!(write_grid_bytes_py, m)?)?;
543 m.add_function(wrap_pyfunction!(write_map_bytes_py, m)?)?;
544
545 m.add_function(wrap_pyfunction!(read_dmap_by_indices_py, m)?)?;
547 m.add_function(wrap_pyfunction!(read_iqdat_by_indices_py, m)?)?;
548 m.add_function(wrap_pyfunction!(read_rawacf_by_indices_py, m)?)?;
549 m.add_function(wrap_pyfunction!(read_fitacf_by_indices_py, m)?)?;
550 m.add_function(wrap_pyfunction!(read_snd_by_indices_py, m)?)?;
551 m.add_function(wrap_pyfunction!(read_grid_by_indices_py, m)?)?;
552 m.add_function(wrap_pyfunction!(read_map_by_indices_py, m)?)?;
553
554 m.add_function(wrap_pyfunction!(read_dmap_by_indices_lax_py, m)?)?;
556 m.add_function(wrap_pyfunction!(read_iqdat_by_indices_lax_py, m)?)?;
557 m.add_function(wrap_pyfunction!(read_rawacf_by_indices_lax_py, m)?)?;
558 m.add_function(wrap_pyfunction!(read_fitacf_by_indices_lax_py, m)?)?;
559 m.add_function(wrap_pyfunction!(read_snd_by_indices_lax_py, m)?)?;
560 m.add_function(wrap_pyfunction!(read_grid_by_indices_lax_py, m)?)?;
561 m.add_function(wrap_pyfunction!(read_map_by_indices_lax_py, m)?)?;
562
563 m.add_function(wrap_pyfunction!(read_dmap_metadata_py, m)?)?;
565 m.add_function(wrap_pyfunction!(read_iqdat_metadata_py, m)?)?;
566 m.add_function(wrap_pyfunction!(read_rawacf_metadata_py, m)?)?;
567 m.add_function(wrap_pyfunction!(read_fitacf_metadata_py, m)?)?;
568 m.add_function(wrap_pyfunction!(read_snd_metadata_py, m)?)?;
569 m.add_function(wrap_pyfunction!(read_grid_metadata_py, m)?)?;
570 m.add_function(wrap_pyfunction!(read_map_metadata_py, m)?)?;
571
572 m.add_function(wrap_pyfunction!(read_dmap_metadata_by_indices_py, m)?)?;
574 m.add_function(wrap_pyfunction!(read_iqdat_metadata_by_indices_py, m)?)?;
575 m.add_function(wrap_pyfunction!(read_rawacf_metadata_by_indices_py, m)?)?;
576 m.add_function(wrap_pyfunction!(read_fitacf_metadata_by_indices_py, m)?)?;
577 m.add_function(wrap_pyfunction!(read_snd_metadata_by_indices_py, m)?)?;
578 m.add_function(wrap_pyfunction!(read_grid_metadata_by_indices_py, m)?)?;
579 m.add_function(wrap_pyfunction!(read_map_metadata_by_indices_py, m)?)?;
580
581 Ok(())
582}