use legume_numeric::matrix::parquet::{
read_parquet_string_columns_by_name, write_named_table, Column,
};
use std::path::Path;
pub const GENE_TYPE: &str = "gene";
pub const TERM_TYPE: &str = "term";
pub const REGION_TYPE: &str = "region";
pub const WORD_TYPE: &str = "word";
pub fn feature_types_path(prefix: &str) -> String {
format!("{prefix}.feature_types.parquet")
}
pub fn write_feature_types(
prefix: &str,
names: &[Box<str>],
types: &[Box<str>],
) -> anyhow::Result<()> {
anyhow::ensure!(
names.len() == types.len(),
"feature types: {} names for {} types",
names.len(),
types.len()
);
write_named_table(
&feature_types_path(prefix),
"feature",
names,
&[(Box::from("type"), Column::Str(types))],
)
}
pub type FeatureType = (Box<str>, Box<str>);
pub fn read_feature_types(prefix: &str) -> anyhow::Result<Option<Vec<FeatureType>>> {
let path = feature_types_path(prefix);
if !Path::new(&path).exists() {
return Ok(None);
}
let mut cols = read_parquet_string_columns_by_name(&path, &["feature", "type"])?;
let types = cols.pop().expect("two columns requested");
let names = cols.pop().expect("two columns requested");
Ok(Some(names.into_iter().zip(types).collect()))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn round_trip_and_absence() {
let dir = tempfile::tempdir().unwrap();
let prefix = dir.path().join("run").to_string_lossy().into_owned();
assert!(read_feature_types(&prefix).unwrap().is_none());
let names: Vec<Box<str>> = vec!["TP53".into(), "GO:1".into()];
let types: Vec<Box<str>> = vec![GENE_TYPE.into(), TERM_TYPE.into()];
write_feature_types(&prefix, &names, &types).unwrap();
let rows = read_feature_types(&prefix).unwrap().unwrap();
assert_eq!(
rows,
vec![
("TP53".into(), GENE_TYPE.into()),
("GO:1".into(), TERM_TYPE.into())
]
);
assert!(write_feature_types(&prefix, &names, &types[..1]).is_err());
}
}