Skip to content

Commit 0fa5ba0

Browse files
Merge pull request #25 from SuperDARNCanada/develop
Release v0.3.0
2 parents 2807627 + 17b8d0f commit 0fa5ba0

File tree

5 files changed

+86
-10
lines changed

5 files changed

+86
-10
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "dmap"
3-
version = "0.2.1"
3+
version = "0.3.0"
44
edition = "2021"
55
rust-version = "1.63.0"
66

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "maturin"
44

55
[project]
66
name = "darn-dmap"
7-
version = "0.2.1"
7+
version = "0.3.0"
88
requires-python = ">=3.8"
99
authors = [
1010
{ name = "Remington Rohel" }

src/lib.rs

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ read_rust!(dmap);
163163
/// Generates two functions: `read_[type]` and `read_[type]_lax`, for strict and lax
164164
/// reading, respectively.
165165
macro_rules! read_py {
166-
($name:ident, $py_name:literal, $lax_name:literal, $bytes_name:literal, $lax_bytes_name:literal) => {
166+
($name:ident, $py_name:literal, $lax_name:literal, $bytes_name:literal, $lax_bytes_name:literal, $sniff_name:literal) => {
167167
paste! {
168168
#[doc = "Reads a `" $name:upper "` file, returning a list of dictionaries containing the fields." ]
169169
#[pyfunction]
@@ -219,29 +219,42 @@ macro_rules! read_py {
219219
result.1,
220220
))
221221
}
222+
223+
#[doc = "Reads a `" $name:upper "` file, returning the first record." ]
224+
#[pyfunction]
225+
#[pyo3(name = $sniff_name)]
226+
#[pyo3(text_signature = "(infile: str, /)")]
227+
fn [< sniff_ $name _py >](infile: PathBuf) -> PyResult<IndexMap<String, DmapField>> {
228+
Ok([< $name:camel Record >]::sniff_file(&infile)
229+
.map_err(PyErr::from)?
230+
.inner()
231+
)
232+
}
222233
}
223234
}
224235
}
225236

226-
read_py!(iqdat, "read_iqdat", "read_iqdat_lax", "read_iqdat_bytes", "read_iqdat_bytes_lax");
237+
read_py!(iqdat, "read_iqdat", "read_iqdat_lax", "read_iqdat_bytes", "read_iqdat_bytes_lax", "sniff_iqdat");
227238
read_py!(
228239
rawacf,
229240
"read_rawacf",
230241
"read_rawacf_lax",
231242
"read_rawacf_bytes",
232-
"read_rawacf_bytes_lax"
243+
"read_rawacf_bytes_lax",
244+
"sniff_rawacf"
233245
);
234246
read_py!(
235247
fitacf,
236248
"read_fitacf",
237249
"read_fitacf_lax",
238250
"read_fitacf_bytes",
239-
"read_fitacf_bytes_lax"
251+
"read_fitacf_bytes_lax",
252+
"sniff_fitacf"
240253
);
241-
read_py!(grid, "read_grid", "read_grid_lax", "read_grid_bytes", "read_grid_bytes_lax");
242-
read_py!(map, "read_map", "read_map_lax", "read_map_bytes", "read_map_bytes_lax");
243-
read_py!(snd, "read_snd", "read_snd_lax", "read_snd_bytes", "read_snd_bytes_lax");
244-
read_py!(dmap, "read_dmap", "read_dmap_lax", "read_dmap_bytes", "read_dmap_bytes_lax");
254+
read_py!(grid, "read_grid", "read_grid_lax", "read_grid_bytes", "read_grid_bytes_lax", "sniff_grid");
255+
read_py!(map, "read_map", "read_map_lax", "read_map_bytes", "read_map_bytes_lax", "sniff_map");
256+
read_py!(snd, "read_snd", "read_snd_lax", "read_snd_bytes", "read_snd_bytes_lax", "sniff_snd");
257+
read_py!(dmap, "read_dmap", "read_dmap_lax", "read_dmap_bytes", "read_dmap_bytes_lax", "sniff_dmap");
245258

246259
/// Checks that a list of dictionaries contains DMAP records, then appends to outfile.
247260
///
@@ -359,5 +372,14 @@ fn dmap(m: &Bound<'_, PyModule>) -> PyResult<()> {
359372
m.add_function(wrap_pyfunction!(write_grid_bytes_py, m)?)?;
360373
m.add_function(wrap_pyfunction!(write_map_bytes_py, m)?)?;
361374

375+
// Sniff the first record
376+
m.add_function(wrap_pyfunction!(sniff_dmap_py, m)?)?;
377+
m.add_function(wrap_pyfunction!(sniff_iqdat_py, m)?)?;
378+
m.add_function(wrap_pyfunction!(sniff_rawacf_py, m)?)?;
379+
m.add_function(wrap_pyfunction!(sniff_fitacf_py, m)?)?;
380+
m.add_function(wrap_pyfunction!(sniff_snd_py, m)?)?;
381+
m.add_function(wrap_pyfunction!(sniff_grid_py, m)?)?;
382+
m.add_function(wrap_pyfunction!(sniff_map_py, m)?)?;
383+
362384
Ok(())
363385
}

src/record.rs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,36 @@ pub trait Record<'a>:
1818
/// Gets the underlying data of `self`.
1919
fn inner(self) -> IndexMap<String, DmapField>;
2020

21+
/// Reads from `dmap_data` and parses into `Vec<Self>`.
22+
///
23+
/// Returns `DmapError` if `dmap_data` cannot be read or contains invalid data.
24+
fn read_first_record(mut dmap_data: impl Read) -> Result<Self, DmapError>
25+
where
26+
Self: Sized,
27+
Self: Send,
28+
{
29+
let mut buffer = [0; 8]; // record size should be an i32 of the data
30+
let read_result = dmap_data.read(&mut buffer[..])?;
31+
if read_result < buffer.len() {
32+
return Err(DmapError::CorruptStream("Unable to read size of first record"))
33+
}
34+
35+
let rec_size = i32::from_le_bytes(buffer[4..8].try_into().unwrap()) as usize; // advance 4 bytes, skipping the "code" field
36+
if rec_size <= 0 {
37+
return Err(DmapError::InvalidRecord(format!(
38+
"Record 0 starting at byte 0 has non-positive size {} <= 0",
39+
rec_size
40+
)));
41+
}
42+
43+
let mut rec = vec![0; rec_size];
44+
rec[0..8].clone_from_slice(&buffer[..]);
45+
dmap_data.read_exact(&mut rec[8..])?;
46+
let first_rec = Self::parse_record(&mut Cursor::new(rec))?;
47+
48+
Ok(first_rec)
49+
}
50+
2151
/// Reads from `dmap_data` and parses into `Vec<Self>`.
2252
///
2353
/// Returns `DmapError` if `dmap_data` cannot be read or contains invalid data.
@@ -171,6 +201,22 @@ pub trait Record<'a>:
171201
}
172202
}
173203

204+
/// Reads the first record of a DMAP file of type `Self`.
205+
fn sniff_file(infile: &PathBuf) -> Result<Self, DmapError>
206+
where
207+
Self: Sized,
208+
Self: Send,
209+
{
210+
let file = File::open(infile)?;
211+
match infile.extension() {
212+
Some(ext) if ext == OsStr::new("bz2") => {
213+
let compressor = BzDecoder::new(file);
214+
Self::read_first_record(compressor)
215+
}
216+
_ => Self::read_first_record(file),
217+
}
218+
}
219+
174220
/// Reads a record from `cursor`.
175221
fn parse_record(cursor: &mut Cursor<Vec<u8>>) -> Result<Self, DmapError>
176222
where

tests/tests.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,14 @@ macro_rules! make_test {
9292
// Clean up tempfile
9393
remove_file(&tempfile).expect("Unable to delete tempfile");
9494
}
95+
96+
#[test]
97+
fn [< test_ $record_type _sniff >] () {
98+
let filename: PathBuf = PathBuf::from(format!("tests/test_files/test.{}", stringify!($record_type)));
99+
let data = [< $record_type:camel Record >]::sniff_file(&filename).expect("Unable to sniff file");
100+
let all_recs = [< $record_type:camel Record >]::read_file(&filename).expect("Unable to read file");
101+
assert_eq!(data, all_recs[0])
102+
}
95103
}
96104
};
97105
}

0 commit comments

Comments
 (0)