VHLGEBT74I5RXLGUWMVCXM64BYG7HOOFT7SURJ5TCTB3AAOQHIKQC #[error("unexpected data after range specification in header")]DataAfterRangeSpec,#[error("section is defined multiple times")]SectionOverwritten(String),#[error("invalid item: {0}")]InvalidItem(String),#[error("unexpected EOF")]
/// ```/// use zhed_packlist::{PackList, ParseError};/// assert_eq!("".parse::<PackList>(), Err(ParseError::UnexpectedEof));/// ```#[cfg_attr(feature = "std", error("unexpected EOF"))]
// 1st line: packlist name and rangelet mut fst = it.next().map(|x| x.trim()).ok_or(Error::UnexpectedEof)?;if let Some(x) = fst.strip_suffix(':') {fst = x;}let (name, range) = {let (rstart, rend) = (fst.find('('), fst.rfind(')'));if rstart.is_some() && rend.is_some() && rstart.unwrap() < rend.unwrap() {let (rstart, rend) = (rstart.unwrap(), rend.unwrap());if rend != (fst.len() - 1) {return Err(Error::DataAfterRangeSpec);}let mut name = fst[..rstart].trim();if let Some(x) = name.strip_suffix(':') {name = x.trim();}(name, &fst[rstart + 1..rend])} else {(fst, "")}};
// 1st line: packlist namelet name = it.next().ok_or(Error::UnexpectedEof)?;
let mut sections = BTreeMap::new();let mut section = (String::new(), Section::default());macro_rules! commit {() => {{if sections.contains_key(§ion.0) {return Err(Error::SectionOverwritten(section.0));}sections.insert(std::mem::take(&mut section.0),std::mem::take(&mut section.1),);}};}for line in it {if line.trim().is_empty() {continue;}let (itemdata, desc) = parse_item(line)?;if let Some(x) = desc.strip_prefix('#') {commit!();section.0 = x.trim().to_string();section.1.summary = itemdata;} else {section.1.items.push((itemdata, desc));}}if !section.0.is_empty() || !section.1.items.is_empty() {commit!();}
let items = it.map(|line| match line.parse() {Ok(x) => x,Err(x) => match x {},}).collect();
range: range.to_string(),sections,
items,})}}impl core::str::FromStr for Item {type Err = core::convert::Infallible;fn from_str(line: &str) -> Result<Self, core::convert::Infallible> {let (det, text) = parse_itemdet(line).map(|(det, rest)| (Some(det), rest)).unwrap_or((None, line));Ok(Self {det,text: text.to_string(),
// parse premarkerslet mut premarkers = Vec::new();while it.next_if(|i| *i == '[').is_some() {let premarker = it.next().ok_or_else(mkerr)?;let _ = it.next().ok_or_else(mkerr)?;let c2 = it.next().ok_or_else(mkerr)?;if c2 != ']' {return Err(mkerr());
// parse markerswhile let Some(x) = line.strip_prefix('[') {let mut it = x.chars();let a = it.next()?;let b = it.next()?;if ']' != it.next()? {return None;
premarkers.push(premarker);while it.next_if(|i| i.is_whitespace()).is_some() {}
line = it.as_str();let wsl = it.take_while(|i| i.is_whitespace()).map(|i| i.len_utf8()).sum::<usize>();let (space, rest) = line.split_at(wsl);ret.markers.push(Spaced {val: [a, b],space: space.to_string(),});line = rest;
// check for multiplier// btp : backtrack point if no valid multiplier is foundlet btp = it.clone();let mut multiplier = None;while let Some(x) = it.next_if(|i| i.is_ascii_digit()) {let m = multiplier.get_or_insert(0);*m *= 10;*m += u32::from((x as u8) - b'0');}if it.next_if(|&i| i == 'x').is_none() {multiplier = None;it = btp;} else {while it.next_if(|i| i.is_whitespace()).is_some() {}
if let Some((val, space, rest)) = parse_multiplier(line) {ret.multiplier = Some(Spaced {val,space: space.to_string(),});line = rest;
impl fmt::Display for PackList {fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {f.write_str(&self.name)?;if !self.range.is_empty() {write!(f, " ({})", self.range)?;}writeln!(f)?;for (section, sdata) in &self.sections {if !section.is_empty() {writeln!(f, "\n{}# {}", sdata.summary, section)?;
fn parse_multiplier(line: &str) -> Option<(u32, &str, &str)> {if !line.starts_with(|i: char| i.is_ascii_digit()) {return None;}let mut multiplier = 0;let mut it = line.chars();loop {match it.next() {Some('x') => {let tmp = it.as_str();let wsl = it.take_while(|i| i.is_whitespace()).map(|i| i.len_utf8()).sum::<usize>();let (a, b) = tmp.split_at(wsl);return Some((multiplier, a, b));
if self.multiplier != 1 {write!(f, "{}x ", self.multiplier)?;
f.write_str(self.text.trim_end())}}impl fmt::Display for ItemDetails {fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {for m in &self.markers {write!(f, "[{}{}]{}", m.val[0], m.val[1], m.space)?;}if let Some(x) = &self.multiplier {write!(f, "{}x{}", x.val, x.space)?;
#[test]fn ex0() {let inp1 = r#"Packliste Ölland (80 Tage, 50 Nächtsle):[ ] 15 Ruß[ ] 132x Wattestäbe[# ] 0x Handtuch[- ] 5000x # Wachsbaum ...[ ] Ranke[* ] 5 x Metrik + BNaumbnd"#;let inp2 = r#"Packliste Ölland (80 Tage, 50 Nächtsle)[ ] 15 Ruß[ ] 132x Wattestäbe[# ] 0x Handtuch[- ] 5000x # Wachsbaum ...[ ] Ranke[* ] 5 x Metrik + BNaumbnd"#;let mut sections = BTreeMap::new();sections.insert(String::new(),Section {summary: ItemData::default(),items: vec![(ItemData {premarkers: vec![' '],multiplier: 1,},"15 Ruß".to_string(),),(ItemData {premarkers: vec![' '],multiplier: 132,},"Wattestäbe".to_string(),),(ItemData {premarkers: vec!['#'],multiplier: 0,},"Handtuch".to_string(),),],},);sections.insert("Wachsbaum ...".to_string(),Section {summary: ItemData {premarkers: vec!['-'],multiplier: 5000,},items: vec![(ItemData {premarkers: vec![' '],multiplier: 1,},"Ranke".to_string(),),(ItemData {premarkers: vec!['*'],multiplier: 1,},"5 x Metrik + BNaumbnd".to_string(),),],},);let res = PackList {name: "Packliste Ölland".to_string(),range: "80 Tage, 50 Nächtsle".to_string(),sections,};assert_eq!(inp1.parse::<PackList>().unwrap(), res);assert_eq!(inp2.parse::<PackList>().unwrap(), res);assert_eq!(res.to_string(), inp2);}#[test]fn fail0() {assert_eq!("Packl (xyz) ...\n".parse::<PackList>(),Err(Error::DataAfterRangeSpec));assert_eq!("P\n[ ] # 1\n[ ] # 1\n".parse::<PackList>(),Err(Error::SectionOverwritten("1".to_string())));assert_eq!("P\n[ ]".parse::<PackList>(),Err(Error::InvalidItem("[ ]".to_string())));}
let inp2 = r#"Packliste Ölland[* ] [ ] [. ] 15 132x Ruß[* ] [ ] [. ] 132x Ruß"#;let mut sections = BTreeMap::new();sections.insert(String::new(),Section {summary: ItemData::default(),items: vec![(ItemData {premarkers: vec!['*', ' ', '.'],multiplier: 1,
let res = PackList {name: "Packliste Ölland:".to_string(),items: vec![Item {det: Some(ItemDetails {markers: vec![Spaced {val: ['*', ' '],space: " ".to_string(),
"Ruß".to_string(),),],},);let res = PackList {name: "Packliste Ölland".to_string(),range: String::new(),sections,
Spaced {val: ['.', ' '],space: " ".to_string(),},],multiplier: Some(Spaced {val: 132,space: " ".to_string(),}),}),text: "Ruß".to_string(),}],
#[test]fn correctly_trimmed(s in "(?s:.+)") {let s2: String = s.lines().flat_map(|i| i.trim_end().chars().chain(core::iter::once('\n'))).collect();let x1 = s.parse::<PackList>().unwrap();let x2 = s2.parse::<PackList>().unwrap();assert_eq!(x1, x2);}#[test]fn roundtrip(s in "(?s:[^\r]+\n)".prop_filter("line ends are trimmed", |v| v.lines().all(|l| l == l.trim_end()))) {assert_eq!(s, s.parse::<PackList>().unwrap().to_string());}
#[derive(Clone, Debug, Default, PartialEq, Eq)]pub struct Section {pub summary: ItemData,pub items: Vec<(ItemData, String)>,
/// packlist item details#[derive(Clone, Debug, PartialEq, Eq)]pub struct ItemDetails {pub markers: Vec<Spaced<[char; 2]>>,pub multiplier: Option<Spaced<u32>>,
impl PackList {pub fn reset_premarkers(&mut self) {self.sections.values_mut().for_each(|sd| sd.reset_premarkers());}
pub trait Commands {fn reset_markers(&mut self);}pub mod prelude {pub use crate::Commands as _;
impl Section {pub fn reset_premarkers(&mut self) {core::iter::once(&mut self.summary).chain(self.items.iter_mut().map(|item| &mut item.0)).flat_map(|item| item.premarkers.iter_mut()).for_each(|i| *i = ' ');
impl Commands for PackList {fn reset_markers(&mut self) {self.items.iter_mut().for_each(|i| i.reset_markers());
#[test]fn ex0rs() {let mut sections = BTreeMap::new();sections.insert(String::new(),Section {summary: ItemData::default(),items: vec![(ItemData {premarkers: vec![' '],multiplier: 1,},"15 Ruß".to_string(),),(ItemData {premarkers: vec![' '],multiplier: 132,},"Wattestäbe".to_string(),),(ItemData {premarkers: vec!['#'],multiplier: 0,},"Handtuch".to_string(),),],},);sections.insert("Wachsbaum ...".to_string(),Section {summary: ItemData {premarkers: vec!['-'],multiplier: 5000,},items: vec![(ItemData {premarkers: vec![' '],multiplier: 1,},"Ranke".to_string(),),(ItemData {premarkers: vec!['*'],multiplier: 1,},"5 x Metrik + BNaumbnd".to_string(),),],},);let mut res = PackList {name: "Packliste Ölland".to_string(),range: "80 Tage, 50 Nächtsle".to_string(),sections,};let mut sections2 = BTreeMap::new();sections2.insert(String::new(),Section {summary: ItemData::default(),items: vec![(ItemData {premarkers: vec![' '],multiplier: 1,},"15 Ruß".to_string(),),(ItemData {premarkers: vec![' '],multiplier: 132,},"Wattestäbe".to_string(),),(ItemData {premarkers: vec![' '],multiplier: 0,},"Handtuch".to_string(),),],},);sections2.insert("Wachsbaum ...".to_string(),Section {summary: ItemData {premarkers: vec![' '],multiplier: 5000,},items: vec![(ItemData {premarkers: vec![' '],multiplier: 1,},"Ranke".to_string(),),(ItemData {premarkers: vec![' '],multiplier: 1,},"5 x Metrik + BNaumbnd".to_string(),),],},);let res2 = PackList {name: "Packliste Ölland".to_string(),range: "80 Tage, 50 Nächtsle".to_string(),sections: sections2,};res.reset_premarkers();assert_eq!(res, res2);
impl Commands for ItemDetails {fn reset_markers(&mut self) {self.markers.iter_mut().for_each(|i| i.val = [' ', ' ']);
[features]std = [ "thiserror" ]