use std::{fmt::Display, path::Path};
use context_error::{BasicKind, BoxedError};
use mzcore::{
ontology::Ontologies,
sequence::{Linked, SemiAmbiguous, SimpleLinear},
};
use mzcv::{Term, curie, term};
use serde::{Deserialize, Serialize};
use crate::*;
#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
#[allow(clippy::upper_case_acronyms, missing_docs)]
pub enum KnownFileFormat {
#[cfg(feature = "mzannotate")]
AnnotatedSpectrum,
BasicCSV(BasicCSVVersion),
DeepNovoFamily(DeepNovoFamilyVersion),
Fasta,
InstaNovo(InstaNovoVersion),
MaxQuant(MaxQuantVersion),
MetaMorpheus(MetaMorpheusVersion),
MzTab,
NovoB(NovoBVersion),
Novor(NovorVersion),
Opair(OpairVersion),
Peaks(PeaksVersion),
PepNet(PepNetVersion),
PiHelixNovo(PiHelixNovoVersion),
PiPrimeNovo(PiPrimeNovoVersion),
PLGS(PLGSVersion),
PLink(PLinkVersion),
PowerNovo(PowerNovoVersion),
Proteoscape(ProteoscapeVersion),
PUniFind(PUniFindVersion),
Sage(SageVersion),
MSFragger(MSFraggerVersion),
SpectrumSequenceList(SpectrumSequenceListVersion),
}
impl KnownFileFormat {
pub const fn name(self) -> &'static str {
match self {
#[cfg(feature = "mzannotate")]
Self::AnnotatedSpectrum => "mzSpecLib",
Self::BasicCSV(_) => "CSV",
Self::DeepNovoFamily(_) => "DeepNovo Family",
Self::Fasta => "Fasta",
Self::InstaNovo(_) => "InstaNovo",
Self::MaxQuant(_) => "MaxQuant",
Self::MetaMorpheus(_) => "MetaMorpheus",
Self::NovoB(_) => "NovoB",
Self::Novor(_) => "Novor",
Self::Opair(_) => "OPair",
Self::Peaks(_) => "PEAKS",
Self::PepNet(_) => "PepNet",
Self::MzTab => "mzTab",
Self::PiHelixNovo(_) => "Ï€-HelixNovo",
Self::PiPrimeNovo(_) => "Ï€-PrimeNovo",
Self::PLGS(_) => "ProteinLynx Global Server",
Self::PLink(_) => "pLink",
Self::PowerNovo(_) => "PowerNovo",
Self::Proteoscape(_) => "Proteoscape",
Self::PUniFind(_) => "pUniFind",
Self::Sage(_) => "Sage",
Self::MSFragger(_) => "MSFragger",
Self::SpectrumSequenceList(_) => "SpectrumSequenceList",
}
}
pub fn version(self) -> Option<String> {
match self {
#[cfg(feature = "mzannotate")]
Self::AnnotatedSpectrum => None,
Self::Fasta => None,
Self::BasicCSV(version) => Some(version.to_string()),
Self::DeepNovoFamily(version) => Some(version.to_string()),
Self::InstaNovo(version) => Some(version.to_string()),
Self::MaxQuant(version) => Some(version.to_string()),
Self::MetaMorpheus(version) => Some(version.to_string()),
Self::MzTab => Some("1.0".to_string()),
Self::NovoB(version) => Some(version.to_string()),
Self::Novor(version) => Some(version.to_string()),
Self::Opair(version) => Some(version.to_string()),
Self::Peaks(version) => Some(version.to_string()),
Self::PepNet(version) => Some(version.to_string()),
Self::PiHelixNovo(version) => Some(version.to_string()),
Self::PiPrimeNovo(version) => Some(version.to_string()),
Self::PLGS(version) => Some(version.to_string()),
Self::PLink(version) => Some(version.to_string()),
Self::PowerNovo(version) => Some(version.to_string()),
Self::Proteoscape(version) => Some(version.to_string()),
Self::PUniFind(version) => Some(version.to_string()),
Self::Sage(version) => Some(version.to_string()),
Self::MSFragger(version) => Some(version.to_string()),
Self::SpectrumSequenceList(version) => Some(version.to_string()),
}
}
pub const fn term(self) -> Option<Term> {
match self {
#[cfg(feature = "mzannotate")]
Self::AnnotatedSpectrum => None,
Self::Fasta => Some(term!(MS:1001348|FASTA format)),
Self::BasicCSV(_) => None,
Self::DeepNovoFamily(DeepNovoFamilyVersion::DeepNovoV0_0_1) => {
Some(term!(MS:1003996|DeepNovo))
}
Self::DeepNovoFamily(DeepNovoFamilyVersion::PointNovoFamily) => {
Some(term!(MS:1003998|PointNovo))
} Self::InstaNovo(
InstaNovoVersion::V1_0_0
| InstaNovoVersion::V1_1_0
| InstaNovoVersion::V1_1_4
| InstaNovoVersion::V1_2_2,
) => Some(term!(MS:1003612|InstaNovo)),
Self::InstaNovo(
InstaNovoVersion::PlusV1_1_4
| InstaNovoVersion::PlusV1_2_2
| InstaNovoVersion::CombinedV1_2_2,
) => Some(term!(MS:1003613|InstaNovo+)),
Self::MaxQuant(_) => Some(term!(MS:1001583|MaxQuant)),
Self::MetaMorpheus(_) => Some(term!(MS:1002826|MetaMorpheus)),
Self::MzTab => Some(term!(MS:1002601|mzTab)),
Self::NovoB(_) => Some(term!(MS:1004001|NovoB)),
Self::Novor(_) => Some(term!(MS:1002984|Novor)),
Self::Opair(OpairVersion::Opair) => Some(term!(MS:1003429|FragPipe)),
Self::Peaks(_) => Some(term!(MS:1001946|PEAKS Studio)),
Self::PepNet(_) => Some(term!(MS:1004002|PepNet)),
Self::PiHelixNovo(_) => Some(term!(MS:1004003|Ï€-HelixNovo)),
Self::PiPrimeNovo(_) => Some(term!(MS:1004004|Ï€-PrimeNovo)),
Self::PLGS(PLGSVersion::V3_0) => Some(term!(MS:1000601|ProteinLynx Global Server)),
Self::PLink(PLinkVersion::V2_3) => Some(term!(MS:1003432|pLink2)),
Self::PowerNovo(_) => Some(term!(MS:1004005|PowerNovo)),
Self::Proteoscape(_) => Some(term!(MS:1004009|ProteoScape)),
Self::PUniFind(_) => Some(term!(MS:1004006|pUniFind)),
Self::Sage(_) => Some(term!(MS:1004007|Sage)),
Self::MSFragger(MSFraggerVersion::V4_2) => Some(term!(MS:1003014|MSFragger)),
Self::MSFragger(MSFraggerVersion::Philosopher) => Some(term!(MS:1003018|Philosopher)),
Self::MSFragger(MSFraggerVersion::FragPipeV20Or21 | MSFraggerVersion::FragPipeV22) => {
Some(term!(MS:1003429|FragPipe))
}
Self::SpectrumSequenceList(_) => {
Some(term!(MS:1004008|BiblioSpec Spectrum Sequence List))
}
}
}
}
impl TryFrom<CVTerm> for KnownFileFormat {
type Error = ();
fn try_from(value: CVTerm) -> Result<Self, Self::Error> {
match value.term.accession {
curie!(MS:1001348|FASTA format) => Ok(Self::Fasta),
curie!(MS:1003996|DeepNovo) => {
Ok(Self::DeepNovoFamily(DeepNovoFamilyVersion::DeepNovoV0_0_1))
}
curie!(MS:1003998|PointNovo)
| curie!(MS:1003999|PGPointNovo)
| curie!(MS:1004000|BiATNovo) => {
Ok(Self::DeepNovoFamily(DeepNovoFamilyVersion::PointNovoFamily))
}
curie!(MS:1003612|InstaNovo) => {
for v in [
InstaNovoVersion::V1_0_0,
InstaNovoVersion::V1_1_0,
InstaNovoVersion::V1_1_4,
InstaNovoVersion::V1_2_2,
] {
if value.value.eq_ignore_ascii_case(v.name()) {
return Ok(Self::InstaNovo(v));
}
}
Err(())
}
curie!(MS:1003613|InstaNovo+) => {
for v in [
InstaNovoVersion::PlusV1_1_4,
InstaNovoVersion::PlusV1_2_2,
InstaNovoVersion::CombinedV1_2_2,
] {
if value.value.eq_ignore_ascii_case(v.name()) {
return Ok(Self::InstaNovo(v));
}
}
Err(())
}
curie!(MS:1001583|MaxQuant) => {
for v in MaxQuantPSM::VERSIONS {
if value.value.eq_ignore_ascii_case(v.version.name()) {
return Ok(Self::MaxQuant(v.version));
}
}
Err(())
}
curie!(MS:1002826|MetaMorpheus) => {
Ok(Self::MetaMorpheus(MetaMorpheusVersion::MetaMorpheus))
}
curie!(MS:1002601|mzTab) => Ok(Self::MzTab),
curie!(MS:1002984|Novor) => {
for v in NovorPSM::VERSIONS {
if value.value.eq_ignore_ascii_case(v.version.name()) {
return Ok(Self::Novor(v.version));
}
}
Err(())
}
curie!(MS:1001946|PEAKS Studio) => {
for v in PeaksPSM::VERSIONS {
if value.value.eq_ignore_ascii_case(v.version.name()) {
return Ok(Self::Peaks(v.version));
}
}
Err(())
}
curie!(MS:1003432|pLink2) => Ok(Self::PLink(PLinkVersion::V2_3)),
curie!(MS:1003014|MSFragger) => Ok(Self::MSFragger(MSFraggerVersion::V4_2)),
curie!(MS:1003018|Philosopher) => Ok(Self::MSFragger(MSFraggerVersion::Philosopher)),
curie!(MS:1003429|FragPipe) => {
for v in MSFraggerPSM::VERSIONS {
if value.value.eq_ignore_ascii_case(v.version.name()) {
return Ok(Self::MSFragger(v.version));
}
}
for v in OpairPSM::VERSIONS {
if value.value.eq_ignore_ascii_case(v.version.name()) {
return Ok(Self::Opair(v.version));
}
}
Err(())
}
curie!(MS:1000601|ProteinLynx Global Server) => Ok(Self::PLGS(PLGSVersion::V3_0)),
curie!(MS:1004001|NovoB) => Ok(Self::NovoB(NovoBVersion::V0_0_1)),
curie!(MS:1004002|PepNet) => Ok(Self::PepNet(PepNetVersion::V1_0)),
curie!(MS:1004003|Ï€-HelixNovo) => Ok(Self::PiHelixNovo(PiHelixNovoVersion::V1_1)),
curie!(MS:1004004|Ï€-PrimeNovo) => Ok(Self::PiPrimeNovo(PiPrimeNovoVersion::V0_1)),
curie!(MS:1004005|PowerNovo) => Ok(Self::PowerNovo(PowerNovoVersion::V1_0_17)),
curie!(MS:1004009|ProteoScape) => Ok(Self::Proteoscape(ProteoscapeVersion::V2025b)),
curie!(MS:1004006|pUniFind) => Ok(Self::PUniFind(PUniFindVersion::V0_1)),
curie!(MS:1004007|Sage) => Ok(Self::Sage(SageVersion::V0_14)),
curie!(MS:1004008|BiblioSpec Spectrum Sequence List) => {
Ok(Self::SpectrumSequenceList(SpectrumSequenceListVersion::SSL))
}
_ => Err(()),
}
}
}
impl Display for KnownFileFormat {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}{}",
self.name(),
self.version().map_or(String::new(), |v| format!(" - {v}"))
)
}
}
impl From<KnownFileFormat> for FileFormat {
fn from(value: KnownFileFormat) -> Self {
match value {
#[cfg(feature = "mzannotate")]
KnownFileFormat::AnnotatedSpectrum => Self::AnnotatedSpectrum,
KnownFileFormat::BasicCSV(version) => Self::BasicCSV(Some(version)),
KnownFileFormat::DeepNovoFamily(version) => Self::DeepNovoFamily(Some(version)),
KnownFileFormat::Fasta => Self::Fasta,
KnownFileFormat::InstaNovo(version) => Self::InstaNovo(Some(version)),
KnownFileFormat::MaxQuant(version) => Self::MaxQuant(Some(version)),
KnownFileFormat::MetaMorpheus(version) => Self::MetaMorpheus(Some(version)),
KnownFileFormat::MzTab => Self::MzTab,
KnownFileFormat::NovoB(version) => Self::NovoB(Some(version)),
KnownFileFormat::Novor(version) => Self::Novor(Some(version)),
KnownFileFormat::Opair(version) => Self::Opair(Some(version)),
KnownFileFormat::Peaks(version) => Self::Peaks(Some(version)),
KnownFileFormat::PepNet(version) => Self::PepNet(Some(version)),
KnownFileFormat::PiHelixNovo(version) => Self::PiHelixNovo(Some(version)),
KnownFileFormat::PiPrimeNovo(version) => Self::PiPrimeNovo(Some(version)),
KnownFileFormat::PLGS(version) => Self::PLGS(Some(version)),
KnownFileFormat::PLink(version) => Self::PLink(Some(version)),
KnownFileFormat::PowerNovo(version) => Self::PowerNovo(Some(version)),
KnownFileFormat::Proteoscape(version) => Self::Proteoscape(Some(version)),
KnownFileFormat::PUniFind(version) => Self::PUniFind(Some(version)),
KnownFileFormat::Sage(version) => Self::Sage(Some(version)),
KnownFileFormat::MSFragger(version) => Self::MSFragger(Some(version)),
KnownFileFormat::SpectrumSequenceList(version) => {
Self::SpectrumSequenceList(Some(version))
}
}
}
}
#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
#[allow(clippy::upper_case_acronyms, missing_docs)]
pub enum FileFormat {
#[cfg(feature = "mzannotate")]
AnnotatedSpectrum,
BasicCSV(Option<BasicCSVVersion>),
DeepNovoFamily(Option<DeepNovoFamilyVersion>),
Fasta,
InstaNovo(Option<InstaNovoVersion>),
MaxQuant(Option<MaxQuantVersion>),
MetaMorpheus(Option<MetaMorpheusVersion>),
MzTab,
NovoB(Option<NovoBVersion>),
Novor(Option<NovorVersion>),
Opair(Option<OpairVersion>),
Peaks(Option<PeaksVersion>),
PepNet(Option<PepNetVersion>),
PiHelixNovo(Option<PiHelixNovoVersion>),
PiPrimeNovo(Option<PiPrimeNovoVersion>),
PLGS(Option<PLGSVersion>),
PLink(Option<PLinkVersion>),
PowerNovo(Option<PowerNovoVersion>),
Proteoscape(Option<ProteoscapeVersion>),
PUniFind(Option<PUniFindVersion>),
Sage(Option<SageVersion>),
MSFragger(Option<MSFraggerVersion>),
SpectrumSequenceList(Option<SpectrumSequenceListVersion>),
#[default]
Undefined,
}
impl FileFormat {
pub fn open<'a>(
self,
path: &Path,
ontologies: &'a Ontologies,
) -> Result<GeneralPSMs<'a>, BoxedError<'static, BasicKind>> {
match self {
#[cfg(feature = "mzannotate")]
Self::AnnotatedSpectrum => annotated_spectrum::parse_mzspeclib(path, ontologies),
Self::BasicCSV(version) => {
BasicCSVPSM::parse_file(path, ontologies, false, version).map(PSMIter::into_box)
}
Self::DeepNovoFamily(version) => {
DeepNovoFamilyPSM::parse_file(path, ontologies, false, version)
.map(PSMIter::into_box)
}
Self::Fasta => FastaData::parse_file(path).map(|sequences| {
let b: Box<
dyn Iterator<
Item = Result<
PSM<Linked, MaybePeptidoform>,
BoxedError<'static, BasicKind>,
>,
>,
> = Box::new(
sequences
.into_iter()
.map(|p| Ok(PSM::<SemiAmbiguous, PeptidoformPresent>::from(p).cast())),
);
b
}),
Self::InstaNovo(version) => {
InstaNovoPSM::parse_file(path, ontologies, false, version).map(PSMIter::into_box)
}
Self::MaxQuant(version) => {
MaxQuantPSM::parse_file(path, ontologies, false, version).map(PSMIter::into_box)
}
Self::MetaMorpheus(version) => {
MetaMorpheusPSM::parse_file(path, ontologies, false, version).map(PSMIter::into_box)
}
Self::MzTab => MzTabPSM::parse_file(path, ontologies).map(|(_, _, sequences)| {
let b: Box<
dyn Iterator<
Item = Result<
PSM<Linked, MaybePeptidoform>,
BoxedError<'static, BasicKind>,
>,
>,
> = Box::new(
sequences
.into_iter()
.map(|p| p.map(|p| PSM::<SimpleLinear, MaybePeptidoform>::from(p).cast())),
);
b
}),
Self::PiHelixNovo(version) => {
PiHelixNovoPSM::parse_file(path, ontologies, false, version).map(PSMIter::into_box)
}
Self::PiPrimeNovo(version) => {
PiPrimeNovoPSM::parse_file(path, ontologies, false, version).map(PSMIter::into_box)
}
Self::NovoB(version) => {
NovoBPSM::parse_file(path, ontologies, false, version).map(PSMIter::into_box)
}
Self::Novor(version) => {
NovorPSM::parse_file(path, ontologies, false, version).map(PSMIter::into_box)
}
Self::Opair(version) => {
OpairPSM::parse_file(path, ontologies, false, version).map(PSMIter::into_box)
}
Self::PLGS(version) => {
PLGSPSM::parse_file(path, ontologies, false, version).map(PSMIter::into_box)
}
Self::PLink(version) => {
PLinkPSM::parse_file(path, ontologies, false, version).map(PSMIter::into_box)
}
Self::Peaks(version) => {
PeaksPSM::parse_file(path, ontologies, false, version).map(PSMIter::into_box)
}
Self::PepNet(version) => {
PepNetPSM::parse_file(path, ontologies, false, version).map(PSMIter::into_box)
}
Self::MSFragger(version) => {
MSFraggerPSM::parse_file(path, ontologies, false, version).map(PSMIter::into_box)
}
Self::PowerNovo(version) => {
PowerNovoPSM::parse_file(path, ontologies, false, version).map(PSMIter::into_box)
}
Self::Proteoscape(version) => {
ProteoscapePSM::parse_file(path, ontologies, false, version).map(PSMIter::into_box)
}
Self::PUniFind(version) => {
PUniFindPSM::parse_file(path, ontologies, false, version).map(PSMIter::into_box)
}
Self::Sage(version) => {
SagePSM::parse_file(path, ontologies, false, version).map(PSMIter::into_box)
}
Self::SpectrumSequenceList(version) => {
SpectrumSequenceListPSM::parse_file(path, ontologies, false, version)
.map(PSMIter::into_box)
}
Self::Undefined => open_psm_file(path, ontologies, false),
}
}
}