Renames headproc to more descriptive headparse
This commit is contained in:
453
src/headparse.rs
Normal file
453
src/headparse.rs
Normal file
@@ -0,0 +1,453 @@
|
||||
use std::{collections::HashMap, u64, vec};
|
||||
|
||||
use crate::SignalFormat;
|
||||
|
||||
/// Record information obtained from the header.
|
||||
///
|
||||
/// Only the `name`, `signal_count`, and `sampling_freq` values are required by default.
|
||||
#[derive(Debug, Clone)]
|
||||
struct Record {
|
||||
name: String,
|
||||
seg_num: Option<u64>,
|
||||
signal_count: u64,
|
||||
sampling_freq: u64,
|
||||
counter_freq: Option<u64>,
|
||||
base_counter_val: Option<u64>,
|
||||
sample_num: Option<u64>,
|
||||
basetime: Option<String>, // I dont thing we really need to care much about these
|
||||
basedate: Option<String>
|
||||
}
|
||||
|
||||
impl Record {
|
||||
/// Attempts to generate the record information from a string of the argument line
|
||||
pub fn from_str(argument_line: &str) -> Result<Record, &str> {
|
||||
let args: Vec<&str> = argument_line.split(' ').collect();
|
||||
if args.len() < 2 {
|
||||
return Err("error: header file provided lacks sufficient record arguments.");
|
||||
}
|
||||
let name = args[0].to_string();
|
||||
let seg_num: Option<u64>;
|
||||
let sig_count: u64;
|
||||
let sampling_freq: u64;
|
||||
// Everything else is initialized with None, this is for sake of me not getting a stroke
|
||||
let mut counter_freq: Option<u64> = None;
|
||||
let mut base_counter_val: Option<u64> = None;
|
||||
let mut sample_num: Option<u64> = None;
|
||||
let mut basetime: Option<String> = None;
|
||||
let mut basedate: Option<String> = None;
|
||||
|
||||
|
||||
// Signals and segments are kept in a single argument organized as signal/segment
|
||||
// segments are optional and we need to check this too
|
||||
{
|
||||
let seg_sig: Vec<&str> = args[1].split('/').collect();
|
||||
if seg_sig.len() == 2 {
|
||||
match seg_sig[1].parse::<u64>() {
|
||||
Ok(value) => {
|
||||
seg_num = Some(value);
|
||||
}
|
||||
Err(_) => {
|
||||
return Err("error: failed to parse segment number from header.");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
seg_num = None;
|
||||
}
|
||||
match seg_sig[0].parse::<u64>() {
|
||||
Ok(value) => {
|
||||
sig_count = value;
|
||||
}
|
||||
Err(_) => {
|
||||
return Err("error: failed to parse signal number from header.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Parse everything else, if present
|
||||
loop { // This is apparently the way to get safe correct goto-like behaviour
|
||||
if args.len() <= 2 { // Sampling frequency and counter frequency
|
||||
sampling_freq = 250; // Default value
|
||||
break;
|
||||
}
|
||||
|
||||
{
|
||||
let freq_dual: Vec<&str> = args[2].split('/').collect();
|
||||
if freq_dual.len() == 2 {
|
||||
match freq_dual[1].parse::<u64>() {
|
||||
Ok(value) => {
|
||||
counter_freq = Some(value);
|
||||
}
|
||||
Err(_) => {
|
||||
return Err("error: failed to parse counter frequency from header.");
|
||||
}
|
||||
}
|
||||
}
|
||||
match freq_dual[0].parse::<u64>() {
|
||||
Ok(value) => {
|
||||
sampling_freq = value;
|
||||
}
|
||||
Err(_) => {
|
||||
return Err("error: failed to parse sampling frequency from header.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if args.len() <= 3 {break;}
|
||||
|
||||
match args[3].parse::<u64>() {
|
||||
Ok(value) => {
|
||||
base_counter_val = Some(value);
|
||||
}
|
||||
Err(_) => {}
|
||||
}
|
||||
|
||||
if args.len() <= 4 {break;}
|
||||
|
||||
match args[4].parse::<u64>() {
|
||||
Ok(value) => {
|
||||
sample_num = Some(value);
|
||||
}
|
||||
Err(_) => {}
|
||||
}
|
||||
|
||||
if args.len() <= 5 {break;}
|
||||
|
||||
basetime = Some(args[5].to_string());
|
||||
|
||||
if args.len() <= 6 {break;}
|
||||
|
||||
basedate = Some(args[6].to_string());
|
||||
|
||||
break;
|
||||
}
|
||||
Ok(Record {
|
||||
name: name,
|
||||
seg_num: seg_num,
|
||||
signal_count: sig_count,
|
||||
sampling_freq: sampling_freq,
|
||||
counter_freq: counter_freq,
|
||||
base_counter_val: base_counter_val,
|
||||
sample_num: sample_num,
|
||||
basetime: basetime,
|
||||
basedate: basedate
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, Hash, PartialEq)]
|
||||
enum AdcBlockKeys {
|
||||
Gain,
|
||||
Baseline,
|
||||
Units
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct SignalSpec {
|
||||
filename: String,
|
||||
format: SignalFormat,
|
||||
samples_frame: Option<u64>,
|
||||
skew: Option<u64>,
|
||||
offset: Option<u64>,
|
||||
adc_gain: Option<f64>,
|
||||
baseline: Option<i64>,
|
||||
units: Option<String>,
|
||||
adc_resolution: Option<u64>,
|
||||
adc_zero: i64,
|
||||
initial_val: Option<i64>,
|
||||
checksum: Option<i64>,
|
||||
blocksize: Option<u64>,
|
||||
desc: Option<String>
|
||||
}
|
||||
|
||||
impl SignalSpec {
|
||||
/// Attempts to generate a valid signal specification struct from a supplied string.
|
||||
/// Returns a `Result<SignalSpec, &str>` containing possible error information.
|
||||
///
|
||||
/// ## Arguments
|
||||
/// - `argument_line` - argument line for the signal specification, taken from header file
|
||||
pub fn from_str(argument_line: &str) -> Result<SignalSpec, &str> {
|
||||
let args: Vec<&str> = argument_line.split(' ').collect();
|
||||
|
||||
if args.len() < 2 {
|
||||
return Err("error: signal provided by header file lacks sufficient arguments.");
|
||||
}
|
||||
let name = args[0].to_string();
|
||||
let sigformat: SignalFormat;
|
||||
// Optional args
|
||||
let samples_frame: Option<u64> = None;
|
||||
let skew: Option<u64> = None;
|
||||
let offset: Option<u64> = None;
|
||||
let mut adc_gain: Option<f64> = None;
|
||||
let mut baseline: Option<i64> = None;
|
||||
let mut units: Option<String> = None;
|
||||
let mut adc_resolution: Option<u64> = None;
|
||||
let mut adc_zero:i64 = 0;
|
||||
let mut initial_val: Option<i64> = None;
|
||||
let mut checksum: Option<i64> = None;
|
||||
let mut blocksize: Option<u64> = None;
|
||||
let mut desc: Option<String> = None;
|
||||
|
||||
// TODO: implement samplesperframe, skew, and offset
|
||||
match args[1].parse::<u64>() {
|
||||
Ok(value) => {
|
||||
sigformat = SignalSpec::parse_format(value);
|
||||
},
|
||||
Err(_) => {
|
||||
return Err("error: unable to parse format from signal");
|
||||
}
|
||||
}
|
||||
|
||||
loop {
|
||||
if args.len() <= 2 {
|
||||
break;
|
||||
}
|
||||
|
||||
{
|
||||
let mut results: HashMap<AdcBlockKeys, String> = HashMap::new();
|
||||
let mut buffer: String = String::new();
|
||||
let mut found_baseline = false;
|
||||
let mut found_units = false;
|
||||
|
||||
// Parse through all the characters and get the individual parts
|
||||
for character in args[2].chars() {
|
||||
loop {
|
||||
if !found_baseline && !found_units && character == '(' {
|
||||
found_baseline = true;
|
||||
results.insert(AdcBlockKeys::Gain, buffer);
|
||||
buffer = String::new();
|
||||
break;
|
||||
}
|
||||
else if found_baseline && character == ')' {
|
||||
results.insert(AdcBlockKeys::Baseline, buffer);
|
||||
buffer = String::new();
|
||||
break;
|
||||
}
|
||||
else if !found_units && character == '/' {
|
||||
found_units = true;
|
||||
if !found_baseline {
|
||||
results.insert(AdcBlockKeys::Gain, buffer);
|
||||
}
|
||||
buffer = String::new();
|
||||
break;
|
||||
}
|
||||
buffer.push(character);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if found_units {
|
||||
results.insert(AdcBlockKeys::Units, buffer);
|
||||
}
|
||||
|
||||
// Try and get the actual values out of the results
|
||||
match results.get(&AdcBlockKeys::Gain) {
|
||||
Some(value ) => {
|
||||
match value.parse::<f64>() {
|
||||
Ok(parsed) => {
|
||||
adc_gain = Some(parsed);
|
||||
},
|
||||
Err(_) => {
|
||||
return Err("error: Signal contains adc gain block with invalid entry");
|
||||
}
|
||||
}
|
||||
},
|
||||
None => {
|
||||
return Err("error: Signal contains adc gain block but the block is empty.");
|
||||
}
|
||||
}
|
||||
|
||||
match results.get(&AdcBlockKeys::Baseline) {
|
||||
Some(value) => {
|
||||
match value.parse::<i64>() {
|
||||
Ok(parsed) => {
|
||||
baseline = Some(parsed);
|
||||
},
|
||||
Err(_) => {
|
||||
return Err("error: Unable to parse baseline from signal");
|
||||
}
|
||||
}
|
||||
},
|
||||
None => {}
|
||||
}
|
||||
match results.get(&AdcBlockKeys::Units) {
|
||||
Some(value) => {
|
||||
units = Some(value.clone());
|
||||
},
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
|
||||
if args.len() <= 3 {
|
||||
break;
|
||||
}
|
||||
match args[3].parse::<u64>() {
|
||||
Ok(value) => {
|
||||
adc_resolution = Some(value);
|
||||
},
|
||||
Err(_) => {
|
||||
return Err("error: unable to parse resolution from signal");
|
||||
}
|
||||
}
|
||||
|
||||
if args.len() <= 4 {
|
||||
break;
|
||||
}
|
||||
match args[4].parse::<i64>() {
|
||||
Ok(value) => {
|
||||
adc_zero = value;
|
||||
},
|
||||
Err(_) => {
|
||||
adc_zero = 0; // Standard: If this field is missing, a value of zero is assumed.
|
||||
}
|
||||
}
|
||||
|
||||
if args.len() <= 5 {
|
||||
break;
|
||||
}
|
||||
match args[5].parse::<i64>() {
|
||||
Ok(value) => {
|
||||
initial_val = Some(value);
|
||||
},
|
||||
Err(_) => {} // Standard: If this field is missing, adc_zero value is presumed.
|
||||
// We do this on the data parsing part.
|
||||
}
|
||||
|
||||
if args.len() <= 6 {
|
||||
break;
|
||||
}
|
||||
match args[6].parse::<i64>() {
|
||||
Ok(value) => {
|
||||
checksum = Some(value);
|
||||
},
|
||||
Err(_) => {
|
||||
return Err("error: unable to parse checksum from signal");
|
||||
}
|
||||
}
|
||||
|
||||
if args.len() <= 7 {
|
||||
break;
|
||||
}
|
||||
match args[7].parse::<u64>() {
|
||||
Ok(value) => {
|
||||
blocksize = Some(value);
|
||||
},
|
||||
Err(_) => {
|
||||
return Err("error: unable to parse block size from signal");
|
||||
}
|
||||
}
|
||||
|
||||
if args.len() <= 8 {
|
||||
break;
|
||||
}
|
||||
desc = Some(args[8].to_string());
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
Ok(
|
||||
SignalSpec { filename: name,
|
||||
format: sigformat,
|
||||
samples_frame: samples_frame,
|
||||
skew: skew, offset: offset,
|
||||
adc_gain: adc_gain,
|
||||
baseline: baseline,
|
||||
units: units,
|
||||
adc_resolution: adc_resolution,
|
||||
adc_zero: adc_zero,
|
||||
initial_val: initial_val,
|
||||
checksum: checksum,
|
||||
blocksize: blocksize,
|
||||
desc: desc }
|
||||
)
|
||||
}
|
||||
|
||||
fn parse_format(formatnum: u64) -> SignalFormat {
|
||||
match formatnum {
|
||||
16 => SignalFormat::Format16,
|
||||
212 => SignalFormat::Format212,
|
||||
0..=u64::MAX => SignalFormat::Unimpl
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Header {
|
||||
record: Option<Record>,
|
||||
signal_specs: Vec<SignalSpec>
|
||||
}
|
||||
|
||||
impl Header {
|
||||
/// Creates a completely empty, not fully initialized header
|
||||
///
|
||||
/// This is a workaround because I couldn't figure out how to make the compiler
|
||||
/// accept non-initializing the value for the first pass of a for loop in
|
||||
/// `parse_header()`
|
||||
fn new() -> Header {
|
||||
Header { record: None, signal_specs: vec![] }
|
||||
}
|
||||
|
||||
fn from_record(record: Record) -> Header {
|
||||
Header { record: Some(record), signal_specs: vec![] }
|
||||
}
|
||||
|
||||
fn add_signal_spec(&mut self, spec: SignalSpec) {
|
||||
self.signal_specs.push(spec);
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
match self.record {
|
||||
Some(_) => false,
|
||||
None => true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempts to parse the header file
|
||||
pub fn parse_header(header_data: &str) -> Result<Header, &str> {
|
||||
let header_lines: Vec<&str> = header_data.split("\n").collect();
|
||||
let mut found_record: bool = false;
|
||||
let mut header: Header = Header::new();
|
||||
let mut specs_read: u64 = 0;
|
||||
let mut specs_max: u64 = 0;
|
||||
for line in header_lines {
|
||||
// Ignore commented lines
|
||||
if line.starts_with("#") {
|
||||
continue;
|
||||
}
|
||||
|
||||
if !found_record {
|
||||
let possible_record = Record::from_str(line);
|
||||
match possible_record {
|
||||
Ok(rec) => {
|
||||
specs_max = rec.signal_count;
|
||||
header = Header::from_record(rec);
|
||||
found_record = true;
|
||||
continue;
|
||||
}
|
||||
Err(e) => {
|
||||
return Err(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let possible_spec = SignalSpec::from_str(line);
|
||||
match possible_spec {
|
||||
Ok(spec) => {
|
||||
header.add_signal_spec(spec);
|
||||
}
|
||||
Err(e) => {
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
|
||||
specs_read += 1;
|
||||
if specs_read >= specs_max {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if header.is_empty() {
|
||||
return Err("Unable to parse valid header information");
|
||||
}
|
||||
Ok(header)
|
||||
}
|
||||
Reference in New Issue
Block a user