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