1use super::Command;
20use thiserror::Error;
21use yash_env::Env;
22use yash_env::alias::Alias;
23use yash_env::alias::HashEntry;
24use yash_env::semantics::Field;
25use yash_env::source::pretty::{Report, ReportType, Snippet};
26use yash_quote::quoted;
27
28#[derive(Clone, Debug, Eq, Error, PartialEq)]
30pub enum Error {
31 #[error("alias {name} not found")]
33 NonExistentAlias { name: Field },
34}
35
36impl Error {
37 #[must_use]
39 pub fn to_report(&self) -> Report<'_> {
40 let mut report = Report::new();
41 report.r#type = ReportType::Error;
42 report.title = "cannot print alias definition".into();
43 report.snippets = match self {
44 Self::NonExistentAlias { name } => {
45 Snippet::with_primary_span(&name.origin, self.to_string().into())
46 }
47 };
48 report
49 }
50}
51
52impl<'a> From<&'a Error> for Report<'a> {
53 #[inline]
54 fn from(error: &'a Error) -> Self {
55 error.to_report()
56 }
57}
58
59fn define<S>(env: &mut Env<S>, name_value: Field) -> Result<(), Field> {
64 let Some(equal) = name_value.value.find('=') else {
65 return Err(name_value);
66 };
67 let replacement = name_value.value[equal + 1..].to_owned();
68 let name = {
69 let mut name = name_value.value;
70 name.truncate(equal);
71 name.shrink_to_fit();
73 name
74 };
75 let global = false;
77
78 env.aliases
79 .replace(HashEntry::new(name, replacement, global, name_value.origin));
80
81 Ok(())
82}
83
84fn find_and_print<S>(env: &Env<S>, name: Field, result: &mut String) -> Result<(), Error> {
89 let alias = env
90 .aliases
91 .get(name.value.as_str())
92 .ok_or(Error::NonExistentAlias { name })?;
93
94 print(&alias.0, result);
95
96 Ok(())
97}
98
99fn print(alias: &Alias, result: &mut String) {
102 use std::fmt::Write as _;
103 writeln!(
104 result,
105 "{}={}",
106 quoted(&alias.name),
107 quoted(&alias.replacement),
108 )
109 .unwrap();
110}
111
112impl Command {
113 pub async fn execute<S>(self, env: &mut Env<S>) -> (String, Vec<Error>) {
118 let mut output = String::new();
119 let mut errors = Vec::new();
120
121 if self.operands.is_empty() {
122 let mut aliases = env.aliases.iter().collect::<Vec<_>>();
124 aliases.sort_unstable_by_key(|alias| &alias.0.name);
126 for alias in aliases {
127 print(&alias.0, &mut output);
128 }
129 } else {
130 for operand in self.operands {
131 if let Err(operand) = define(env, operand) {
132 let result = find_and_print(env, operand, &mut output);
133 errors.extend(result.err());
134 }
135 }
136 }
137
138 (output, errors)
139 }
140}
141
142#[cfg(test)]
143mod tests {
144 use super::*;
145 use futures_util::FutureExt as _;
146 use yash_env::source::Location;
147
148 #[test]
149 fn defining_alias() {
150 let mut env = Env::new_virtual();
151 let origin = Location::dummy("definition location");
152
153 let result = define(
154 &mut env,
155 Field {
156 value: "foo=bar".into(),
157 origin: origin.clone(),
158 },
159 );
160
161 assert_eq!(result, Ok(()));
162 assert_eq!(
163 *env.aliases.get("foo").unwrap().0,
164 Alias {
165 name: "foo".into(),
166 replacement: "bar".into(),
167 global: false,
168 origin,
169 }
170 );
171 }
172
173 #[test]
174 fn defining_alias_without_value() {
175 let mut env = Env::new_virtual();
176 let field = Field::dummy("valueless");
177 let result = define(&mut env, field.clone());
178 assert_eq!(result, Err(field));
179 assert_eq!(env.aliases.len(), 0);
180 }
181
182 #[test]
183 fn finding_and_printing_alias() {
184 let mut env = Env::new_virtual();
185 env.aliases.insert(HashEntry::new(
186 "foo".into(),
187 "bar".into(),
188 false,
189 Location::dummy("definition location"),
190 ));
191 let mut result = String::new();
192
193 let return_value = find_and_print(&env, Field::dummy("foo"), &mut result);
194
195 assert_eq!(return_value, Ok(()));
196 assert_eq!(result, "foo=bar\n");
197 }
198
199 #[test]
200 fn finding_non_existent_alias() {
201 let name = Field::dummy("foo");
202 let mut result = String::new();
203
204 let return_value = find_and_print(&Env::new_virtual(), name.clone(), &mut result);
205
206 assert_eq!(return_value, Err(Error::NonExistentAlias { name }));
207 assert_eq!(result, "");
208 }
209
210 #[test]
211 fn printing_quoted_alias_name() {
212 let alias = Alias {
213 name: "foo bar".into(),
214 replacement: "x".into(),
215 global: false,
216 origin: Location::dummy("definition location"),
217 };
218 let mut result = String::new();
219
220 print(&alias, &mut result);
221
222 assert_eq!(result, "'foo bar'=x\n");
223 }
224
225 #[test]
226 fn printing_quoted_alias_value() {
227 let alias = Alias {
228 name: "ll".into(),
229 replacement: "ls -l".into(),
230 global: false,
231 origin: Location::dummy("definition location"),
232 };
233 let mut result = String::new();
234
235 print(&alias, &mut result);
236
237 assert_eq!(result, "ll='ls -l'\n");
238 }
239
240 #[test]
241 fn executing_with_operands() {
242 let mut env = Env::new_virtual();
243 let operands = Field::dummies(["foo=bar", "bar", "foo"]);
244 let command = Command { operands };
245
246 let (output, errors) = command.execute(&mut env).now_or_never().unwrap();
247
248 assert_eq!(output, "foo=bar\n");
249 assert_eq!(
250 errors,
251 [Error::NonExistentAlias {
252 name: Field::dummy("bar")
253 }]
254 );
255 }
256
257 #[test]
258 fn executing_with_no_operands() {
259 let mut env = Env::new_virtual();
260 env.aliases.insert(HashEntry::new(
261 "foo".into(),
262 "bar".into(),
263 false,
264 Location::dummy("foo location"),
265 ));
266 env.aliases.insert(HashEntry::new(
267 "ll".into(),
268 "ls -l".into(),
269 false,
270 Location::dummy("ll location"),
271 ));
272 env.aliases.insert(HashEntry::new(
273 "ls".into(),
274 "ls --color".into(),
275 false,
276 Location::dummy("ls location"),
277 ));
278 env.aliases.insert(HashEntry::new(
279 "cat".into(),
280 "cat".into(),
281 false,
282 Location::dummy("cat location"),
283 ));
284
285 let command = Command { operands: vec![] };
286
287 let (output, errors) = command.execute(&mut env).now_or_never().unwrap();
288 assert_eq!(output, "cat=cat\nfoo=bar\nll='ls -l'\nls='ls --color'\n");
290 assert_eq!(errors, []);
291 }
292}