runscript/exec.rs
1use std::collections::HashMap;
2use std::path::Path;
3use std::process::{ExitStatus, Output, Stdio};
4use std::sync::atomic::AtomicBool;
5use std::sync::mpsc::{Receiver, Sender};
6use std::sync::{mpsc, Arc};
7
8use conch_parser::ast::{
9 Arithmetic, AtomicCommandList, AtomicShellPipeableCommand, AtomicTopLevelCommand,
10 AtomicTopLevelWord, ComplexWord, ListableCommand,
11 Parameter, ParameterSubstitution, PipeableCommand, Redirect, RedirectOrCmdWord, SimpleCommand,
12 SimpleWord, Word,
13};
14use glob::{MatchOptions, Pattern, glob_with};
15use itertools::Itertools;
16
17use crate::parser::RunscriptLocation;
18use crate::{run, Script};
19
20#[derive(Clone)]
21pub struct ExecConfig<'a> {
22 /// The verbosity of output to the supplied output stream
23 pub verbosity: Verbosity,
24 /// The output stream to output to, or `None` to produce no output
25 pub output_stream: Option<Arc<termcolor::StandardStream>>,
26 /// The working directory to execute the script's commands in
27 pub working_directory: &'a Path,
28 /// Positional arguments to pass to the script.
29 ///
30 ///The first argument replaces `$1`, the second replaces `$2`, etc.
31 pub positional_args: Vec<String>,
32 /// Whether to store the text printed to stdout by the executed programs
33 pub capture_stdout: bool,
34 /// A map of environment variables to remap
35 pub env_remap: &'a HashMap<String, String>,
36}
37
38#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
39pub enum Verbosity {
40 Normal,
41 Quiet,
42 Silent,
43}
44
45#[derive(Debug)]
46pub enum ProcessExit {
47 Bool(bool),
48 Status(ExitStatus),
49}
50
51#[derive(Debug)]
52pub struct ProcessOutput {
53 pub status: ProcessExit,
54 pub stdout: Vec<u8>,
55 pub stderr: Vec<u8>,
56}
57
58#[derive(Debug)]
59pub enum CommandExecError {
60 InvalidGlob {
61 glob: String,
62 loc: RunscriptLocation,
63 },
64 NoGlobMatches {
65 glob: String,
66 loc: RunscriptLocation,
67 },
68 BadCommand {
69 err: std::io::Error,
70 loc: RunscriptLocation,
71 },
72}
73
74impl ProcessExit {
75 /// Whether the process was successful or not.
76 ///
77 /// Returns the value of a `Bool` variant, or calls the `success` function on a `Status` variant
78 pub fn success(&self) -> bool {
79 match self {
80 ProcessExit::Bool(b) => *b,
81 ProcessExit::Status(s) => s.success(),
82 }
83 }
84
85 /// The exit code the process exited with, if any.
86 ///
87 /// Coerces the `Bool` variant into `true = 0`, `false = 1`
88 pub fn code(&self) -> Option<i32> {
89 match self {
90 ProcessExit::Bool(b) => Some(!b as i32),
91 ProcessExit::Status(s) => s.code(),
92 }
93 }
94
95 /// The `ExitStatus` the process exited with.
96 ///
97 /// Panics on the `Bool` variant if on neither unix nor windows
98 pub fn status(self) -> ExitStatus {
99 match self {
100 ProcessExit::Status(s) => s,
101 ProcessExit::Bool(b) => convert_bool(b),
102 }
103 }
104}
105
106#[cfg(unix)]
107fn convert_bool(b: bool) -> ExitStatus {
108 use std::os::unix::process::ExitStatusExt;
109
110 ExitStatus::from_raw(!b as i32)
111}
112
113#[cfg(windows)]
114fn convert_bool(b: bool) -> ExitStatus {
115 use std::os::windows::process::ExitStatusExt;
116
117 ExitStatus::from_raw(!b as u32)
118}
119
120#[cfg(not(any(unix, windows)))]
121fn convert_bool(_: bool) -> ExitStatus {
122 panic!("Expected ExitStatus")
123}
124
125impl ProcessOutput {
126 pub fn new(success: bool) -> ProcessOutput {
127 ProcessOutput {
128 status: ProcessExit::Bool(success),
129 stdout: vec![],
130 stderr: vec![],
131 }
132 }
133}
134
135impl From<Output> for ProcessOutput {
136 fn from(o: Output) -> Self {
137 ProcessOutput {
138 status: ProcessExit::Status(o.status),
139 stdout: o.stdout,
140 stderr: o.stderr,
141 }
142 }
143}
144
145struct Interrupt;
146
147struct Interruptable(Receiver<Interrupt>, AtomicBool);
148struct Interrupter(Sender<Interrupt>);
149
150impl Interruptable {
151 fn was_interrupted(&self) -> bool {
152 self.1.fetch_or(
153 self.0.try_recv().is_ok(),
154 std::sync::atomic::Ordering::AcqRel,
155 )
156 }
157}
158
159impl Interrupter {
160 fn new() -> (Interrupter, Interruptable) {
161 let (sender, receiver) = mpsc::channel();
162 (
163 Interrupter(sender),
164 Interruptable(receiver, AtomicBool::new(false)),
165 )
166 }
167
168 fn interrupt(&self) {
169 self.0.send(Interrupt);
170 }
171}
172
173pub fn exec_script(
174 script: &Script,
175 config: &ExecConfig,
176) -> Result<ProcessOutput, CommandExecError> {
177 exec_script_entries(&script.commands, config, &HashMap::new())
178}
179
180fn exec_script_entries(
181 commands: &[AtomicTopLevelCommand<String>],
182 config: &ExecConfig,
183 env: &HashMap<String, String>,
184) -> Result<ProcessOutput, CommandExecError> {
185 // let mut stdout = vec![];
186 // let mut stderr = vec![];
187 crossbeam_utils::thread::scope(|s| {
188 let mut jobs = vec![];
189
190 for AtomicTopLevelCommand(command) in commands {
191 use conch_parser::ast::Command;
192
193 let (command, is_job) = match command {
194 Command::Job(job) => (job, true),
195 Command::List(list) => (list, false),
196 };
197
198 let (interrupter, interruptable) = Interrupter::new();
199
200 if is_job {
201 s.spawn(move |_| exec_andor_list(command, config, env, interruptable));
202 jobs.push(interrupter);
203 } else {
204 exec_andor_list(command, config, env, interruptable);
205 }
206 }
207
208 for job in jobs {
209 job.interrupt();
210 }
211 })
212 .unwrap();
213
214 Ok(ProcessOutput::new(true))
215}
216
217fn exec_andor_list(
218 command: &AtomicCommandList<String, AtomicTopLevelWord<String>, AtomicTopLevelCommand<String>>,
219 config: &ExecConfig,
220 env: &HashMap<String, String>,
221 interrupter: Interruptable,
222) -> Result<ProcessOutput, CommandExecError> {
223 let mut previous_output = exec_listable_command(&command.first, config, env, &interrupter);
224 //TODO: Chain
225 Ok(ProcessOutput::new(true))
226}
227
228fn exec_listable_command(
229 command: &ListableCommand<
230 AtomicShellPipeableCommand<
231 String,
232 AtomicTopLevelWord<String>,
233 AtomicTopLevelCommand<String>,
234 >,
235 >,
236 config: &ExecConfig,
237 env: &HashMap<String, String>,
238 interrupter: &Interruptable,
239) -> Result<ProcessOutput, CommandExecError> {
240 match command {
241 ListableCommand::Pipe(negate, commands) => todo!(),
242 ListableCommand::Single(command) => {
243 exec_pipeable_command(&command, config, env, interrupter)
244 }
245 }
246}
247
248fn exec_pipeable_command(
249 command: &AtomicShellPipeableCommand<
250 String,
251 AtomicTopLevelWord<String>,
252 AtomicTopLevelCommand<String>,
253 >,
254 config: &ExecConfig,
255 env: &HashMap<String, String>,
256 interrupter: &Interruptable,
257) -> Result<ProcessOutput, CommandExecError> {
258 match command {
259 PipeableCommand::Simple(command) => exec_simple_command(&command, config, env, interrupter),
260 PipeableCommand::Compound(command) => {
261 // Stuff like if, while, until, etc.
262 todo!();
263 }
264 PipeableCommand::FunctionDef(name, body) => {
265 todo!() //TODO: What to do here?? Store it in a hashmap?
266 // Should I even support functions at all?
267 // Probably not, since that's kinda what I'm doing anyway.
268 }
269 }
270}
271
272fn exec_simple_command(
273 command: &SimpleCommand<
274 String,
275 AtomicTopLevelWord<String>,
276 Redirect<AtomicTopLevelWord<String>>,
277 >,
278 config: &ExecConfig,
279 env: &HashMap<String, String>,
280 interrupter: &Interruptable,
281) -> Result<ProcessOutput, CommandExecError> {
282 use std::process::Command;
283
284 // TODO: Env variables
285 // TODO: Redirects
286
287 let command_words = command
288 .redirects_or_cmd_words
289 .iter()
290 .filter_map(|r| {
291 if let RedirectOrCmdWord::CmdWord(w) = r {
292 Some(evaluate_tl_word(w, config, env))
293 } else {
294 None
295 }
296 })
297 .flatten_ok()
298 .collect::<Result<Vec<_>, _>>()?;
299
300 // TODO: Print pre-evaluated command words
301 eprintln!(
302 "> {}",
303 command_words.join(" ")
304 );
305
306 // TODO: "Builtin" commands
307 // TODO: Interruption
308
309 let output = Command::new(&command_words[0])
310 .args(&command_words[1..])
311 .stdin(Stdio::inherit())
312 .stdout(if config.capture_stdout {
313 Stdio::piped()
314 } else {
315 Stdio::inherit()
316 })
317 .stderr(if config.capture_stdout {
318 Stdio::piped()
319 } else {
320 Stdio::inherit()
321 })
322 .output()
323 .unwrap();
324
325 Ok(output.into())
326}
327
328fn evaluate_tl_word(
329 AtomicTopLevelWord(word): &AtomicTopLevelWord<String>,
330 config: &ExecConfig,
331 env: &HashMap<String, String>,
332) -> Result<Vec<String>, CommandExecError> {
333 match word {
334 ComplexWord::Concat(words) => {
335 let words = words
336 .iter()
337 .map(|w| evaluate_word(w, config, env))
338 .collect::<Result<Vec<_>, _>>()?;
339 let is_glob = !words.iter().all(|w| matches!(w, GlobPart::Words(_)));
340 if is_glob {
341 let working_dir_path = {
342 let mut path = Pattern::escape(&config.working_directory.to_string_lossy().into_owned());
343 path.push('/');
344 path
345 };
346 let stringified_glob = std::iter::once(working_dir_path.clone()).chain(words.iter().map(|w| match w {
347 GlobPart::Words(words) => words.iter().map(|s| Pattern::escape(s)).join(" "),
348 GlobPart::Star => "*".to_owned(),
349 GlobPart::Question => "?".to_owned(),
350 GlobPart::SquareOpen => "[".to_owned(),
351 GlobPart::SquareClose => "]".to_owned(),
352 })).join("");
353 Ok(glob_with(&stringified_glob, MatchOptions::new()).unwrap().map(|path| path.unwrap().to_string_lossy().into_owned().strip_prefix(&working_dir_path).unwrap().to_owned()).collect::<Vec<_>>())
354 } else {
355 Ok(words.into_iter().flat_map(GlobPart::into_string).collect())
356 }
357 },
358 ComplexWord::Single(word) => evaluate_word(word, config, env).map(GlobPart::into_string),
359 }
360}
361
362fn evaluate_word(
363 word: &Word<
364 String,
365 SimpleWord<
366 String,
367 Parameter<String>,
368 Box<
369 ParameterSubstitution<
370 Parameter<String>,
371 AtomicTopLevelWord<String>,
372 AtomicTopLevelCommand<String>,
373 Arithmetic<String>,
374 >,
375 >,
376 >,
377 >,
378 config: &ExecConfig,
379 env: &HashMap<String, String>,
380) -> Result<GlobPart, CommandExecError> {
381 match word {
382 Word::SingleQuoted(literal) => Ok(vec![literal.clone()].into()),
383 Word::DoubleQuoted(words) => Ok(vec![words
384 .iter()
385 .map(|w| evaluate_simple_word(w, config, env).map(GlobPart::into_string))
386 .flatten_ok()
387 .collect::<Result<String, _>>()?].into()),
388 Word::Simple(word) => evaluate_simple_word(word, config, env),
389 }
390}
391
392fn evaluate_simple_word(
393 word: &SimpleWord<
394 String,
395 Parameter<String>,
396 Box<
397 ParameterSubstitution<
398 Parameter<String>,
399 AtomicTopLevelWord<String>,
400 AtomicTopLevelCommand<String>,
401 Arithmetic<String>,
402 >,
403 >,
404 >,
405 config: &ExecConfig,
406 env: &HashMap<String, String>,
407) -> Result<GlobPart, CommandExecError> {
408 match word {
409 SimpleWord::Literal(s) => Ok(vec![s.clone()].into()),
410 SimpleWord::Escaped(s) => Ok(vec![s.clone()].into()),
411 SimpleWord::Param(p) => Ok(evaluate_parameter(p, config, env).into()),
412 SimpleWord::Subst(p) => Ok(evaluate_param_subst(p, config, env)?.into()),
413 SimpleWord::Star => Ok(GlobPart::Star),
414 SimpleWord::Question => Ok(GlobPart::Question),
415 SimpleWord::SquareOpen => Ok(GlobPart::SquareOpen),
416 SimpleWord::SquareClose => Ok(GlobPart::SquareClose),
417 SimpleWord::Tilde => todo!(),
418 SimpleWord::Colon => Ok(vec![":".to_owned()].into()),
419 }
420}
421
422fn evaluate_param_subst(
423 param: &ParameterSubstitution<
424 Parameter<String>,
425 AtomicTopLevelWord<String>,
426 AtomicTopLevelCommand<String>,
427 Arithmetic<String>,
428 >,
429 config: &ExecConfig,
430 env: &HashMap<String, String>,
431) -> Result<Vec<String>, CommandExecError> {
432 match param {
433 ParameterSubstitution::Command(commands) => exec_script_entries(commands, config, env).map(|output| vec![String::from_utf8(output.stdout).unwrap()]),
434 ParameterSubstitution::Len(p) => Ok(vec![format!("{}", match p {
435 Parameter::At | Parameter::Star => config.positional_args.len(),
436 p => evaluate_parameter(p, config, env).into_iter().map(|s| s.len()).reduce(|acc, s| acc + s + 1).unwrap_or(0),
437 })]),
438 ParameterSubstitution::Arith(_) => todo!(),
439 ParameterSubstitution::Default(_, _, _) => todo!(),
440 ParameterSubstitution::Assign(_, _, _) => todo!(),
441 ParameterSubstitution::Error(_, _, _) => todo!(),
442 ParameterSubstitution::Alternative(_, _, _) => todo!(),
443 ParameterSubstitution::RemoveSmallestSuffix(_, _) => todo!(),
444 ParameterSubstitution::RemoveLargestSuffix(_, _) => todo!(),
445 ParameterSubstitution::RemoveSmallestPrefix(_, _) => todo!(),
446 ParameterSubstitution::RemoveLargestPrefix(_, _) => todo!(),
447 }
448}
449
450fn evaluate_parameter(
451 parameter: &Parameter<String>,
452 config: &ExecConfig,
453 env: &HashMap<String, String>,
454) -> Vec<String> {
455 match parameter {
456 Parameter::Positional(n) => config
457 .positional_args
458 .get(*n as usize)
459 .cloned()
460 .into_iter()
461 .collect(),
462 Parameter::Var(name) => env
463 .get(name)
464 .cloned()
465 .or_else(|| std::env::var(name).ok())
466 .into_iter()
467 .collect(),
468 Parameter::At => config.positional_args.clone(),
469 Parameter::Pound => vec![format!("{}", config.positional_args.len())],
470 Parameter::Dollar => vec![format!("{}", std::process::id())],
471
472 Parameter::Star => todo!(), // Like @ but runs evaluate_word on each word
473 Parameter::Question => todo!(), // Exit code of previous top-level command
474 Parameter::Dash => todo!(), // Options of current run invocation. Perhaps could be useful?
475 Parameter::Bang => todo!(), // PID of most recent job
476 }
477}
478
479enum GlobPart {
480 Words(Vec<String>),
481 Star,
482 Question,
483 SquareOpen,
484 SquareClose,
485}
486
487impl GlobPart {
488 fn into_string(self) -> Vec<String> {
489 match self {
490 GlobPart::Words(words) => words,
491 GlobPart::Star => vec!["*".to_owned()],
492 GlobPart::Question => vec!["?".to_owned()],
493 GlobPart::SquareOpen => vec!["[".to_owned()],
494 GlobPart::SquareClose => vec!["]".to_owned()],
495 }
496 }
497}
498
499impl From<Vec<String>> for GlobPart {
500 fn from(v: Vec<String>) -> Self {
501 Self::Words(v)
502 }
503}
504
505/*
506
507fn exec_script_entries(entries: &[ScriptEntry], config: &ExecConfig, env_remap: &HashMap<String, String>) -> Result<ProcessOutput, (CommandExecError, ScriptEntry)> {
508 let mut env = config.env_remap.clone();
509 let mut stdout_acc = Vec::new();
510 let mut stderr_acc = Vec::new();
511 for entry in entries {
512 match entry {
513 ScriptEntry::Command(command) => {
514 let output = exec_tl_cmd(&command, config, &env, config.capture_stdout)
515 .map_err(|e| e.either(|e| (e, entry.clone()), |e| e))?;
516 if config.capture_stdout {
517 stdout_acc.extend(output.stdout.into_iter());
518 stderr_acc.extend(output.stderr.into_iter());
519 }
520 if !output.status.success() {
521 if config.verbosity < Verbosity::Silent {
522 match output.status.code() {
523 Some(i) => eprintln!("=> exit {}", i),
524 None => eprintln!("=> {}", signal(&output.status.status())),
525 }
526 }
527 return Ok(ProcessOutput {
528 status: ProcessExit::Bool(false),
529 stdout: stdout_acc,
530 stderr: stderr_acc,
531 });
532 }
533 },
534 ScriptEntry::Env { var, val, loc } => {
535 if config.verbosity < Verbosity::Silent {
536 eprintln!("> {}", entry);
537 }
538 env.insert(
539 var.clone(),
540 evaluate_arg(val, loc.clone(), config, &env, false).map_err(|e| (e, entry.clone()))?.join(" ")
541 );
542 }
543 }
544 }
545 Ok(ProcessOutput {
546 status: ProcessExit::Bool(true),
547 stdout: stdout_acc,
548 stderr: stderr_acc,
549 })
550}
551
552fn exec_tl_cmd(command: &script::TopLevelCommand, config: &ExecConfig, env_remap: &HashMap<String, String>, piped: bool) -> Result<ProcessOutput, Either<CommandExecError, (CommandExecError, ScriptEntry)>> {
553 match command {
554 script::TopLevelCommand::Command(c) => exec_cmd(c, config, env_remap, piped),
555 script::TopLevelCommand::BlockCommand(entries) => exec_script_entries(&entries, config, env_remap).map_err(|e| Right(e)),
556 }
557}
558
559fn exec_cmd(command: &script::Command, config: &ExecConfig, env_remap: &HashMap<String, String>, piped: bool) -> Result<ProcessOutput, Either<CommandExecError, (CommandExecError, ScriptEntry)>> {
560 let target = &evaluate_arg(&command.target, command.loc.clone(), config, env_remap, false).map_err(|e| Left(e))?[0];
561 if target == "run" {
562 if config.verbosity < Verbosity::Silent {
563 eprintln!("> {}", command);
564 }
565 let args = command.args.iter()
566 .map(|x| evaluate_arg(x, command.loc.clone(), config, env_remap, true))
567 .collect::<Result<Vec<Vec<String>>, CommandExecError>>().map_err(|e| Left(e))?
568 .into_iter()
569 .flatten()
570 .collect::<Vec<_>>();
571 //TODO: Pass env
572 return run(
573 &args.iter().map(|s| &**s).collect::<Vec<_>>(),
574 config.working_directory,
575 config.verbosity,
576 piped,
577 env_remap,
578 ).map_err(|err| Left(CommandExecError::BadCommand { err, loc: command.loc.clone() }));
579 }
580
581 let mut stdin: Option<Vec<u8>> = None;
582
583 let chained_output = match &*command.chained {
584 ChainedCommand::Pipe(c) => {
585 let h = exec_cmd(&c, config, env_remap, true)?;
586 stdin = Some(h.stdout);
587 None
588 },
589 ChainedCommand::And(c) => {
590 let h = exec_tl_cmd(&c, config, env_remap, piped)?;
591 if !h.status.success() {
592 return Ok(h);
593 }
594 Some(h.stdout)
595 },
596 ChainedCommand::Or(c) => {
597 let h = exec_tl_cmd(&c, config, env_remap, piped)?;
598 if h.status.success() {
599 return Ok(h);
600 }
601 Some(h.stdout)
602 },
603 ChainedCommand::None => None
604 };
605
606 let mut args = Vec::with_capacity(command.args.len());
607 let mut args_did_evaluate = false;
608 for arg in &command.args {
609 match arg {
610 Argument::Unquoted(ArgPart::Str(_)) | Argument::Single(_) => {}
611 Argument::Double(v) if v.iter().all(|p| matches!(p, ArgPart::Str(_))) => {}
612 _ => args_did_evaluate = true,
613 }
614 match evaluate_arg(arg, command.loc.clone(), config, env_remap, true) {
615 Ok(v) => args.extend(v),
616 Err(e) => {
617 eprintln!("> {}", command);
618 return Err(Left(e));
619 }
620 }
621 }
622
623 if config.verbosity < Verbosity::Silent {
624 if let Some(mut lock) = config.output_stream.as_deref().map(termcolor::StandardStream::lock) {
625 write!(lock, "> {}", command).expect("Failed to write");
626 if args_did_evaluate {
627 use termcolor::WriteColor;
628
629 lock.set_color(ColorSpec::new().set_italic(true)).expect("Failed to set italic");
630 write!(
631 lock,
632 " = {}{}",
633 target,
634 args.iter()
635 .map(|arg| if arg.chars().all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-') {
636 arg.clone()
637 } else {
638 format!("'{}'", arg)
639 })
640 .fold("".to_owned(), |mut acc, s| { acc.push(' '); acc.push_str(&s); acc })
641 ).expect("Failed to write");
642 lock.reset().expect("Failed to reset colour");
643 }
644 writeln!(lock).expect("Failed to write");
645 }
646 }
647
648 let mut child = match Command::new(target)
649 .args(args)
650 .current_dir(config.working_directory)
651 .envs(env_remap)
652 .stdin(match stdin { Some(_) => Stdio::piped(), None => if config.verbosity >= Verbosity::Quiet { Stdio::null() } else { Stdio::inherit() } })
653 .stdout(if piped { Stdio::piped() } else if config.verbosity >= Verbosity::Quiet { Stdio::null() } else { Stdio::inherit() })
654 .stderr(if piped { Stdio::piped() } else if config.verbosity >= Verbosity::Quiet { Stdio::null() } else { Stdio::inherit() })
655 .spawn() {
656 Ok(c) => c,
657 Err(e) => {
658 return Err(Left(CommandExecError::BadCommand{
659 err: e,
660 loc: command.loc.clone(),
661 }))
662 }
663 };
664
665 if let Some(v) = stdin {
666 let buffer = &v;
667 child.stdin.as_mut().unwrap().write_all(buffer).expect("Failed to write stdin");
668 }
669
670 let mut output = ProcessOutput::from(child.wait_with_output().expect("Command was never started"));
671 if let Some(mut o) = chained_output {
672 o.append(&mut output.stdout);
673 output.stdout = o;
674 }
675
676 Ok(output)
677}
678
679fn evaluate_arg(arg: &Argument, command_loc: RunscriptLocation, config: &ExecConfig, env_override: &HashMap<String, String>, match_globs: bool) -> Result<Vec<String>, CommandExecError> {
680 match arg {
681 Argument::Unquoted(p) => match p {
682 ArgPart::Str(s) => {
683 if match_globs && s.chars().any(|c| c == '*' || c == '(' || c == '|' || c == '<' || c == '[' || c == '?') {
684 match Glob::new(&s) {
685 Ok(walker) => {
686 let cwd = config.working_directory;
687 let strings = walker.walk(cwd).into_iter()
688 .map(|k| k.to_string_lossy().into_owned())
689 .collect::<Vec<String>>();
690 if strings.is_empty() {
691 Err(CommandExecError::NoGlobMatches { glob: s.clone(), loc: command_loc })
692 } else {
693 Ok(strings)
694 }
695 },
696 Err(err) => Err(CommandExecError::InvalidGlob { glob: s.clone(), err, loc: command_loc })
697 }
698 } else {
699 Ok(vec![s.clone()])
700 }
701 },
702 _ => evaluate_part(p, env_override, config),
703 },
704 Argument::Single(s) => Ok(vec![s.clone()]),
705 Argument::Double(p) => {
706 let args = p.iter().map(|x| evaluate_part(x, env_override, config)).collect::<Result<Vec<Vec<String>>, CommandExecError>>()?.iter().fold(vec![], |mut acc, x| { acc.append(&mut x.clone()); acc });
707 Ok(vec![args.iter().fold(String::new(), |acc, s| acc + &s)])
708 }
709 }
710}
711
712fn evaluate_part(part: &ArgPart, env_override: &HashMap<String, String>, config: &ExecConfig) -> Result<Vec<String>, CommandExecError> {
713 use std::env::VarError::*;
714 match part {
715 ArgPart::Str(s) => Ok(vec![s.clone()]),
716 ArgPart::Arg(n) => Ok(vec![config.positional_args.get(*n - 1).cloned().unwrap_or_else(|| "".to_owned())]),
717 ArgPart::Var(v) => Ok(vec![env_override.get(v).cloned().unwrap_or_else(|| std::env::var(v).unwrap_or_else(|err| match err { NotPresent => "".to_owned(), NotUnicode(s) => s.to_string_lossy().into_owned() }))]),
718 ArgPart::AllArgs => Ok(config.positional_args.clone()),
719 ArgPart::Cmd(c) => {
720 Ok(vec![String::from_utf8_lossy(
721 &match Command::new(&evaluate_arg(&c.target, c.loc.clone(), config, env_override, false)?[0])
722 .args(c.args.iter().map(|x| evaluate_arg(x, c.loc.clone(), config, env_override, true)).collect::<Result<Vec<Vec<String>>, CommandExecError>>()?.iter().fold(vec![], |mut acc, x| { acc.append(&mut x.clone()); acc }))
723 .current_dir(config.working_directory)
724 .output() {
725 Ok(o) => o.stdout,
726 Err(e) => return Err(CommandExecError::BadCommand {
727 err: e,
728 loc: c.loc.clone(),
729 })
730 }
731 ).into_owned()])
732 }
733 }
734}
735
736*/
737
738#[cfg(unix)]
739use std::os::raw::{c_char, c_int};
740
741#[cfg(unix)]
742extern "C" {
743 fn strsignal(sig: c_int) -> *const c_char;
744}
745
746#[cfg(unix)]
747fn signal(status: &ExitStatus) -> String {
748 use std::ffi::CStr;
749 use std::os::unix::process::ExitStatusExt;
750
751 let signal = status.signal().expect("Expected signal");
752
753 // SAFETY: Function is guaranteed by POSIX to exist.
754 let sigstr = unsafe { CStr::from_ptr(strsignal(signal as c_int)) };
755 format!(
756 "signal {} ({})",
757 signal,
758 sigstr.to_string_lossy().to_owned()
759 )
760}
761
762#[cfg(not(unix))]
763fn signal(_: &ExitStatus) -> String {
764 panic!("Non-unix program terminated with signal");
765}