thot_core/project/
script.rs

1use crate::result::{Error, Result, ScriptError};
2use crate::types::{ResourceId, ResourcePath};
3use chrono::prelude::*;
4use std::collections::HashMap;
5use std::ffi::OsStr;
6use std::path::Path;
7
8#[cfg(feature = "serde")]
9use serde::{Deserialize, Serialize};
10
11// **************
12// *** Script ***
13// **************
14
15/// Represents a Script belonging to a specific project.
16#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
17#[derive(Debug, Clone)]
18pub struct Script {
19    pub rid: ResourceId,
20    pub path: ResourcePath,
21    pub name: Option<String>,
22    pub description: Option<String>,
23    pub env: ScriptEnv,
24    pub creator: Option<ResourceId>,
25    created: DateTime<Utc>,
26}
27
28impl Script {
29    pub fn new(path: ResourcePath) -> Result<Self> {
30        // setup env
31        let file_name = path.as_path().file_name();
32        if file_name.is_none() {
33            return Err(Error::ScriptError(ScriptError::UnknownLanguage(None)));
34        }
35
36        let file_name = Path::new(file_name.unwrap());
37        let env = ScriptEnv::new(file_name)?;
38
39        // create Script
40        Ok(Script {
41            rid: ResourceId::new(),
42            path,
43            name: None,
44            description: None,
45            creator: None,
46            created: Utc::now(),
47            env,
48        })
49    }
50
51    /// Returns the date-time the script was created.
52    /// This does not refer to the creation date-time of the script file,
53    /// but rather the abstract Script object.
54    pub fn created(&self) -> &DateTime<Utc> {
55        &self.created
56    }
57}
58
59// ***************
60// *** Scripts ***
61// ***************
62
63/// Project scripts.
64#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
65#[derive(Debug, Default)]
66pub struct Scripts {
67    pub scripts: Vec<Script>,
68}
69
70impl Scripts {
71    pub fn new() -> Self {
72        Scripts {
73            scripts: Vec::new(),
74        }
75    }
76
77    /// Gets a [`Script`] by its [`ResourceId`] if it is registered,
78    /// otherwise returns `None`.
79    pub fn get(&self, rid: &ResourceId) -> Option<&Script> {
80        for script in &self.scripts {
81            if &script.rid == rid {
82                return Some(script);
83            }
84        }
85
86        None
87    }
88
89    /// Returns whether a script with the given id is registered.
90    pub fn contains(&self, rid: &ResourceId) -> bool {
91        self.get(rid).is_some()
92    }
93
94    /// Returns whether a script with the given path is registered.
95    pub fn contains_path(&self, path: &ResourcePath) -> bool {
96        self.by_path(path).is_some()
97    }
98
99    /// Gets a script by its path if it is registered.
100    pub fn by_path(&self, path: &ResourcePath) -> Option<&Script> {
101        for script in &self.scripts {
102            if &script.path == path {
103                return Some(&script);
104            }
105        }
106
107        None
108    }
109}
110
111// ******************
112// *** Script Env ***
113// ******************
114
115/// Defines the environemnt the script should run in.
116#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
117#[derive(Debug, Clone)]
118pub struct ScriptEnv {
119    pub language: ScriptLang,
120    pub cmd: String,
121    pub args: Vec<String>,
122    pub env: HashMap<String, String>,
123}
124
125impl ScriptEnv {
126    /// Creates a new script environment for the given script.
127    pub fn new(script: &Path) -> Result<Self> {
128        let path_ext = script.extension();
129        if path_ext.is_none() {
130            return Err(Error::ScriptError(ScriptError::UnknownLanguage(None)));
131        }
132
133        // lang
134        let path_ext = path_ext.unwrap();
135        let language = ScriptLang::from_extension(path_ext);
136        if language.is_none() {
137            return Err(Error::ScriptError(ScriptError::UnknownLanguage(Some(
138                path_ext.to_os_string(),
139            ))));
140        }
141        let language = language.unwrap();
142
143        // cmd
144        let cmd = match &language {
145            ScriptLang::Python => "python",
146            ScriptLang::R => "Rscript",
147        };
148        let cmd = cmd.to_string();
149
150        // args
151        let args = Vec::new();
152
153        // env
154        let env = HashMap::new();
155
156        Ok(ScriptEnv {
157            language,
158            cmd,
159            args,
160            env,
161        })
162    }
163}
164
165// *******************
166// *** Script Lang ***
167// *******************
168
169/// Defines the language of the script.
170#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
171#[derive(Debug, Clone)]
172pub enum ScriptLang {
173    Python,
174    R,
175}
176
177impl ScriptLang {
178    /// Returns the language type from a file extension
179    /// or `None` if none match.
180    pub fn from_extension(ext: &OsStr) -> Option<Self> {
181        let ext = ext.to_str();
182        if ext.is_none() {
183            return None;
184        }
185
186        match ext.unwrap() {
187            "py" => Some(Self::Python),
188            "r" => Some(Self::R),
189            _ => None,
190        }
191    }
192}
193
194#[cfg(test)]
195#[path = "./script_test.rs"]
196mod script_test;