#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct Attributes {
pub pairs: Vec<(String, String)>,
}
impl Attributes {
pub fn parse_gff3(col: &str) -> Self {
let mut pairs = Vec::new();
if col == "." || col.is_empty() {
return Self { pairs };
}
for field in col.split(';') {
let field = field.trim();
if field.is_empty() {
continue;
}
match field.split_once('=') {
Some((k, v)) => pairs.push((k.trim().to_string(), v.to_string())),
None => pairs.push((field.to_string(), String::new())),
}
}
Self { pairs }
}
pub fn get(&self, key: &str) -> Option<&str> {
self.pairs
.iter()
.find(|(k, _)| k == key)
.map(|(_, v)| v.as_str())
}
pub fn first(&self, key: &str) -> Option<&str> {
self.get(key).map(|v| v.split(',').next().unwrap_or(v))
}
pub fn is_empty(&self) -> bool {
self.pairs.is_empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_basic() {
let a = Attributes::parse_gff3("ID=gene1;Name=BRCA2;Parent=x,y");
assert_eq!(a.get("ID"), Some("gene1"));
assert_eq!(a.first("Parent"), Some("x"));
assert_eq!(a.get("missing"), None);
}
#[test]
fn handles_dot_and_empty() {
assert!(Attributes::parse_gff3(".").is_empty());
assert!(Attributes::parse_gff3("").is_empty());
}
}