use crate::Source;
use std::collections::HashMap;
use std::ffi::{OsStr, OsString};
#[derive(Clone, Debug)]
pub enum Value {
Flag,
One(OsString),
Many(Vec<OsString>),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Status {
Unset,
Set(Source),
}
const POS_PREFIX: &str = "@pos";
#[must_use]
pub fn key_for(path: &[&str], name: &str) -> String {
if path.is_empty() {
name.to_string()
} else {
format!("{}.{}", path.join("."), name)
}
}
#[must_use]
pub fn pos_key_for(path: &[&str], name: &str) -> String {
if path.is_empty() {
format!("{POS_PREFIX}.{name}")
} else {
format!("{}.{}.{}", path.join("."), POS_PREFIX, name)
}
}
fn key_for_strings(path: &[String], name: &str) -> String {
if path.is_empty() {
name.to_string()
} else {
format!("{}.{}", path.join("."), name)
}
}
fn pos_key_for_strings(path: &[String], name: &str) -> String {
if path.is_empty() {
format!("{POS_PREFIX}.{name}")
} else {
format!("{}.{}.{}", path.join("."), POS_PREFIX, name)
}
}
#[derive(Debug)]
pub struct Matches {
pub(crate) values: HashMap<String, Value>,
pub(crate) status: HashMap<String, Status>,
pub(crate) flag_counts: HashMap<String, usize>,
leaf_path: Vec<String>,
}
impl Matches {
pub(crate) fn new() -> Self {
Self { values: HashMap::new(), status: HashMap::new(), flag_counts: HashMap::new(), leaf_path: Vec::new() }
}
pub(crate) fn set_leaf_path(&mut self, path: &[&str]) {
self.leaf_path.clear();
self.leaf_path.extend(path.iter().map(|s| (*s).to_string()));
}
#[must_use]
pub fn leaf_path(&self) -> Vec<&str> {
self.leaf_path.iter().map(std::string::String::as_str).collect()
}
#[must_use]
pub fn view(&self) -> MatchView<'_> {
MatchView { m: self, path: self.leaf_path() }
}
#[must_use]
pub fn at<'a>(&'a self, path: &[&'a str]) -> MatchView<'a> {
MatchView { m: self, path: path.to_vec() }
}
#[must_use]
pub fn get_position(&self, name: &str) -> Option<&[OsString]> {
let k = pos_key_for_strings(&self.leaf_path, name);
match self.values.get(&k) {
Some(Value::Many(vs)) => Some(vs.as_slice()),
Some(Value::One(v)) => Some(std::slice::from_ref(v)),
_ => None,
}
}
#[must_use]
pub fn get_position_one(&self, name: &str) -> Option<&OsStr> {
let k = pos_key_for_strings(&self.leaf_path, name);
match self.values.get(&k) {
Some(Value::One(v)) => Some(v.as_os_str()),
Some(Value::Many(vs)) => vs.first().map(std::ffi::OsString::as_os_str),
_ => None,
}
}
#[must_use]
pub fn get_value(&self, name: &str) -> Option<&OsStr> {
let k = key_for_strings(&self.leaf_path, name);
match self.values.get(&k) {
Some(Value::One(v)) => Some(v.as_os_str()),
Some(Value::Many(vs)) => vs.first().map(std::ffi::OsString::as_os_str),
_ => None,
}
}
#[must_use]
pub fn get_values(&self, name: &str) -> Option<&[OsString]> {
let k = key_for_strings(&self.leaf_path, name);
match self.values.get(&k) {
Some(Value::Many(vs)) => Some(vs.as_slice()),
Some(Value::One(v)) => Some(std::slice::from_ref(v)),
_ => None,
}
}
#[must_use]
pub fn is_set(&self, name: &str) -> bool {
let k = key_for_strings(&self.leaf_path, name);
self.status.contains_key(&k)
}
#[must_use]
pub fn is_set_from(&self, name: &str, src: Source) -> bool {
let k = key_for_strings(&self.leaf_path, name);
matches!(self.status.get(&k), Some(Status::Set(s)) if *s == src)
}
#[must_use]
pub fn flag_count(&self, name: &str) -> usize {
let k = key_for_strings(&self.leaf_path, name);
*self.flag_counts.get(&k).unwrap_or(&0)
}
}
pub struct MatchView<'a> {
m: &'a Matches,
path: Vec<&'a str>,
}
impl MatchView<'_> {
#[must_use]
#[allow(clippy::missing_const_for_fn)]
pub fn path(&self) -> &[&str] {
&self.path
}
#[must_use]
pub fn is_set(&self, name: &str) -> bool {
let k = key_for(&self.path, name);
self.m.status.contains_key(&k)
}
#[must_use]
pub fn is_set_from(&self, name: &str, src: Source) -> bool {
let k = key_for(&self.path, name);
matches!(self.m.status.get(&k), Some(Status::Set(s)) if *s == src)
}
#[must_use]
pub fn flag_count(&self, name: &str) -> usize {
let k = key_for(&self.path, name);
*self.m.flag_counts.get(&k).unwrap_or(&0)
}
#[must_use]
pub fn value(&self, name: &str) -> Option<&OsStr> {
let k = key_for(&self.path, name);
match self.m.values.get(&k) {
Some(Value::One(v)) => Some(v.as_os_str()),
Some(Value::Many(vs)) => vs.first().map(std::ffi::OsString::as_os_str),
Some(Value::Flag) | None => None,
}
}
#[must_use]
pub fn values(&self, name: &str) -> Option<&[OsString]> {
let k = key_for(&self.path, name);
match self.m.values.get(&k) {
Some(Value::Many(vs)) => Some(vs.as_slice()),
Some(Value::One(v)) => Some(std::slice::from_ref(v)),
_ => None,
}
}
#[must_use]
pub fn pos_one(&self, name: &str) -> Option<&OsStr> {
let k = pos_key_for(&self.path, name);
match self.m.values.get(&k) {
Some(Value::One(v)) => Some(v.as_os_str()),
Some(Value::Many(vs)) => vs.first().map(std::ffi::OsString::as_os_str),
_ => None,
}
}
#[must_use]
pub fn pos_many(&self, name: &str) -> Option<&[OsString]> {
let k = pos_key_for(&self.path, name);
match self.m.values.get(&k) {
Some(Value::Many(vs)) => Some(vs.as_slice()),
Some(Value::One(v)) => Some(std::slice::from_ref(v)),
_ => None,
}
}
#[must_use]
pub fn parse<T: std::str::FromStr>(&self, name: &str) -> Option<Result<T, T::Err>> {
self.value(name).map(|s| s.to_string_lossy().parse::<T>())
}
}