scriptplan_core/
lib.rs

1use std::collections::vec_deque::Iter;
2use std::collections::VecDeque;
3use std::iter::{Chain, Iterator, Once};
4use std::process::ExitStatus;
5use std::rc::Rc;
6use std::sync::Arc;
7
8use futures::future::join_all;
9use futures::join;
10
11use async_recursion::async_recursion;
12use async_trait::async_trait;
13
14use scriptplan_lang_utils::{has_parameters, apply_args};
15
16#[async_trait]
17pub trait Command {
18    async fn run(&self, args: VarArgs) -> Result<ExitStatus, ()>;
19}
20
21pub type VarArgs = VecDeque<Arc<String>>;
22#[derive(Debug)]
23pub struct ScriptGroup<CommandGeneric: Command> {
24    pub bail: bool,
25    // Enforces that there's always at least 1 script
26    pub first: Script<CommandGeneric>,
27    pub rest: VecDeque<Script<CommandGeneric>>,
28}
29
30impl<CommandGeneric: Command> ScriptGroup<CommandGeneric> {
31    fn iter(&self) -> Chain<Once<&'_ Script<CommandGeneric>>, Iter<'_, Script<CommandGeneric>>> {
32        std::iter::once(&self.first).chain(self.rest.iter())
33    }
34}
35
36// TODO: Don't do this lol :3
37fn clone_args(args: &VarArgs) -> VarArgs {
38    return args.iter().map(|x| x.clone()).collect();
39}
40
41/**
42 * TODO: Choose a better name
43 */
44#[derive(Debug)]
45pub enum CommandGroup<CommandGeneric: Command> {
46    Parallel(ScriptGroup<CommandGeneric>),
47    Series(ScriptGroup<CommandGeneric>),
48}
49
50fn merge_status(status1: ExitStatus, status2: ExitStatus) -> ExitStatus {
51    if !status1.success() {
52        return status2;
53    }
54    return status1;
55}
56
57
58impl<CommandGeneric: Command> CommandGroup<CommandGeneric> {
59    async fn run(
60        &self,
61        parser: &impl ScriptParser<CommandGeneric>,
62        args: VarArgs,
63    ) -> Result<ExitStatus, ()> {
64        async fn run_script<'a, CommandGeneric: Command>(script: &'a Script<CommandGeneric>, args: &VarArgs, parser: &impl ScriptParser<CommandGeneric>) -> Result<ExitStatus, ()> {
65            match script {
66                Script::Alias(alias) => alias.run(parser, if has_parameters(&alias.args) { clone_args(&args) } else { VecDeque::new( ) }).await,
67                default => default.run(parser, clone_args(&args)).await 
68            }
69        }
70        // TODO: Figure out what to do with args
71        match self {
72            Self::Parallel(group) => {
73                if group.bail {
74                    println!("Warning: Bail in parallel groups are currently not supported");
75                }
76
77                let mut promises = Vec::<_>::new();
78
79                let mut group_iter = group.iter();
80                let mut command = group_iter.next().unwrap();
81
82                while let Some(next_command) = group_iter.next() {
83                    promises.push(run_script(command, &args, parser));
84
85                    command = next_command;
86                }
87
88                let last_result = run_script(command, &args, parser);
89                let (results, last) = join!(join_all(promises), last_result);
90
91                let final_status =
92                    results
93                        .into_iter()
94                        .fold(last.unwrap(), |prev_exit_status, this_result| {
95                            if let Ok(current_status) = this_result {
96                                return merge_status(prev_exit_status, current_status);
97                            } else {
98                                // TODO: Do something with the error
99                                return prev_exit_status;
100                            }
101                        });
102
103                return Ok(final_status);
104            }
105            Self::Series(group) => {
106                let mut rest_iter = group.rest.iter();
107                if let Some(last_command) = rest_iter.next_back() {
108                    let mut exit_status = run_script(&group.first, &args, parser).await.unwrap();
109
110                    for command in rest_iter {
111                        exit_status = merge_status(
112                            exit_status,
113                            run_script(&command, &args, parser).await.unwrap(),
114                        );
115                    }
116
117                    exit_status = merge_status(
118                        exit_status,
119                        run_script(last_command, &args, parser).await.unwrap(),
120                    );
121
122                    Ok(exit_status)
123                } else {
124                    Ok(run_script(&group.first, &args, parser).await.unwrap())
125                }
126            }
127        }
128    }
129}
130
131#[derive(Debug)]
132pub struct Alias {
133    pub task: String,
134    pub args: VarArgs,
135}
136
137/**
138 * TODO: Choose a better name
139 */
140#[derive(Debug)]
141pub enum Script<CommandGeneric: Command> {
142    Command(CommandGeneric),
143    Group(Box<CommandGroup<CommandGeneric>>),
144    Alias(Alias),
145}
146
147impl Alias {
148    #[async_recursion(?Send)]
149    pub async fn run<CommandGeneric : Command>(
150        &self,
151        parser: &impl ScriptParser<CommandGeneric>,
152        args: VarArgs,
153    ) -> Result<ExitStatus, ()> {
154        let final_args = (|| {
155            let has_params = has_parameters(&self.args);
156
157            if has_params {
158                apply_args(&self.args, &args)
159            } else {
160                let joined_args: VecDeque<Arc<String>> = self
161                    .args
162                    .iter()
163                    .into_iter()
164                    .map(|arg| arg.clone())
165                    .chain(args.into_iter())
166                    .collect();
167
168                return joined_args;
169            }
170        })();
171
172        parser
173            .parse(self.task.as_str())
174            .unwrap()
175            .run(parser, final_args)
176            .await
177    }
178}
179
180impl<CommandGeneric: Command> Script<CommandGeneric> {
181    #[async_recursion(?Send)]
182    pub async fn run(
183        &self,
184        parser: &impl ScriptParser<CommandGeneric>,
185        args: VarArgs,
186    ) -> Result<ExitStatus, ()> {
187        match self {
188            Script::Command(cmd) => cmd.run(args).await,
189            Script::Group(group) => group.run(parser, args).await,
190            Script::Alias(alias) => alias.run(parser, args).await,
191        }
192    }
193}
194
195pub trait ScriptParser<CommandGeneric: Command> {
196    fn parse(&self, task: &str) -> Result<Rc<Script<CommandGeneric>>, ()>;
197}