yash_env/variable/
quirk.rs1use super::Value;
20use super::Variable;
21use crate::source::{Location, Source};
22use either::{Left, Right};
23use std::borrow::Cow;
24
25#[derive(Clone, Debug, Eq, PartialEq)]
36pub enum Quirk {
37 LineNumber,
44 }
47
48#[derive(Clone, Debug, Eq, PartialEq)]
54pub enum Expansion<'a> {
55 Unset,
57 Scalar(Cow<'a, str>),
59 Array(Cow<'a, [String]>),
61}
62
63impl Default for Expansion<'_> {
65 fn default() -> Self {
66 Self::Unset
67 }
68}
69
70impl From<String> for Expansion<'static> {
71 fn from(value: String) -> Self {
72 Expansion::Scalar(Cow::Owned(value))
73 }
74}
75
76impl<'a> From<&'a str> for Expansion<'a> {
77 fn from(value: &'a str) -> Self {
78 Expansion::Scalar(Cow::Borrowed(value))
79 }
80}
81
82impl<'a> From<&'a String> for Expansion<'a> {
83 fn from(value: &'a String) -> Self {
84 Expansion::Scalar(Cow::Borrowed(value))
85 }
86}
87
88impl From<Option<String>> for Expansion<'static> {
89 fn from(value: Option<String>) -> Self {
90 match value {
91 Some(value) => value.into(),
92 None => Expansion::Unset,
93 }
94 }
95}
96
97impl From<Vec<String>> for Expansion<'static> {
98 fn from(values: Vec<String>) -> Self {
99 Expansion::Array(Cow::Owned(values))
100 }
101}
102
103impl<'a> From<&'a [String]> for Expansion<'a> {
104 fn from(values: &'a [String]) -> Self {
105 Expansion::Array(Cow::Borrowed(values))
106 }
107}
108
109impl<'a> From<&'a Vec<String>> for Expansion<'a> {
110 fn from(values: &'a Vec<String>) -> Self {
111 Expansion::Array(Cow::Borrowed(values))
112 }
113}
114
115impl From<Value> for Expansion<'static> {
116 fn from(value: Value) -> Self {
117 match value {
118 Value::Scalar(value) => Expansion::from(value),
119 Value::Array(values) => Expansion::from(values),
120 }
121 }
122}
123
124impl<'a> From<&'a Value> for Expansion<'a> {
125 fn from(value: &'a Value) -> Self {
126 match value {
127 Value::Scalar(value) => Expansion::from(value),
128 Value::Array(values) => Expansion::from(values),
129 }
130 }
131}
132
133impl From<Option<Value>> for Expansion<'static> {
134 fn from(value: Option<Value>) -> Self {
135 match value {
136 Some(value) => value.into(),
137 None => Expansion::Unset,
138 }
139 }
140}
141
142impl<'a, V> From<Option<&'a V>> for Expansion<'a>
143where
144 Expansion<'a>: From<&'a V>,
145{
146 fn from(value: Option<&'a V>) -> Self {
147 match value {
148 Some(value) => value.into(),
149 None => Expansion::Unset,
150 }
151 }
152}
153
154impl From<Expansion<'_>> for Option<Value> {
155 fn from(expansion: Expansion<'_>) -> Option<Value> {
156 match expansion {
157 Expansion::Unset => None,
158 Expansion::Scalar(value) => Some(Value::Scalar(value.into_owned())),
159 Expansion::Array(values) => Some(Value::Array(values.into_owned())),
160 }
161 }
162}
163
164impl<'a> From<&'a Expansion<'a>> for Expansion<'a> {
165 fn from(expansion: &'a Expansion<'a>) -> Expansion<'a> {
166 match expansion {
167 Expansion::Unset => Expansion::Unset,
168 Expansion::Scalar(value) => value.as_ref().into(),
169 Expansion::Array(values) => values.as_ref().into(),
170 }
171 }
172}
173
174impl Expansion<'_> {
175 #[must_use]
177 pub fn into_owned(self) -> Option<Value> {
178 self.into()
179 }
180
181 #[must_use]
183 pub fn as_ref(&self) -> Expansion<'_> {
184 self.into()
185 }
186
187 #[must_use]
193 pub fn len(&self) -> usize {
194 match self {
195 Expansion::Unset => 0,
196 Expansion::Scalar(value) => value.len(),
197 Expansion::Array(values) => values.len(),
198 }
199 }
200
201 #[must_use]
203 pub fn is_empty(&self) -> bool {
204 self.len() == 0
205 }
206
207 pub fn split(&self) -> impl Iterator<Item = &str> {
234 match self {
235 Self::Unset => Right([].iter().map(String::as_str)),
236 Self::Scalar(value) => Left(value.split(':')),
237 Self::Array(values) => Right(values.iter().map(String::as_str)),
238 }
239 }
240}
241
242pub fn expand<'a>(var: &'a Variable, mut location: &Location) -> Expansion<'a> {
244 match &var.quirk {
245 None => var.value.as_ref().into(),
246
247 Some(Quirk::LineNumber) => {
248 while let Source::Alias { original, .. } = &*location.code.source {
249 location = original;
250 }
251 let line_number = location.code.line_number(location.range.start);
252 line_number.to_string().into()
253 }
254 }
255}
256
257#[cfg(test)]
258mod tests {
259 use super::*;
260 use crate::alias::Alias;
261 use crate::source::Code;
262 use std::num::NonZeroU64;
263 use std::rc::Rc;
264
265 #[test]
266 fn expand_no_quirk() {
267 let var = Variable::new("foo");
268 let loc = Location::dummy("somewhere");
269 let result = var.expand(&loc);
270 assert_eq!(result, Expansion::Scalar("foo".into()));
271 }
272
273 fn stub_code() -> Rc<Code> {
274 Code {
275 value: "foo\nbar\nbaz\n".to_string().into(),
276 start_line_number: NonZeroU64::new(42).unwrap(),
277 source: Source::Unknown.into(),
278 }
279 .into()
280 }
281
282 #[test]
283 fn expand_line_number_of_first_line() {
284 let var = Variable {
285 quirk: Some(Quirk::LineNumber),
286 ..Default::default()
287 };
288 let code = stub_code();
289 let range = 1..3;
290 let loc = Location { code, range };
291 let result = var.expand(&loc);
292 assert_eq!(result, Expansion::Scalar("42".into()));
293 }
294
295 #[test]
296 fn expand_line_number_of_third_line() {
297 let var = Variable {
298 quirk: Some(Quirk::LineNumber),
299 ..Default::default()
300 };
301 let code = stub_code();
302 let range = 8..12;
303 let loc = Location { code, range };
304 let result = var.expand(&loc);
305 assert_eq!(result, Expansion::Scalar("44".into()));
306 }
307
308 #[test]
309 fn expand_line_number_in_alias() {
310 fn to_alias(original: Location) -> Location {
311 let alias = Alias {
312 name: "name".to_string(),
313 replacement: "replacement".to_string(),
314 global: false,
315 origin: Location::dummy("alias"),
316 }
317 .into();
318 let code = Code {
319 value: " \n \n ".to_string().into(),
320 start_line_number: NonZeroU64::new(15).unwrap(),
321 source: Source::Alias { original, alias }.into(),
322 }
323 .into();
324 let range = 0..1;
325 Location { code, range }
326 }
327
328 let var = Variable {
329 quirk: Some(Quirk::LineNumber),
330 ..Default::default()
331 };
332 let code = stub_code();
333 let range = 8..12;
334 let loc = to_alias(to_alias(Location { code, range }));
335 let result = var.expand(&loc);
336 assert_eq!(result, Expansion::Scalar("44".into()));
337 }
338}