1use crate::Source;
2use std::collections::HashMap;
3use std::ffi::{OsStr, OsString};
4
5#[derive(Clone, Debug)]
7pub enum Value {
8 Flag,
9 One(OsString),
10 Many(Vec<OsString>),
11}
12
13#[derive(Clone, Copy, Debug, PartialEq, Eq)]
15pub enum Status {
16 Unset,
17 Set(Source),
18}
19
20const POS_PREFIX: &str = "@pos";
22
23#[must_use]
25pub fn key_for(path: &[&str], name: &str) -> String {
26 if path.is_empty() {
27 name.to_string()
28 } else {
29 format!("{}.{}", path.join("."), name)
30 }
31}
32
33#[must_use]
35pub fn pos_key_for(path: &[&str], name: &str) -> String {
36 if path.is_empty() {
37 format!("{POS_PREFIX}.{name}")
38 } else {
39 format!("{}.{}.{}", path.join("."), POS_PREFIX, name)
40 }
41}
42
43fn key_for_strings(path: &[String], name: &str) -> String {
44 if path.is_empty() {
45 name.to_string()
46 } else {
47 format!("{}.{}", path.join("."), name)
48 }
49}
50
51fn pos_key_for_strings(path: &[String], name: &str) -> String {
53 if path.is_empty() {
54 format!("{POS_PREFIX}.{name}")
55 } else {
56 format!("{}.{}.{}", path.join("."), POS_PREFIX, name)
57 }
58}
59
60#[derive(Debug)]
63pub struct Matches {
64 pub(crate) values: HashMap<String, Value>,
65 pub(crate) status: HashMap<String, Status>,
66 pub(crate) flag_counts: HashMap<String, usize>,
67 leaf_path: Vec<String>,
68}
69
70impl Matches {
71 pub(crate) fn new() -> Self {
72 Self { values: HashMap::new(), status: HashMap::new(), flag_counts: HashMap::new(), leaf_path: Vec::new() }
73 }
74
75 pub(crate) fn set_leaf_path(&mut self, path: &[&str]) {
77 self.leaf_path.clear();
78 self.leaf_path.extend(path.iter().map(|s| (*s).to_string()));
79 }
80
81 #[must_use]
83 pub fn leaf_path(&self) -> Vec<&str> {
84 self.leaf_path.iter().map(std::string::String::as_str).collect()
85 }
86
87 #[must_use]
89 pub fn view(&self) -> MatchView<'_> {
90 MatchView { m: self, path: self.leaf_path() }
91 }
92
93 #[must_use]
95 pub fn at<'a>(&'a self, path: &[&'a str]) -> MatchView<'a> {
96 MatchView { m: self, path: path.to_vec() }
97 }
98
99 #[must_use]
102 pub fn get_position(&self, name: &str) -> Option<&[OsString]> {
103 let k = pos_key_for_strings(&self.leaf_path, name);
104 match self.values.get(&k) {
105 Some(Value::Many(vs)) => Some(vs.as_slice()),
106 Some(Value::One(v)) => Some(std::slice::from_ref(v)),
107 _ => None,
108 }
109 }
110
111 #[must_use]
113 pub fn get_position_one(&self, name: &str) -> Option<&OsStr> {
114 let k = pos_key_for_strings(&self.leaf_path, name);
115 match self.values.get(&k) {
116 Some(Value::One(v)) => Some(v.as_os_str()),
117 Some(Value::Many(vs)) => vs.first().map(std::ffi::OsString::as_os_str),
118 _ => None,
119 }
120 }
121
122 #[must_use]
124 pub fn get_value(&self, name: &str) -> Option<&OsStr> {
125 let k = key_for_strings(&self.leaf_path, name);
126 match self.values.get(&k) {
127 Some(Value::One(v)) => Some(v.as_os_str()),
128 Some(Value::Many(vs)) => vs.first().map(std::ffi::OsString::as_os_str),
129 _ => None,
130 }
131 }
132
133 #[must_use]
135 pub fn get_values(&self, name: &str) -> Option<&[OsString]> {
136 let k = key_for_strings(&self.leaf_path, name);
137 match self.values.get(&k) {
138 Some(Value::Many(vs)) => Some(vs.as_slice()),
139 Some(Value::One(v)) => Some(std::slice::from_ref(v)),
140 _ => None,
141 }
142 }
143
144 #[must_use]
146 pub fn is_set(&self, name: &str) -> bool {
147 let k = key_for_strings(&self.leaf_path, name);
148 self.status.contains_key(&k)
149 }
150
151 #[must_use]
153 pub fn is_set_from(&self, name: &str, src: Source) -> bool {
154 let k = key_for_strings(&self.leaf_path, name);
155 matches!(self.status.get(&k), Some(Status::Set(s)) if *s == src)
156 }
157
158 #[must_use]
161 pub fn flag_count(&self, name: &str) -> usize {
162 let k = key_for_strings(&self.leaf_path, name);
163 *self.flag_counts.get(&k).unwrap_or(&0)
164 }
165}
166
167pub struct MatchView<'a> {
169 m: &'a Matches,
170 path: Vec<&'a str>,
171}
172
173impl MatchView<'_> {
174 #[must_use]
176 #[allow(clippy::missing_const_for_fn)]
177 pub fn path(&self) -> &[&str] {
178 &self.path
179 }
180
181 #[must_use]
183 pub fn is_set(&self, name: &str) -> bool {
184 let k = key_for(&self.path, name);
185 self.m.status.contains_key(&k)
186 }
187
188 #[must_use]
190 pub fn is_set_from(&self, name: &str, src: Source) -> bool {
191 let k = key_for(&self.path, name);
192 matches!(self.m.status.get(&k), Some(Status::Set(s)) if *s == src)
193 }
194
195 #[must_use]
198 pub fn flag_count(&self, name: &str) -> usize {
199 let k = key_for(&self.path, name);
200 *self.m.flag_counts.get(&k).unwrap_or(&0)
201 }
202
203 #[must_use]
205 pub fn value(&self, name: &str) -> Option<&OsStr> {
206 let k = key_for(&self.path, name);
207 match self.m.values.get(&k) {
208 Some(Value::One(v)) => Some(v.as_os_str()),
209 Some(Value::Many(vs)) => vs.first().map(std::ffi::OsString::as_os_str),
210 Some(Value::Flag) | None => None,
211 }
212 }
213
214 #[must_use]
216 pub fn values(&self, name: &str) -> Option<&[OsString]> {
217 let k = key_for(&self.path, name);
218 match self.m.values.get(&k) {
219 Some(Value::Many(vs)) => Some(vs.as_slice()),
220 Some(Value::One(v)) => Some(std::slice::from_ref(v)),
221 _ => None,
222 }
223 }
224
225 #[must_use]
227 pub fn pos_one(&self, name: &str) -> Option<&OsStr> {
228 let k = pos_key_for(&self.path, name);
229 match self.m.values.get(&k) {
230 Some(Value::One(v)) => Some(v.as_os_str()),
231 Some(Value::Many(vs)) => vs.first().map(std::ffi::OsString::as_os_str),
232 _ => None,
233 }
234 }
235
236 #[must_use]
238 pub fn pos_many(&self, name: &str) -> Option<&[OsString]> {
239 let k = pos_key_for(&self.path, name);
240 match self.m.values.get(&k) {
241 Some(Value::Many(vs)) => Some(vs.as_slice()),
242 Some(Value::One(v)) => Some(std::slice::from_ref(v)),
243 _ => None,
244 }
245 }
246
247 #[must_use]
249 pub fn parse<T: std::str::FromStr>(&self, name: &str) -> Option<Result<T, T::Err>> {
250 self.value(name).map(|s| s.to_string_lossy().parse::<T>())
251 }
252}