use std::{
collections::HashMap,
fmt::{Display, Formatter},
};
use std::path::PathBuf;
use heck::SnakeCase;
use lazy_static::lazy_static;
use serde::{Serialize, Deserialize};
use anyhow::{anyhow, Context as AnyhowContext, Result};
use regex::Regex;
use crate::{
common::Env,
};
use crate::common::LogLevel;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Options {
#[serde(default)]
pub shell: ShellType,
pub shell_path: Option<String>,
#[serde(default = "false_default")]
pub dry_run: bool,
#[serde(default = "true_default")]
pub ansi: bool,
#[serde(default)]
pub log_level: LogLevel,
pub log_dir: Option<String>,
#[serde(default = "true_default")]
pub system_env: bool,
pub dotenv: Option<PathBuf>,
#[serde(default = "temp_path_default")]
pub temp_path: PathBuf,
}
impl Default for Options {
fn default() -> Self {
Self {
shell: ShellType::default(),
shell_path: None,
dry_run: false_default(),
ansi: true_default(),
log_level: LogLevel::default(),
log_dir: None,
system_env: true_default(),
dotenv: None,
temp_path: temp_path_default(),
}
}
}
fn temp_path_default() -> PathBuf {
std::env::temp_dir()
}
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum ShellType {
Bash,
Python,
Sh,
Ruby,
Php,
Node,
Cmd,
PowerShell,
Other(String),
}
lazy_static! {
static ref COMMAND_PATTERN: Regex = Regex::new(r"^(?<program>((\\[ ])|[A-z0-9/.])+)( +(?<args>.*))?( +|$)").unwrap();
}
impl ShellType {
pub fn executable(&self, path: Option<&String>) -> Result<String> {
if let Some(path) = path {
let program = COMMAND_PATTERN.captures(path).and_then(|cap| {
cap.name("program").map(|login| login.as_str().to_string())
});
program.with_context(|| {
anyhow!("Invalid command: {}", path)
})
} else {
Ok(match self {
ShellType::Bash => "bash".to_string(),
ShellType::Python => "python".to_string(),
ShellType::Sh => "sh".to_string(),
ShellType::Ruby => "ruby".to_string(),
ShellType::Php => "php".to_string(),
ShellType::Node => "node".to_string(),
ShellType::Cmd => "cmd.exe".to_string(),
ShellType::PowerShell => "powershell.exe".to_string(),
ShellType::Other(shell) => {
return Err(anyhow!("Shell type not supported {}", shell));
}
})
}
}
pub fn args(&self, _path: Option<&String>, program: String) -> Result<Vec<String>> {
Ok( match self {
ShellType::Bash => ["-c".to_string(), program].to_vec(),
ShellType::Python => ["-c".to_string(), program].to_vec(),
ShellType::Sh => ["-c".to_string(), program].to_vec(),
ShellType::Ruby => ["-e".to_string(), program].to_vec(),
ShellType::Php => ["-r".to_string(), program].to_vec(),
ShellType::Node => ["-e".to_string(), program].to_vec(),
ShellType::Cmd => {
return Err(anyhow!("Sorry! Windows command shell is not supported yet"));
},
ShellType::PowerShell => {
return Err(anyhow!("Sorry! Windows command shell is not supported yet"));
},
ShellType::Other(shell) => {
return Err(anyhow!("Shell type not supported {}", shell));
}
})
}
}
impl Default for ShellType {
fn default() -> Self {
if cfg!(target_os = "windows") {
ShellType::Cmd
} else {
ShellType::Sh
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Shell {
pub shell: Option<ShellType>,
pub shell_path: Option<String>,
pub run: String,
}
impl Display for Shell {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.run)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum TaskHandler {
Shell(Shell),
}
impl Display for TaskHandler {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
TaskHandler::Shell(shell) => write!(f, "{}", shell),
}
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Task {
pub id: Option<String>,
pub name: Option<String>,
#[serde(flatten)]
pub handler: TaskHandler,
pub env: Option<Env>,
pub cwd: Option<String>,
pub hooks: Option<Hooks>,
pub policy: Option<ExecutionPolicy>,
}
impl Task {
pub fn get_name(&self) -> String {
self.name.clone().unwrap_or_else(|| self.handler.to_string())
}
}
pub type Tasks = Vec<Task>;
pub type Hooks = HashMap<Hook, Tasks>;
fn true_default() -> bool {
true
}
fn false_default() -> bool {
false
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LogOptions {
#[serde(default = "true_default")]
pub stdout: bool,
#[serde(default = "true_default")]
pub stderr: bool,
#[serde(default = "true_default")]
pub hooks: bool,
#[serde(default = "true_default")]
pub internal: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
pub struct FileHandler {
pub output: Option<String>,
#[serde(default = "false_default")]
pub split: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
#[serde(rename_all = "snake_case", tag = "type")]
pub enum LogHandlerType {
File(FileHandler),
Console,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LogHandler {
pub name: Option<String>,
#[serde(flatten)]
pub handler: LogHandlerType,
#[serde(flatten)]
pub options: LogOptions,
}
impl LogHandler {
pub fn default_console() -> Self {
Self {
name: None,
handler: LogHandlerType::Console,
options: LogOptions {
stdout: true,
stderr: true,
hooks: true,
internal: true,
},
}
}
}
pub type LogHandlers = Vec<LogHandler>;
#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
#[serde(rename_all = "snake_case")]
pub enum Hook {
BeforeJob,
AfterJob,
BeforeTask,
AfterTask,
OnFailure,
OnSuccess,
}
impl Display for Hook {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", format!("{:?}", self).to_snake_case())
}
}
#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
#[serde(rename_all = "snake_case")]
pub enum ExecutionPolicy {
NoPriorFailed,
PriorSuccess,
Always,
}
impl Default for ExecutionPolicy {
fn default() -> Self {
ExecutionPolicy::NoPriorFailed
}
}
impl Display for ExecutionPolicy {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", format!("{:?}", self).to_snake_case())
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Job {
pub id: Option<String>,
pub name: String,
pub env: Option<Env>,
pub cwd: Option<String>,
pub tasks: Tasks,
pub hooks: Option<Hooks>,
pub logging: Option<LogHandlers>,
#[serde(default)]
pub policy: ExecutionPolicy,
pub options: Option<Options>,
}