spc_core/
lib.rs

1#[allow(dead_code)]
2use camino::Utf8Path;
3use lex::SPCReader;
4use miette::IntoDiagnostic;
5use parse::ParsedSPC;
6
7mod block;
8mod header;
9mod lex;
10mod logblock;
11mod parse;
12pub(crate) mod units;
13mod write;
14
15use lex::LexedSPC;
16use parse::TryParse;
17use units::{
18    xzwType, xzwTypeCreationError, yType, yTypeCreationError, InstrumentTechnique,
19    InstrumentTechniqueCreationError,
20};
21use write::{CsvWriter, WriteSPC};
22use zerocopy::{BigEndian, LittleEndian};
23
24pub fn write_spc(input_path: &Utf8Path, parsed: ParsedSPC) -> miette::Result<()> {
25    let output_path = input_path.with_extension("csv");
26    let writer = CsvWriter;
27
28    let mut file_handle = fs_err::OpenOptions::new()
29        .write(true)
30        .create(true)
31        .open(output_path)
32        .into_diagnostic()?;
33
34    writer
35        .write_spc(&mut file_handle, &parsed)
36        .into_diagnostic()
37}
38
39pub fn parse(source: &'_ [u8]) -> miette::Result<ParsedSPC> {
40    Ok(match source.get(1).copied() {
41        Some(0x4c) => lex_big_endian_spc(source)?.try_parse(),
42        Some(0x4b) | Some(0x4d) => lex_little_endian_spc(source)?.try_parse(),
43        Some(b) => panic!("impossible file type descriptor {b}"),
44        None => panic!("file contained less than two bytes"),
45    }?)
46}
47
48pub fn lex_big_endian_spc(source: &'_ [u8]) -> miette::Result<LexedSPC<'_, BigEndian>> {
49    log::info!("lexing big-endian SPC file");
50    assert!(matches!(source.get(1).copied().unwrap(), 0x4c));
51    SPCReader::big_endian(source).lex()
52}
53
54pub fn lex_little_endian_spc(source: &'_ [u8]) -> miette::Result<LexedSPC<'_, LittleEndian>> {
55    log::info!("lexing little-endian SPC file");
56    assert!(matches!(source.get(1).copied().unwrap(), 0x4b | 0x4d));
57    SPCReader::little_endian(source).lex()
58}
59
60#[cfg(test)]
61mod test {
62    use std::io::{BufReader, Cursor, Read};
63
64    use fs_err::File;
65    use miette::{Context, IntoDiagnostic};
66
67    use crate::{
68        parse,
69        write::{CsvWriter, WriteSPC},
70    };
71
72    #[test]
73    fn ftir_data_parse_matches_expected() -> miette::Result<()> {
74        let expected = include_str!("/tmp/spc/test_data/Ft-ir.csv");
75
76        let spc = "/tmp/spc/test_data/Ft-ir.spc";
77
78        let file = File::open(spc)
79            .into_diagnostic()
80            .wrap_err_with(|| format!("opening '{}' failed", spc))?;
81
82        let source = BufReader::new(file)
83            .bytes()
84            .collect::<Result<Vec<_>, _>>()
85            .unwrap();
86
87        let parsed = parse(&source[..])?;
88
89        let writer = CsvWriter;
90        let mut sink: Cursor<Vec<u8>> = Cursor::new(Vec::new());
91
92        writer.write_spc(&mut sink, &parsed).unwrap();
93
94        let output_vec: Vec<u8> = sink.into_inner();
95        let output_string: String = String::from_utf8(output_vec).unwrap();
96
97        for (expected, actual) in expected
98            .split_terminator('\n')
99            .zip(output_string.split_terminator('\n'))
100        {
101            assert_eq!(expected, actual);
102        }
103
104        Ok(())
105    }
106}