use std::collections::HashMap;
use std::path::PathBuf;
const SEPARATOR: char = ',';
const OPTIONAL_MARKER: char = '?';
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RulesEntry {
pub path: PathBuf,
pub optional: bool,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum RulesListError {
Empty,
EmptyEntry {
position: usize,
},
DuplicatePath {
first: usize,
position: usize,
},
}
impl std::fmt::Display for RulesListError {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Empty => return write!(formatter, "the rules list is empty; name at least one rules file"),
Self::EmptyEntry { position } => {
return write!(
formatter,
"rules list entry {position} names no file (an empty entry between commas, or a bare '?')",
);
}
Self::DuplicatePath { first, position } => {
return write!(
formatter,
"rules list entry {position} names the same file as entry {first}",
);
}
}
}
}
impl std::error::Error for RulesListError {}
pub fn parse_rules_list(value: &str) -> Result<Vec<RulesEntry>, RulesListError> {
if value.is_empty() {
return Err(RulesListError::Empty);
}
let mut entries: Vec<RulesEntry> = Vec::new();
let mut first_positions: HashMap<&str, usize> = HashMap::new();
for (index, raw) in value.split(SEPARATOR).enumerate() {
let position: usize = index + 1;
let (name, optional): (&str, bool) = match raw.strip_suffix(OPTIONAL_MARKER) {
Some(without_marker) => (without_marker, true),
None => (raw, false),
};
if name.is_empty() {
return Err(RulesListError::EmptyEntry { position });
}
if let Some(&first) = first_positions.get(name) {
return Err(RulesListError::DuplicatePath { first, position });
}
first_positions.insert(name, position);
entries.push(RulesEntry { path: PathBuf::from(name), optional });
}
return Ok(entries);
}
#[cfg(test)]
#[path = "rules_list_tests.rs"]
mod tests;