use super::{Cmd, CmdOptions, Runnable};
use std::path::Path;
pub const SCRIPT_FILE_PATH_PLACEHOLDER: &str = "@FILE_PATH";
#[derive(Debug, Clone, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[non_exhaustive]
pub enum ScriptingLanguage {
#[default]
Bash,
Python,
Php,
JavaScript,
Perl,
Lua,
Ruby,
Groovy,
Other(ScriptRunConfig),
}
impl From<ScriptingLanguage> for ScriptRunConfig {
fn from(value: ScriptingLanguage) -> Self {
match value {
ScriptingLanguage::Bash => {
ScriptRunConfig::new("bash", vec![SCRIPT_FILE_PATH_PLACEHOLDER], "sh")
}
ScriptingLanguage::Python => {
ScriptRunConfig::new("python", vec![SCRIPT_FILE_PATH_PLACEHOLDER], "py")
}
ScriptingLanguage::Php => {
ScriptRunConfig::new("php", vec!["-f", SCRIPT_FILE_PATH_PLACEHOLDER], "php")
}
ScriptingLanguage::JavaScript => {
ScriptRunConfig::new("node", vec![SCRIPT_FILE_PATH_PLACEHOLDER], "js")
}
ScriptingLanguage::Perl => {
ScriptRunConfig::new("perl", vec![SCRIPT_FILE_PATH_PLACEHOLDER], "pl")
}
ScriptingLanguage::Lua => {
ScriptRunConfig::new("lua", vec![SCRIPT_FILE_PATH_PLACEHOLDER], "lua")
}
ScriptingLanguage::Ruby => {
ScriptRunConfig::new("ruby", vec![SCRIPT_FILE_PATH_PLACEHOLDER], "rb")
}
ScriptingLanguage::Groovy => {
ScriptRunConfig::new("groovy", vec![SCRIPT_FILE_PATH_PLACEHOLDER], "groovy")
}
ScriptingLanguage::Other(run_config) => run_config,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ScriptRunConfig {
cmd: String,
args: Vec<String>,
file_extension: String,
}
impl ScriptRunConfig {
pub fn new<C, T, I, F>(cmd: C, args: I, file_extension: F) -> Self
where
C: Into<String>,
T: Into<String>,
I: IntoIterator<Item = T>,
F: Into<String>,
{
Self {
cmd: cmd.into(),
args: args.into_iter().map(Into::into).collect(),
file_extension: file_extension.into(),
}
}
pub(crate) fn replace_path_placeholder(&mut self, file_path: &str) {
self.args = self
.args
.iter()
.map(|arg| {
if arg == SCRIPT_FILE_PATH_PLACEHOLDER {
file_path
} else {
arg
}
.to_owned()
})
.collect();
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Script {
#[cfg_attr(feature = "serde", serde(default))]
pub(crate) lang: ScriptingLanguage,
pub(crate) content: String,
#[cfg_attr(feature = "serde", serde(default))]
pub(crate) args: Vec<String>,
#[cfg_attr(feature = "serde", serde(default))]
pub(crate) options: CmdOptions,
}
impl Script {
pub fn new<S>(lang: ScriptingLanguage, content: S) -> Self
where
S: Into<String>,
{
Self {
lang,
content: content.into(),
args: Vec::new(),
options: CmdOptions::default(),
}
}
pub fn with_args<S, T, I>(lang: ScriptingLanguage, content: S, args: I) -> Self
where
S: Into<String>,
T: Into<String>,
I: IntoIterator<Item = T>,
{
Self {
lang,
content: content.into(),
args: args.into_iter().map(Into::into).collect(),
options: CmdOptions::default(),
}
}
pub fn with_options<S>(lang: ScriptingLanguage, content: S, options: CmdOptions) -> Self
where
S: Into<String>,
{
Self {
lang,
content: content.into(),
args: Vec::new(),
options,
}
}
pub fn with_args_and_options<S, T, I>(
lang: ScriptingLanguage,
content: S,
args: I,
options: CmdOptions,
) -> Self
where
S: Into<String>,
T: Into<String>,
I: IntoIterator<Item = T>,
{
Self {
lang,
content: content.into(),
args: args.into_iter().map(Into::into).collect(),
options,
}
}
pub fn set_args<S, I>(&mut self, args: I)
where
S: Into<String>,
I: IntoIterator<Item = S>,
{
self.args = args.into_iter().map(Into::into).collect();
}
pub fn set_options(&mut self, options: CmdOptions) {
self.options = options;
}
pub fn add_arg<S>(&mut self, arg: S)
where
S: Into<String>,
{
self.args.push(arg.into());
}
pub fn language(&self) -> &ScriptingLanguage {
&self.lang
}
pub fn content(&self) -> &str {
&self.content
}
pub fn args(&self) -> &[String] {
&self.args
}
pub fn options(&self) -> &CmdOptions {
&self.options
}
pub fn options_mut(&mut self) -> &mut CmdOptions {
&mut self.options
}
}
impl Runnable for Script {
fn bootstrap_cmd(&self, process_dir: &Path) -> Result<Cmd, String> {
let mut run_config: ScriptRunConfig = self.lang.clone().into();
let file_path = create_script_file(self, &run_config, process_dir)?;
run_config.replace_path_placeholder(&file_path);
run_config.args.extend_from_slice(&self.args);
let cmd = Cmd {
cmd: run_config.cmd,
args: run_config.args,
options: self.options.clone(),
};
Ok(cmd)
}
}
fn create_script_file(
script: &Script,
run_config: &ScriptRunConfig,
script_file_dir: &Path,
) -> Result<String, String> {
let file_path = script_file_dir
.join("script")
.with_extension(&run_config.file_extension);
std::fs::write(&file_path, &script.content).map_err(|err| err.to_string())?;
file_path
.to_str()
.ok_or("Script file path cannot be converted to UTF-8 string".to_owned())
.map(|v| v.to_owned())
}