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]
104 pub fn get_position(&self, name: &str) -> Option<&[OsString]> {
105 let k = pos_key_for_strings(&self.leaf_path, name);
106 match self.values.get(&k) {
107 Some(Value::Many(vs)) => Some(vs.as_slice()),
108 Some(Value::One(v)) => Some(std::slice::from_ref(v)),
109 _ => None,
110 }
111 }
112
113 #[must_use]
115 pub fn get_position_one(&self, name: &str) -> Option<&OsStr> {
116 let k = pos_key_for_strings(&self.leaf_path, name);
117 match self.values.get(&k) {
118 Some(Value::One(v)) => Some(v.as_os_str()),
119 Some(Value::Many(vs)) => vs.first().map(std::ffi::OsString::as_os_str),
120 _ => None,
121 }
122 }
123
124 #[must_use]
126 pub fn get_value(&self, name: &str) -> Option<&OsStr> {
127 let k = key_for_strings(&self.leaf_path, name);
128 match self.values.get(&k) {
129 Some(Value::One(v)) => Some(v.as_os_str()),
130 Some(Value::Many(vs)) => vs.first().map(std::ffi::OsString::as_os_str),
131 _ => None,
132 }
133 }
134
135 #[must_use]
137 pub fn get_values(&self, name: &str) -> Option<&[OsString]> {
138 let k = key_for_strings(&self.leaf_path, name);
139 match self.values.get(&k) {
140 Some(Value::Many(vs)) => Some(vs.as_slice()),
141 Some(Value::One(v)) => Some(std::slice::from_ref(v)),
142 _ => None,
143 }
144 }
145
146 #[must_use]
148 pub fn is_set(&self, name: &str) -> bool {
149 let k = key_for_strings(&self.leaf_path, name);
150 self.status.contains_key(&k)
151 }
152
153 #[must_use]
155 pub fn is_set_from(&self, name: &str, src: Source) -> bool {
156 let k = key_for_strings(&self.leaf_path, name);
157 matches!(self.status.get(&k), Some(Status::Set(s)) if *s == src)
158 }
159}
160
161pub struct MatchView<'a> {
163 m: &'a Matches,
164 path: Vec<&'a str>,
165}
166
167impl MatchView<'_> {
168 #[must_use]
170 #[allow(clippy::missing_const_for_fn)]
171 pub fn path(&self) -> &[&str] {
172 &self.path
173 }
174
175 #[must_use]
177 pub fn is_set(&self, name: &str) -> bool {
178 let k = key_for(&self.path, name);
179 self.m.status.contains_key(&k)
180 }
181
182 #[must_use]
184 pub fn is_set_from(&self, name: &str, src: Source) -> bool {
185 let k = key_for(&self.path, name);
186 matches!(self.m.status.get(&k), Some(Status::Set(s)) if *s == src)
187 }
188
189 #[must_use]
191 pub fn value(&self, name: &str) -> Option<&OsStr> {
192 let k = key_for(&self.path, name);
193 match self.m.values.get(&k) {
194 Some(Value::One(v)) => Some(v.as_os_str()),
195 Some(Value::Many(vs)) => vs.first().map(std::ffi::OsString::as_os_str),
196 Some(Value::Flag) | None => None,
197 }
198 }
199
200 #[must_use]
202 pub fn values(&self, name: &str) -> Option<&[OsString]> {
203 let k = key_for(&self.path, name);
204 match self.m.values.get(&k) {
205 Some(Value::Many(vs)) => Some(vs.as_slice()),
206 Some(Value::One(v)) => Some(std::slice::from_ref(v)),
207 _ => None,
208 }
209 }
210
211 #[must_use]
213 pub fn pos_one(&self, name: &str) -> Option<&OsStr> {
214 let k = pos_key_for(&self.path, name);
215 match self.m.values.get(&k) {
216 Some(Value::One(v)) => Some(v.as_os_str()),
217 Some(Value::Many(vs)) => vs.first().map(std::ffi::OsString::as_os_str),
218 _ => None,
219 }
220 }
221
222 #[must_use]
224 pub fn pos_many(&self, name: &str) -> Option<&[OsString]> {
225 let k = pos_key_for(&self.path, name);
226 match self.m.values.get(&k) {
227 Some(Value::Many(vs)) => Some(vs.as_slice()),
228 Some(Value::One(v)) => Some(std::slice::from_ref(v)),
229 _ => None,
230 }
231 }
232
233 #[must_use]
235 pub fn parse<T: std::str::FromStr>(&self, name: &str) -> Option<Result<T, T::Err>> {
236 self.value(name).map(|s| s.to_string_lossy().parse::<T>())
237 }
238}