1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
use std::collections::{HashMap, HashSet};

pub mod parsing;
pub mod runtime;

#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Module {
    pub namespace: String,
    pub pragmas: Pragma,
    pub links: HashSet<String>,
    pub tasks: HashMap<String, Task>,
    pub cmd_defs: HashMap<Command, CommandDefinition>,
}

impl Module {
    pub fn namespace(&mut self, namespace: impl Into<String>) -> &mut Self {
        self.namespace = namespace.into();
        self
    }

    pub fn pragma(&mut self, pragma: Pragma) -> &mut Self {
        self.pragmas = pragma;
        self
    }

    pub fn link(&mut self, link: impl Into<String>) -> &mut Self {
        self.links.insert(link.into());
        self
    }

    pub fn task(&mut self, task_name: impl Into<String>, task: Task) -> &mut Self {
        self.tasks.insert(task_name.into(), task);
        self
    }

    pub fn cmd_def(&mut self, name: Command, definition: CommandDefinition) -> &mut Self {
        if let Command::Internal(_) = name {
            panic!("Internal commands cannot be CommandDefinitions");
        }

        self.cmd_defs.insert(name, definition);
        self
    }

    pub fn link_module(&mut self, ext_module: Module) -> &mut Self {
        assert!(
            self.links.contains(&ext_module.namespace),
            "Attempted to link an already linked or non specified module.
Likely a developer end issue but double-check your build scripts and dependencies just in case :)"
        );

        self.links.remove(&ext_module.namespace);

        let mut cmd_defs = HashMap::new();
        for (k, v) in ext_module.cmd_defs {
            let Command::Local(name) = k else { unreachable!() };

            cmd_defs.insert(Command::External(ext_module.namespace.clone(), name), v);
        }

        self.cmd_defs.extend(cmd_defs);

        self
    }
}

#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Pragma {
    pub test: Option<String>,
    pub build: Option<String>,
}

impl Pragma {
    pub fn test(&mut self, job_name: impl Into<String>) -> &mut Self {
        self.test = Some(job_name.into());
        self
    }

    pub fn build(&mut self, job_name: impl Into<String>) -> &mut Self {
        self.build = Some(job_name.into());
        self
    }
}

#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Task {
    pub dependencies: HashSet<String>,
    pub commands: Vec<Command>,
}

impl Task {
    pub fn dependency(&mut self, dep_name: impl Into<String>) -> &mut Self {
        self.dependencies.insert(dep_name.into());
        self
    }

    pub fn command(&mut self, command: Command) -> &mut Self {
        self.commands.push(command);
        self
    }
}

#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct CommandDefinition {
    pub commands: Vec<Command>,
}

impl CommandDefinition {
    pub fn command(&mut self, command: Command) -> &mut Self {
        self.commands.push(command);
        self
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Command {
    Internal(InternalCommand),
    Local(String),
    External(String, String),
}

// Execution implemented as a part of the runtime
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum InternalCommand {
    Exec(String),
    SetEnvironmentVar(String, String),
    PrintString(String),
    PrintFile(String),
    MakeDirectory(String),
    MakeFile(String),
    RemoveDirectory(String),
    RemoveFile(String),
    CopyFile(String, String),
    MoveFile(String, String),
}