use std::{ collections::HashSet, fmt::{Display, Write}, hash::Hash, iter::Peekable, }; use anyhow::{ensure, Context, Result}; use super::Package; #[derive(Debug, Eq)] pub struct Section { pub name: String, pub packages: HashSet, } impl Section { pub(crate) fn new(name: String, packages: HashSet) -> Self { Self { name, packages } } pub(crate) fn try_from_lines<'a>( iter: &mut Peekable<(impl Iterator + std::fmt::Debug)>, ) -> Result { let name = iter .find(|line| line.starts_with('[')) .context("finding beginning of next section")? .trim() .trim_start_matches('[') .trim_end_matches(']') .to_string(); let mut packages = HashSet::new(); // `while let` is unstable, unfortunately while iter.peek().is_some() && !iter.peek().unwrap().starts_with('[') { if let Some(package) = Package::try_from(iter.next().unwrap()) { packages.insert(package); } } ensure!(!packages.is_empty()); Ok(Self::new(name, packages)) } } impl Hash for Section { fn hash(&self, state: &mut H) { self.name.hash(state); } } impl PartialEq for Section { fn eq(&self, other: &Self) -> bool { self.name == other.name } } impl PartialOrd for Section { fn partial_cmp(&self, other: &Self) -> Option { self.name.partial_cmp(&other.name) } } impl Ord for Section { fn cmp(&self, _other: &Self) -> std::cmp::Ordering { todo!() } } impl Display for Section { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_fmt(format_args!("[{}]\n", &self.name))?; let mut packages: Vec<_> = self.packages.iter().collect(); packages.sort_unstable(); let mut iter = packages.iter().peekable(); while let Some(package) = iter.next() { package.fmt(f)?; if iter.peek().is_some() { f.write_char('\n')?; } } Ok(()) } }