packlist: less normalization to support proper roundtrips (which only truncate trailing spaces)
Dependencies
- [2]
NAZOIBERpacklist: fix crash on empty input - [3]
JWYGE7FXpacklist : +reset utility - [4]
BUI22CYQ+zhed-packlist - [5]
7WMS7RLPpacklist: add support for parsing of multiple premarkers per item - [6]
V4EAN7NNmove some stuff around; more packlist stuff
Change contents
- replacement in crates/zhed-packlist/src/serial.rs at line 1
use crate::{ItemData, PackList, Section};use std::{collections::BTreeMap, fmt};use crate::{Item, ItemDetails, PackList, Spaced};use alloc::{string::ToString, vec::Vec};use core::fmt; - replacement in crates/zhed-packlist/src/serial.rs at line 5
#[derive(thiserror::Error, Debug, PartialEq, Eq)]#[derive(Debug, PartialEq, Eq)]#[cfg_attr(feature = "std", derive(thiserror::Error))] - replacement in crates/zhed-packlist/src/serial.rs at line 8
#[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"))] - replacement in crates/zhed-packlist/src/serial.rs at line 16
impl std::str::FromStr for PackList {impl core::str::FromStr for PackList { - replacement in crates/zhed-packlist/src/serial.rs at line 20
let mut it = s.lines();let mut it = s.lines().map(|i| i.trim_end()); - replacement in crates/zhed-packlist/src/serial.rs at line 22[4.564]→[4.564:609](∅→∅),[4.609]→[2.51:131](∅→∅),[2.131]→[4.658:1399](∅→∅),[4.658]→[4.658:1399](∅→∅)
// 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)?; - replacement in crates/zhed-packlist/src/serial.rs at line 26[4.1425]→[4.1425:1532](∅→∅),[4.1532]→[4.43:419](∅→∅),[4.419]→[4.1808:2344](∅→∅),[4.1808]→[4.1808:2344](∅→∅)
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(); - replacement in crates/zhed-packlist/src/serial.rs at line 35
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(), - replacement in crates/zhed-packlist/src/serial.rs at line 54
fn parse_item(line: &str) -> Result<(ItemData, String), Error> {let mkerr = || Error::InvalidItem(line.to_string());fn parse_itemdet(mut line: &str) -> Option<(ItemDetails, &str)> { - replacement in crates/zhed-packlist/src/serial.rs at line 56
return Err(mkerr());return None; - replacement in crates/zhed-packlist/src/serial.rs at line 58
let mut it = line.chars().peekable();let mut ret = ItemDetails {markers: Vec::new(),multiplier: None,}; - replacement in crates/zhed-packlist/src/serial.rs at line 63
// 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; - replacement in crates/zhed-packlist/src/serial.rs at line 71
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; - replacement in crates/zhed-packlist/src/serial.rs at line 83
if premarkers.is_empty() {return Err(mkerr());if ret.markers.is_empty() {return None; - replacement in crates/zhed-packlist/src/serial.rs at line 87
// 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; - replacement in crates/zhed-packlist/src/serial.rs at line 95
Ok((ItemData {premarkers,multiplier: multiplier.unwrap_or(1),},it.collect(),))Some((ret, line)) - replacement in crates/zhed-packlist/src/serial.rs at line 98
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)); - replacement in crates/zhed-packlist/src/serial.rs at line 115
for item in &sdata.items {writeln!(f, "{}{}", item.0, item.1)?;Some(i) if i.is_ascii_digit() => {multiplier *= 10;multiplier += u32::from((i as u8) - b'0'); - edit in crates/zhed-packlist/src/serial.rs at line 119
_ => return None,}}}impl fmt::Display for PackList {fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {writeln!(f, "{}", self.name.trim_end())?;for item in &self.items {writeln!(f, "{}", item)?; - replacement in crates/zhed-packlist/src/serial.rs at line 134
impl fmt::Display for ItemData {impl fmt::Display for Item { - replacement in crates/zhed-packlist/src/serial.rs at line 136
for i in &self.premarkers {write!(f, "[{} ] ", i)?;if let Some(x) = &self.det {fmt::Display::fmt(x, f)?; - replacement in crates/zhed-packlist/src/serial.rs at line 139
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)?; - edit in crates/zhed-packlist/src/serial.rs at line 158
use alloc::{string::String, vec}; - edit in crates/zhed-packlist/src/serial.rs at line 160[2.162]→[4.1674:1675](∅→∅),[4.4577]→[4.1674:1675](∅→∅),[4.1675]→[4.4577:5021](∅→∅),[4.4577]→[4.4577:5021](∅→∅),[4.5021]→[4.1676:2595](∅→∅),[4.2595]→[4.5587:5602](∅→∅),[4.5587]→[4.5587:5602](∅→∅),[4.5602]→[4.2596:3413](∅→∅),[4.3413]→[4.5836:6204](∅→∅),[4.5836]→[4.5836:6204](∅→∅),[4.6204]→[4.3414:3827](∅→∅)
#[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())));} - edit in crates/zhed-packlist/src/serial.rs at line 162
fn fail1() {assert_eq!("".parse::<PackList>(), Err(Error::UnexpectedEof));}#[test] - replacement in crates/zhed-packlist/src/serial.rs at line 163
let inp1 = r#"Packliste Ölland:[* ] [ _] [. ] 15 132x Rußlet inp = r#"Packliste Ölland: - replacement in crates/zhed-packlist/src/serial.rs at line 166
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(), - replacement in crates/zhed-packlist/src/serial.rs at line 175
"15 132x Ruß".to_string(),),(ItemData {premarkers: vec!['*', ' ', '.'],multiplier: 132,Spaced {val: [' ', '_'],space: " ".to_string(), - replacement in crates/zhed-packlist/src/serial.rs at line 179
"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(),}], - replacement in crates/zhed-packlist/src/serial.rs at line 192
assert_eq!(inp1.parse::<PackList>().unwrap(), res);assert_eq!(inp2.parse::<PackList>().unwrap(), res);assert_eq!(res.to_string(), inp2);assert_eq!(inp.parse::<PackList>().unwrap(), res);assert_eq!(res.to_string(), inp); - edit in crates/zhed-packlist/src/serial.rs at line 201
#[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());} - replacement in crates/zhed-packlist/src/lib.rs at line 1
use std::collections::BTreeMap;#![cfg_attr(not(feature = "std"), no_std)]#![forbid(unsafe_code,clippy::as_conversions,clippy::cast_ptr_alignment,trivial_casts,unconditional_recursion)]extern crate alloc;use alloc::{string::String, vec, vec::Vec}; - replacement in crates/zhed-packlist/src/lib.rs at line 19
pub range: String,pub sections: BTreeMap<String, Section>,pub items: Vec<Item>,}#[derive(Clone, Debug, PartialEq, Eq)]pub struct Item {pub det: Option<ItemDetails>,pub text: String, - replacement in crates/zhed-packlist/src/lib.rs at line 28
#[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>>, - replacement in crates/zhed-packlist/src/lib.rs at line 36[4.489]→[4.489:511](∅→∅),[4.511]→[4.5119:5150](∅→∅),[4.5150]→[4.536:561](∅→∅),[4.536]→[4.536:561](∅→∅)
pub struct ItemData {pub premarkers: Vec<char>,pub multiplier: u32,pub struct Spaced<T> {pub val: T,// white space folloing the valuepub space: String, - replacement in crates/zhed-packlist/src/lib.rs at line 42
impl Default for ItemData {impl Default for ItemDetails { - replacement in crates/zhed-packlist/src/lib.rs at line 45
premarkers: vec![' '],multiplier: 1,markers: vec![Spaced {val: [' ', ' '],space: String::new(),}],multiplier: None, - replacement in crates/zhed-packlist/src/lib.rs at line 54[4.708]→[4.708:765](∅→∅),[4.765]→[4.5187:5286](∅→∅),[4.5286]→[4.838:844](∅→∅),[4.838]→[4.838:844](∅→∅)
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 _; - replacement in crates/zhed-packlist/src/lib.rs at line 62
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()); - replacement in crates/zhed-packlist/src/lib.rs at line 68
#[cfg(test)]mod tests {use super::*;impl Commands for Item {fn reset_markers(&mut self) {if let Some(x) = &mut self.det {x.reset_markers();}}} - replacement in crates/zhed-packlist/src/lib.rs at line 76[4.1065]→[4.1065:1138](∅→∅),[4.1138]→[4.5492:6411](∅→∅),[4.6411]→[4.1704:1719](∅→∅),[4.1704]→[4.1704:1719](∅→∅),[4.1719]→[4.6412:7229](∅→∅),[4.7229]→[4.1953:2171](∅→∅),[4.1953]→[4.1953:2171](∅→∅),[4.2171]→[4.7230:8957](∅→∅),[4.8957]→[4.2739:2754](∅→∅),[4.2739]→[4.2739:2754](∅→∅),[4.2754]→[4.8958:8969](∅→∅),[4.8969]→[4.2988:3232](∅→∅),[4.2988]→[4.2988:3232](∅→∅)
#[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 = [' ', ' ']); - edit in crates/zhed-packlist/src/bin/zhed-packlist-reset.rs at line 2
use zhed_packlist::prelude::*; - replacement in crates/zhed-packlist/src/bin/zhed-packlist-reset.rs at line 8
std::io::stdin().lock().read_to_string(&mut inp).expect("unable to read packlist from stdin");std::io::stdin().lock().read_to_string(&mut inp).expect("unable to read packlist from stdin"); - replacement in crates/zhed-packlist/src/bin/zhed-packlist-reset.rs at line 13
pl.reset_premarkers();pl.reset_markers(); - replacement in crates/zhed-packlist/Cargo.toml at line 7
thiserror = "1.0"[dependencies.thiserror]version = "1.0"optional = true - edit in crates/zhed-packlist/Cargo.toml at line 14[2.441]
[features]std = [ "thiserror" ]