#![allow(deprecated)]
mod helpers;
mod parsing;
#[cfg(test)]
mod tests;
use parsing::*;
use crate::{byteorder::ByteOrder, datatype::FcsDataType};
use serde::{Deserialize, Serialize};
use std::{borrow::Cow, hash::Hash, sync::Arc};
use strum_macros::Display;
#[derive(Debug)]
pub enum KeywordCreationResult {
Int(IntegerKeyword),
Float(FloatKeyword),
String(StringKeyword),
Byte(ByteKeyword),
Mixed(MixedKeyword),
UnableToParse,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, Display)]
pub enum Keyword {
Int(IntegerKeyword),
Float(FloatKeyword),
String(StringKeyword),
Byte(ByteKeyword),
Mixed(MixedKeyword),
}
type LowerBound = f32;
type UpperBound = f32;
#[derive(Clone, Debug, Display, Serialize, Deserialize, PartialEq)]
#[allow(deprecated)]
pub enum MixedKeyword {
PnCalibration(f32, String),
PnD(String, LowerBound, UpperBound),
PnE(f32, f32),
GnE(f32, f32),
RnW(Vec<f32>),
SPILLOVER {
n_parameters: usize,
parameter_names: Vec<String>,
matrix_values: Vec<f32>,
},
PnL(Vec<usize>),
}
impl StringableKeyword for MixedKeyword {
fn get_str(&self) -> Cow<'_, str> {
match self {
Self::PnCalibration(f1, s) => Cow::Owned(format!("PnCalibration({}, {})", f1, s)),
Self::PnD(s, f1, f2) => Cow::Owned(format!("PnD({}, {}, {})", s, f1, f2)),
Self::PnE(f1, f2) => Cow::Owned(format!("PnE({}, {})", f1, f2)),
Self::GnE(f1, f2) => Cow::Owned(format!("GnE({}, {})", f1, f2)),
Self::PnL(vec) => Cow::Owned(format!(
"PnL({})",
vec.iter()
.map(|v| v.to_string())
.collect::<Vec<_>>()
.join(", ")
)),
Self::RnW(vec) => Cow::Owned(format!(
"RnW({})",
vec.iter()
.map(|v| v.to_string())
.collect::<Vec<_>>()
.join(", ")
)),
Self::SPILLOVER {
n_parameters,
parameter_names,
matrix_values,
} => Cow::Owned(format!(
"SPILLOVER({}, {}, {})",
n_parameters,
parameter_names.join(", "),
matrix_values
.iter()
.map(|v| v.to_string())
.collect::<Vec<_>>()
.join(", ")
)),
}
}
}
impl Eq for MixedKeyword {}
impl Hash for MixedKeyword {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
match self {
Self::PnCalibration(f1, s) => {
f1.to_bits().hash(state);
s.hash(state);
}
Self::PnD(s, f1, f2) => {
s.hash(state);
f1.to_bits().hash(state);
f2.to_bits().hash(state);
}
Self::PnE(f1, f2) | Self::GnE(f1, f2) => {
f1.to_bits().hash(state);
f2.to_bits().hash(state);
}
Self::PnL(vec) => {
for v in vec {
v.hash(state);
}
}
Self::RnW(vec) => {
for f in vec {
f.to_bits().hash(state);
}
}
Self::SPILLOVER {
n_parameters,
parameter_names,
matrix_values,
} => {
n_parameters.hash(state);
parameter_names.hash(state);
for f in matrix_values {
f.to_bits().hash(state);
}
}
}
}
}
#[derive(Clone, Debug, Display, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub enum IntegerKeyword {
BeginData(usize),
EndData(usize),
BeginAnalysis(usize),
EndAnalysis(usize),
BeginText(usize),
EndText(usize),
PAR(usize),
TOT(usize),
PnR(usize),
PnB(usize),
PnV(usize),
PnL(usize),
PnDisplay(usize),
}
#[derive(Clone, Debug, Display, Serialize, Deserialize, PartialEq)]
pub enum FloatKeyword {
PnG(f32),
}
impl Eq for FloatKeyword {}
impl Hash for FloatKeyword {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
match self {
FloatKeyword::PnG(f) => f.to_bits().hash(state),
}
}
}
#[derive(Clone, Debug, Display, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub enum StringKeyword {
CYT(Arc<str>),
FIL(Arc<str>),
GUID(Arc<str>),
BEGINDATETIME(Arc<str>),
ENDDATETIME(Arc<str>),
CARRIERID(Arc<str>),
CARRIERTYPE(Arc<str>),
LOCATIONID(Arc<str>),
PnN(Arc<str>),
PnS(Arc<str>),
PnF(Arc<str>),
PnType(Arc<str>),
PnDISPLAY(Arc<str>),
PnDET(Arc<str>),
PnTAG(Arc<str>),
PnANALYTE(Arc<str>),
PnFEATURE(Arc<str>),
FLOWRATE(Arc<str>),
VOL(Arc<str>),
ORIGINALITY(Arc<str>),
LastModifier(Arc<str>),
LastModified(Arc<str>),
DATE(Arc<str>),
BTIM(Arc<str>),
ETIM(Arc<str>),
MODE(Arc<str>),
PLATEID(Arc<str>),
PLATENAME(Arc<str>),
WELLID(Arc<str>),
GATE(Arc<str>),
GnF(Arc<str>),
GnN(Arc<str>),
GnP(Arc<str>),
GnR(Arc<str>),
GnS(Arc<str>),
GnT(Arc<str>),
GnV(Arc<str>),
Other(Arc<str>),
}
#[derive(Clone, Debug, Display, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub enum ByteKeyword {
BYTEORD(ByteOrder),
DATATYPE(FcsDataType),
PnDATATYPE(FcsDataType),
}
pub trait StringableKeyword {
fn get_str(&self) -> Cow<'_, str>;
}
pub trait IntegerableKeyword {
fn get_usize(&self) -> &usize;
}
#[allow(unused)]
pub trait FloatableKeyword {
fn get_f32(&self) -> &f32;
}
impl IntegerableKeyword for IntegerKeyword {
fn get_usize(&self) -> &usize {
match self {
Self::TOT(value)
| Self::BeginData(value)
| Self::EndData(value)
| Self::BeginAnalysis(value)
| Self::EndAnalysis(value)
| Self::BeginText(value)
| Self::EndText(value)
| Self::PnR(value)
| Self::PnB(value)
| Self::PnV(value)
| Self::PnL(value)
| Self::PnDisplay(value)
| Self::PAR(value) => value,
}
}
}
impl FloatableKeyword for FloatKeyword {
fn get_f32(&self) -> &f32 {
match self {
Self::PnG(value) => value,
}
}
}
impl StringableKeyword for StringKeyword {
fn get_str(&self) -> Cow<'_, str> {
match self {
Self::CYT(value)
| Self::FIL(value)
| Self::GUID(value)
| Self::BEGINDATETIME(value)
| Self::ENDDATETIME(value)
| Self::CARRIERID(value)
| Self::CARRIERTYPE(value)
| Self::LOCATIONID(value)
| Self::PnN(value)
| Self::PnS(value)
| Self::PnF(value)
| Self::PnType(value)
| Self::PnDISPLAY(value)
| Self::PnDET(value)
| Self::PnTAG(value)
| Self::PnANALYTE(value)
| Self::PnFEATURE(value)
| Self::FLOWRATE(value)
| Self::VOL(value)
| Self::ORIGINALITY(value)
| Self::LastModifier(value)
| Self::LastModified(value)
| Self::DATE(value)
| Self::BTIM(value)
| Self::ETIM(value)
| Self::MODE(value)
| Self::PLATEID(value)
| Self::PLATENAME(value)
| Self::WELLID(value)
| Self::GATE(value)
| Self::GnF(value)
| Self::GnN(value)
| Self::GnP(value)
| Self::GnR(value)
| Self::GnS(value)
| Self::GnT(value)
| Self::GnV(value)
| Self::Other(value) => Cow::Borrowed(value.as_ref()),
}
}
}
impl StringableKeyword for ByteKeyword {
fn get_str(&self) -> Cow<'_, str> {
match self {
Self::DATATYPE(data_type) | Self::PnDATATYPE(data_type) => {
Cow::Borrowed(data_type.to_keyword_str())
}
Self::BYTEORD(byte_order) => Cow::Borrowed(byte_order.to_keyword_str()),
}
}
}
impl StringableKeyword for IntegerKeyword {
fn get_str(&self) -> Cow<'_, str> {
match self {
Self::BeginData(value)
| Self::EndData(value)
| Self::BeginAnalysis(value)
| Self::EndAnalysis(value)
| Self::BeginText(value)
| Self::EndText(value)
| Self::PAR(value)
| Self::TOT(value)
| Self::PnR(value)
| Self::PnB(value)
| Self::PnV(value)
| Self::PnL(value)
| Self::PnDisplay(value) => Cow::Owned(value.to_string()),
}
}
}
impl StringableKeyword for FloatKeyword {
fn get_str(&self) -> Cow<'_, str> {
match self {
Self::PnG(value) => Cow::Owned(value.to_string()),
}
}
}
pub fn match_and_parse_keyword(key: &str, value: &str) -> KeywordCreationResult {
let dollarless_key = if let Some(key) = key.strip_prefix('$') {
key
} else if key == "GUID" {
"GUID"
} else {
return KeywordCreationResult::String(StringKeyword::Other(Arc::from(value.trim())));
};
parse_fixed_keywords(dollarless_key, value)
.or_else(|| parse_parameter_keywords(dollarless_key, value))
.or_else(|| parse_gate_keywords(dollarless_key, value))
.or_else(|| parse_region_keywords(dollarless_key, value))
.unwrap_or_else(|| {
KeywordCreationResult::String(StringKeyword::Other(Arc::from(value.trim())))
})
}
impl From<&StringKeyword> for Arc<str> {
fn from(keyword: &StringKeyword) -> Self {
keyword.get_str().into()
}
}
impl From<&IntegerKeyword> for String {
fn from(keyword: &IntegerKeyword) -> Self {
keyword.get_usize().to_string()
}
}