diff --git a/src/formats/ncbi.rs b/src/formats/ncbi.rs index c3b8522..cb886ef 100644 --- a/src/formats/ncbi.rs +++ b/src/formats/ncbi.rs @@ -83,12 +83,9 @@ pub fn load>(ncbi_directory: P) -> TaxonomyResult, X: Taxonomy<'t, T>>( - tax: &'t X, - out_dir: P, -) -> TaxonomyResult<()> +pub fn save<'t, T, P: AsRef, X: Taxonomy<'t, T>>(tax: &'t X, out_dir: P) -> TaxonomyResult<()> where - T: Clone + Debug + Display + PartialEq, + T: 't + Clone + Debug + Display + PartialEq, { let dir = out_dir.as_ref(); std::fs::create_dir_all(dir)?; @@ -134,7 +131,7 @@ mod tests { let nodes = "1\t|\t1\t|\tno rank\t|\t\t|\t8\t|\t0\t|\t1\t|\t0\t|\t0\t|\t0\t|\t0\t|\t0\t|\t\t| 10239\t|\t1\t|\tno_rank\t|\t\t|\t9\t|\t0\t|\t1\t|\t0\t|\t0\t|\t0\t|\t0\t|\t0\t|\t\t| - 2\t|\t131567\t|\tsuperkingdom\t|\t\t|\t0\t|\t0\t|\t11\t|\t0\t|\t0\t|\t0\t|\t0\t|\t0\t|\t\t| + 2\t|\t131567\t|\tdomain\t|\t\t|\t0\t|\t0\t|\t11\t|\t0\t|\t0\t|\t0\t|\t0\t|\t0\t|\t\t| 543\t|\t91347\t|\tfamily\t|\t\t|\t0\t|\t1\t|\t11\t|\t1\t|\t0\t|\t1\t|\t0\t|\t0\t|\t\t| 561\t|\t543\t|\tgenus\t|\t\t|\t0\t|\t1\t|\t11\t|\t1\t|\t0\t|\t1\t|\t0\t|\t0\t|\t\t| 562\t|\t561\t|\tspecies\t|\tEC\t|\t0\t|\t1\t|\t11\t|\t1\t|\t0\t|\t1\t|\t1\t|\t0\t|\t\t| @@ -239,6 +236,9 @@ mod tests { Some(("561", 1.)) ); + // Check Bacteria (2) keeps its "domain" rank through save/load + assert_eq!(Taxonomy::<&str>::rank(&tax2, "2").unwrap(), TaxRank::Domain); + // Check Escherichia (561) assert_eq!(Taxonomy::<&str>::name(&tax2, "561").unwrap(), "Escherichia"); assert_eq!( diff --git a/src/rank.rs b/src/rank.rs index 193ec2c..c0f27ce 100644 --- a/src/rank.rs +++ b/src/rank.rs @@ -111,61 +111,115 @@ pub enum TaxRank { } impl TaxRank { - /// Converts a TaxRank into a one of the rank strings NCBI uses. - /// Note that this doesn't handle ranks that are not used by the NCBI taxonomy. + /// Converts a TaxRank into the rank string NCBI uses (or, for ranks NCBI + /// doesn't define, our own lowercase spelling). Never coerces to "no rank". pub fn to_ncbi_rank(self) -> &'static str { + self.as_str() + } + + pub fn as_str(self) -> &'static str { match self { - TaxRank::Superkingdom => "superkingdom", + TaxRank::Domain => "domain", TaxRank::AcellularRoot => "acellular root", TaxRank::CellularRoot => "cellular root", + TaxRank::Subdomain => "subdomain", + TaxRank::Realm => "realm", + TaxRank::Subrealm => "subrealm", + TaxRank::Hyperkingdom => "hyperkingdom", + TaxRank::Superkingdom => "superkingdom", TaxRank::Kingdom => "kingdom", TaxRank::Subkingdom => "subkingdom", + TaxRank::Infrakingdom => "infrakingdom", + TaxRank::Parvkingdom => "parvkingdom", TaxRank::Superphylum => "superphylum", TaxRank::Phylum => "phylum", TaxRank::Subphylum => "subphylum", + TaxRank::Infraphylum => "infraphylum", + TaxRank::Microphylum => "microphylum", TaxRank::Superclass => "superclass", TaxRank::Class => "class", TaxRank::Subclass => "subclass", TaxRank::Infraclass => "infraclass", + TaxRank::Parvclass => "parvclass", + TaxRank::Superdivision => "superdivision", + TaxRank::Division => "division", + TaxRank::Subdivision => "subdivision", + TaxRank::Infradivision => "infradivision", + TaxRank::Superlegion => "superlegion", + TaxRank::Legion => "legion", + TaxRank::Sublegion => "sublegion", + TaxRank::Infralegion => "infralegion", + TaxRank::Supercohort => "supercohort", TaxRank::Cohort => "cohort", TaxRank::Subcohort => "subcohort", + TaxRank::Infracohort => "infracohort", TaxRank::Superorder => "superorder", + TaxRank::Gigaorder => "gigaorder", + TaxRank::Magnorder => "magnorder", + TaxRank::Grandorder => "grandorder", + TaxRank::Mirorder => "mirorder", + TaxRank::SeriesFish => "series fish", TaxRank::Order => "order", + TaxRank::Nanorder => "nanorder", + TaxRank::Hypoorder => "hypoorder", + TaxRank::Minorder => "minorder", TaxRank::Suborder => "suborder", TaxRank::Infraorder => "infraorder", TaxRank::Parvorder => "parvorder", + TaxRank::Section => "section", + TaxRank::Subsection => "subsection", + TaxRank::Gigafamily => "gigafamily", + TaxRank::Megafamily => "megafamily", + TaxRank::Grandfamily => "grandfamily", + TaxRank::Hyperfamily => "hyperfamily", TaxRank::Superfamily => "superfamily", + TaxRank::Epifamily => "epifamily", + TaxRank::SeriesLepidoptera => "series lepidoptera", + TaxRank::GroupLepidoptera => "group lepidoptera", TaxRank::Family => "family", TaxRank::Subfamily => "subfamily", + TaxRank::Infrafamily => "infrafamily", + TaxRank::Supertribe => "supertribe", TaxRank::Tribe => "tribe", TaxRank::Subtribe => "subtribe", + TaxRank::Infratribe => "infratribe", TaxRank::Genus => "genus", - TaxRank::Series => "series", TaxRank::Subgenus => "subgenus", + TaxRank::Series => "series", + TaxRank::SubseriesBotany => "subseries botany", TaxRank::SpeciesGroup => "species group", TaxRank::SpeciesSubgroup => "species subgroup", TaxRank::Species => "species", TaxRank::Subspecies => "subspecies", - TaxRank::Strain => "strain", - TaxRank::Synthetic => "synthetic", TaxRank::Varietas => "varietas", + TaxRank::Subvarietas => "subvarietas", TaxRank::Forma => "forma", - TaxRank::Unspecified => "no rank", - TaxRank::Clade => "clade", + TaxRank::Subforma => "subforma", + TaxRank::Cultivar => "cultivar", + TaxRank::Breed => "breed", + TaxRank::Strain => "strain", + TaxRank::Individual => "individual", TaxRank::SeroGroup => "serogroup", + TaxRank::Unspecified => "no rank", TaxRank::Biotype => "biotype", + TaxRank::Clade => "clade", TaxRank::FormaSpecialis => "forma specialis", TaxRank::Isolate => "isolate", TaxRank::Serotype => "serotype", TaxRank::Genotype => "genotype", TaxRank::Morph => "morph", TaxRank::Pathogroup => "pathogroup", - // TODO: not sure if we want to manually coerce everything like this? - _ => "no rank", + TaxRank::Synthetic => "synthetic", } } } +impl fmt::Display for TaxRank { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.as_str()) + } +} + impl FromStr for TaxRank { type Err = Error; @@ -195,8 +249,10 @@ impl FromStr for TaxRank { "subclass" | "subclassis" => Ok(TaxRank::Subclass), "infraclass" => Ok(TaxRank::Infraclass), "parvclass" => Ok(TaxRank::Parvclass), - // note "division" may be either a TaxRank::Division (zoological) - // or synonym for TaxRank::Phylum (botanical) so we don't impl here + "superdivision" => Ok(TaxRank::Superdivision), + "division" => Ok(TaxRank::Division), + "subdivision" => Ok(TaxRank::Subdivision), + "infradivision" => Ok(TaxRank::Infradivision), "superlegion" => Ok(TaxRank::Superlegion), "legion" => Ok(TaxRank::Legion), "sublegion" => Ok(TaxRank::Sublegion), @@ -210,6 +266,7 @@ impl FromStr for TaxRank { "magnorder" => Ok(TaxRank::Magnorder), "grandorder" => Ok(TaxRank::Grandorder), "mirorder" => Ok(TaxRank::Mirorder), + "series fish" => Ok(TaxRank::SeriesFish), "order" | "ordo" => Ok(TaxRank::Order), "nanorder" => Ok(TaxRank::Nanorder), "hypoorder" => Ok(TaxRank::Hypoorder), @@ -225,6 +282,8 @@ impl FromStr for TaxRank { "hyperfamily" => Ok(TaxRank::Hyperfamily), "superfamily" => Ok(TaxRank::Superfamily), "epifamily" => Ok(TaxRank::Epifamily), + "series lepidoptera" => Ok(TaxRank::SeriesLepidoptera), + "group lepidoptera" => Ok(TaxRank::GroupLepidoptera), "family" | "familia" => Ok(TaxRank::Family), "subfamily" => Ok(TaxRank::Subfamily), "infrafamily" => Ok(TaxRank::Infrafamily), @@ -235,6 +294,7 @@ impl FromStr for TaxRank { "genus" | "genera" => Ok(TaxRank::Genus), "subgenus" => Ok(TaxRank::Subgenus), "series" => Ok(TaxRank::Series), + "subseries botany" => Ok(TaxRank::SubseriesBotany), "species group" => Ok(TaxRank::SpeciesGroup), "species subgroup" => Ok(TaxRank::SpeciesSubgroup), "species" => Ok(TaxRank::Species), @@ -247,6 +307,7 @@ impl FromStr for TaxRank { "cultivar" => Ok(TaxRank::Cultivar), "breed" => Ok(TaxRank::Breed), "strain" => Ok(TaxRank::Strain), + "individual" => Ok(TaxRank::Individual), "serogroup" => Ok(TaxRank::SeroGroup), "no rank" | "no_rank" => Ok(TaxRank::Unspecified), "biotype" => Ok(TaxRank::Biotype), @@ -262,105 +323,6 @@ impl FromStr for TaxRank { } } -impl fmt::Display for TaxRank { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let rank_str = match self { - TaxRank::Domain => "domain", - TaxRank::AcellularRoot => "acellular root", - TaxRank::CellularRoot => "cellular root", - TaxRank::Subdomain => "subdomain", - TaxRank::Realm => "realm", - TaxRank::Subrealm => "subrealm", - TaxRank::Hyperkingdom => "hyperkingdom", - TaxRank::Superkingdom => "superkingdom", - TaxRank::Kingdom => "kingdom", - TaxRank::Subkingdom => "subkingdom", - TaxRank::Infrakingdom => "infrakingdom", - TaxRank::Parvkingdom => "parvkingdom", - TaxRank::Superphylum => "superphylum", - TaxRank::Phylum => "phylum", - TaxRank::Subphylum => "subphylum", - TaxRank::Infraphylum => "infraphylum", - TaxRank::Microphylum => "microphylum", - TaxRank::Superclass => "superclass", - TaxRank::Class => "class", - TaxRank::Subclass => "subclass", - TaxRank::Infraclass => "infraclass", - TaxRank::Parvclass => "parvclass", - TaxRank::Superdivision => "superdivision", - TaxRank::Division => "division", - TaxRank::Subdivision => "subdivision", - TaxRank::Infradivision => "infradivision", - TaxRank::Superlegion => "superlegion", - TaxRank::Legion => "legion", - TaxRank::Sublegion => "sublegion", - TaxRank::Infralegion => "infralegion", - TaxRank::Supercohort => "supercohort", - TaxRank::Cohort => "cohort", - TaxRank::Subcohort => "subcohort", - TaxRank::Infracohort => "infracohort", - TaxRank::Superorder => "superorder", - TaxRank::Gigaorder => "gigaorder", - TaxRank::Magnorder => "magnorder", - TaxRank::Grandorder => "grandorder", - TaxRank::Mirorder => "mirorder", - TaxRank::SeriesFish => "series fish", - TaxRank::Order => "order", - TaxRank::Nanorder => "nanorder", - TaxRank::Hypoorder => "hypoorder", - TaxRank::Minorder => "minorder", - TaxRank::Suborder => "suborder", - TaxRank::Infraorder => "infraorder", - TaxRank::Parvorder => "parvorder", - TaxRank::Section => "section", - TaxRank::Subsection => "subsection", - TaxRank::Gigafamily => "gigafamily", - TaxRank::Megafamily => "megafamily", - TaxRank::Grandfamily => "grandfamily", - TaxRank::Hyperfamily => "hyperfamily", - TaxRank::Superfamily => "superfamily", - TaxRank::Epifamily => "epifamily", - TaxRank::SeriesLepidoptera => "series lepidoptera", - TaxRank::GroupLepidoptera => "group lepidoptera", - TaxRank::Family => "family", - TaxRank::Subfamily => "subfamily", - TaxRank::Infrafamily => "infrafamily", - TaxRank::Supertribe => "supertribe", - TaxRank::Tribe => "tribe", - TaxRank::Subtribe => "subtribe", - TaxRank::Infratribe => "infratribe", - TaxRank::Genus => "genus", - TaxRank::Subgenus => "subgenus", - TaxRank::Series => "series", - TaxRank::SubseriesBotany => "subseries botany", - TaxRank::SpeciesGroup => "species group", - TaxRank::SpeciesSubgroup => "species subgroup", - TaxRank::Species => "species", - TaxRank::Subspecies => "subspecies", - TaxRank::Varietas => "varietas", - TaxRank::Subvarietas => "subvarietas", - TaxRank::Forma => "forma", - TaxRank::Subforma => "subforma", - TaxRank::Cultivar => "cultivar", - TaxRank::Breed => "breed", - TaxRank::Strain => "strain", - TaxRank::Individual => "individual", - TaxRank::SeroGroup => "serogroup", - TaxRank::Unspecified => "no rank", - TaxRank::Biotype => "biotype", - TaxRank::Clade => "clade", - TaxRank::FormaSpecialis => "forma specialis", - TaxRank::Isolate => "isolate", - TaxRank::Serotype => "serotype", - TaxRank::Genotype => "genotype", - TaxRank::Morph => "morph", - TaxRank::Pathogroup => "pathogroup", - TaxRank::Synthetic => "synthetic", - }; - write!(f, "{}", rank_str) - } -} - #[cfg(test)] mod test { use super::TaxRank; @@ -461,7 +423,7 @@ mod test { #[test] fn test_str_to_rank() { for rank in RANKS.iter() { - assert!(TaxRank::from_str(rank.to_ncbi_rank()).is_ok()); + assert_eq!(TaxRank::from_str(rank.to_ncbi_rank()).unwrap(), *rank); } assert!(TaxRank::from_str("fake_data").is_err()); } @@ -471,11 +433,11 @@ mod test { for rank in RANKS.iter() { let rank_str = rank.to_string(); assert!(!rank_str.is_empty()); - - let ncbi_str = rank.to_ncbi_rank(); - if ncbi_str != "no rank" { - assert_eq!(ncbi_str, rank_str); + assert_eq!(rank.to_ncbi_rank(), rank_str); + if *rank != Unspecified { + assert_ne!(rank_str, "no rank"); } } + assert_eq!(Domain.to_ncbi_rank(), "domain"); } }