1use yaml_rust::yaml::Hash;
2use yaml_rust::Yaml;
3
4use std::cell::RefCell;
5use std::collections::HashMap;
6use std::collections::VecDeque;
7
8use std::convert::TryFrom;
9use std::ops::Deref;
10use std::process::Stdio;
11use std::rc::Rc;
12use std::sync::Arc;
13
14use shellwords::split;
15
16use async_trait::async_trait;
17
18use std::process::ExitStatus;
19
20use scriptplan_core::Command;
21use scriptplan_core::ScriptGroup;
22use scriptplan_core::ScriptParser;
23use scriptplan_core::VarArgs;
24use scriptplan_core::{Alias, CommandGroup, Script};
25
26use tokio;
27use tokio::io::AsyncWriteExt;
28
29pub extern crate scriptplan_core;
30pub extern crate yaml_rust;
31
32#[derive(Debug)]
33pub struct BashCommand {
34 pub command_str: String,
35}
36
37impl From<&str> for BashCommand {
38 fn from(command_str: &str) -> Self {
39 BashCommand::from(command_str.to_string())
40 }
41}
42
43impl From<String> for BashCommand {
44 fn from(command_str: String) -> Self {
45 BashCommand { command_str }
46 }
47}
48
49#[async_trait]
50impl Command for BashCommand {
51 async fn run(&self, vars: VarArgs) -> Result<ExitStatus, ()> {
52 let has_params = self.command_str.contains("$");
53
54 let args: VecDeque<&str> = vars.iter().map(|x| (*x).as_str()).collect();
55 let mut process = tokio::process::Command::new("bash")
56 .stdin(Stdio::piped())
57 .stdout(Stdio::inherit())
58 .env("PS0", "")
59 .env("PS1", "")
60 .env("PS2", "")
61 .arg("-s")
62 .arg("--")
63 .args(args)
64 .spawn()
65 .unwrap();
66
67 let spread_args = if has_params { "" } else { " $@" };
68
69 process
70 .stdin
71 .take()
72 .unwrap()
73 .write_all((self.command_str.to_string() + spread_args).as_bytes())
74 .await
75 .unwrap();
76
77 let output = process.wait_with_output().await.unwrap();
78
79 let status = output.status;
80
81 return Ok(status);
82 }
83}
84
85fn parse_command(command_str: &str) -> Script<BashCommand> {
86 Script::Command(command_str.into())
87}
88
89fn parse_alias(alias_str: &str) -> Script<BashCommand> {
90 let mut words: Vec<_> = split(alias_str).unwrap();
91 Script::Alias(Alias {
92 task: words.remove(0),
93 args: words.into_iter().map(|string| Arc::new(string)).collect(),
94 })
95}
96
97fn yaml_to_group(yaml: &Yaml) -> Result<ScriptGroup<BashCommand>, ()> {
98 let yaml_list = yaml.as_vec().ok_or(())?;
99
100 let mut scripts_iter = yaml_list.iter().map(yaml_to_script);
101
102 let first = (scripts_iter.next().unwrap())?;
103
104 let scripts_result: Result<VecDeque<_>, _> = scripts_iter.collect();
105 let scripts = scripts_result?;
106
107 Ok(ScriptGroup {
108 bail: false,
109 first,
110 rest: scripts,
111 })
112}
113
114fn yaml_to_script(yaml: &Yaml) -> Result<Script<BashCommand>, ()> {
115 if let Some(command_str) = yaml.as_str() {
116 return Ok(parse_command(command_str));
117 } else if let Some(hash) = yaml.as_hash() {
118 if let Some(task) = hash.get(&Yaml::from_str("task")) {
119 return Ok(parse_alias(task.as_str().unwrap()));
121 } else if let Some(command_str) = hash.get(&Yaml::from_str("script")) {
122 return Ok(parse_command(command_str.as_str().unwrap()));
123 } else if let Some(serial_yaml) = hash.get(&Yaml::from_str("series")) {
124 Ok(Script::Group(Box::new(CommandGroup::Series(
125 yaml_to_group(serial_yaml)?,
126 ))))
127 } else if let Some(parallel_yaml) = hash.get(&Yaml::from_str("parallel")) {
128 Ok(Script::Group(Box::new(CommandGroup::Parallel(
129 yaml_to_group(parallel_yaml)?,
130 ))))
131 } else {
132 panic!("should never happen");
133 }
134 } else {
135 panic!("should never happen");
136 }
137}
138
139enum YamlOrTask<'a> {
140 NotLoaded(&'a Yaml),
141 Loaded(Rc<Script<BashCommand>>),
142}
143
144pub struct LazyTask<'a> {
145 yaml_or_task: RefCell<YamlOrTask<'a>>,
146}
147
148impl<'a> From<&'a Yaml> for LazyTask<'a> {
149 fn from(yaml: &'a Yaml) -> Self {
150 LazyTask {
151 yaml_or_task: RefCell::new(YamlOrTask::NotLoaded(yaml)),
152 }
153 }
154}
155
156impl LazyTask<'_> {
157 fn parse(&self) -> Result<Rc<Script<BashCommand>>, ()> {
158 let mut yaml_or_task = self.yaml_or_task.borrow_mut();
159 match yaml_or_task.deref() {
160 &YamlOrTask::Loaded(ref script) => {
161 return Ok(script.clone());
162 }
163 &YamlOrTask::NotLoaded(ref yaml) => {
164 let script: Rc<Script<BashCommand>> = Rc::new(yaml_to_script(*yaml)?);
165 let script_cell = script.clone();
166
167 *yaml_or_task = YamlOrTask::Loaded(script);
168
169 return Ok(script_cell);
170 }
171 }
172 }
173}
174
175pub struct YamlScriptParser<'a> {
176 pub tasks: HashMap<&'a str, LazyTask<'a>>,
177}
178
179impl<'a> TryFrom<&'a Hash> for YamlScriptParser<'a> {
180 type Error = ();
181
182 fn try_from(yaml_object: &'a Hash) -> Result<Self, Self::Error> {
183 let tasks_result: Result<HashMap<&'a str, LazyTask<'a>, _>, _> = yaml_object
184 .iter()
185 .map(|(yaml_name, yaml_value)| -> Result<_, Self::Error> {
186 return Ok((yaml_name.as_str().ok_or(())?, yaml_value.into()));
187 })
188 .collect();
189 Ok(YamlScriptParser {
190 tasks: tasks_result?,
191 })
192 }
193}
194
195impl ScriptParser<BashCommand> for YamlScriptParser<'_> {
196 fn parse(&self, task_name: &str) -> Result<Rc<Script<BashCommand>>, ()> {
197 self.tasks
198 .get(task_name)
199 .expect(format!("The task {} does not exist", task_name).as_str())
200 .parse()
201 }
202}