use crate::store::{escape, unescape};
pub struct Named {
pub id: u64,
pub name: String,
}
#[derive(Default)]
pub struct List(Vec<Named>);
impl List {
pub fn all(&self) -> &[Named] {
&self.0
}
pub fn name_of(&self, id: u64) -> Option<&str> {
self.0
.iter()
.find(|held| held.id == id)
.map(|held| held.name.as_str())
}
pub fn holds(&self, id: u64) -> bool {
self.name_of(id).is_some()
}
pub fn make(&mut self, wanted: &str) -> Option<u64> {
let id = self
.0
.iter()
.map(|held| held.id)
.max()
.unwrap_or(0)
.checked_add(1)?;
let mut name = wanted.to_string();
for nth in 2.. {
if !self.0.iter().any(|held| held.name == name) {
break;
}
name = format!("{wanted} {nth}");
}
self.0.push(Named { id, name });
Some(id)
}
pub fn rename(&mut self, id: u64, name: String) {
if let Some(held) = self.0.iter_mut().find(|held| held.id == id) {
held.name = name;
}
}
pub fn remove(&mut self, id: u64) {
self.0.retain(|held| held.id != id);
}
pub fn restore(&mut self, id: u64, name: String) {
if !self.holds(id) {
self.0.push(Named { id, name });
}
}
}
pub enum Line {
Named { id: u64, name: String },
Member { asset: u64, group: u64 },
}
const MEMBER: &str = "m";
pub fn written(version: &str, kind: &str, list: &List) -> String {
let mut out = format!("{version}\n");
for held in list.all() {
out.push_str(&format!("{kind}\t{}\t{}\n", held.id, escape(&held.name)));
}
out
}
pub fn member(asset: u64, group: u64) -> String {
format!("{MEMBER}\t{asset}\t{group}\n")
}
pub fn read(text: &str, version: &str, kind: &str) -> Vec<Line> {
let mut lines = text.lines();
if lines.next() != Some(version) {
return Vec::new();
}
lines.filter_map(|line| parse(line, kind)).collect()
}
fn parse(line: &str, kind: &str) -> Option<Line> {
let mut parts = line.split('\t');
let (head, first, second) = (parts.next()?, parts.next()?, parts.next()?);
if parts.next().is_some() {
return None;
}
if head == kind {
return Some(Line::Named {
id: first.parse().ok()?,
name: unescape(second),
});
}
if head == MEMBER {
return Some(Line::Member {
asset: first.parse().ok()?,
group: second.parse().ok()?,
});
}
None
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_list_holding_the_last_id_makes_nothing_more() {
let mut list = List::default();
list.restore(u64::MAX, "Sunday".into());
assert_eq!(list.make("Loud"), None);
assert_eq!(list.all().len(), 1, "and nothing was added");
assert_eq!(list.name_of(u64::MAX), Some("Sunday"));
}
}