use {
crate::error::Error,
anyhow::Result,
itertools::Itertools,
std::collections::HashMap,
};
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
#[serde(rename_all = "snake_case")] pub(crate) struct Workflow {
pub version: String,
pub env: Option<Env>,
#[serde(with = "serde_yaml::with::singleton_map_recursive")]
#[schemars(with = "HashMap<String, Node>")]
pub nodes: HashMap<String, Node>,
pub watch: Option<HashMap<String, WatchExec>>,
}
impl Workflow {
pub fn load(data: &str) -> Result<Self> {
#[derive(Debug, serde::Deserialize)]
struct Versioned {
version: String,
}
let v = serde_yaml::from_str::<Versioned>(data)?;
let major_minor = env!("CARGO_PKG_VERSION").split(".").take(2).join(".");
if &major_minor != "0.0" && &v.version != &major_minor {
Err(Error::VersionCompatibility(format!(
"workflow version {} is incompatible with this CLI version {}",
v.version,
env!("CARGO_PKG_VERSION")
)))?
}
let wf: crate::workflow::Workflow = serde_yaml::from_str(&data)?;
let nodes_allow_regex = fancy_regex::Regex::new(r"^[a-zA-Z0-9_-]+$")?;
for node in wf.nodes.keys() {
if !nodes_allow_regex.is_match(node)? {
Err(Error::InvalidNodeName(node.clone()))?
}
}
Ok(wf)
}
}
#[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
#[serde(rename_all = "snake_case", deny_unknown_fields)]
pub struct Env {
pub capture: Option<String>,
pub vars: Option<HashMap<String, String>>,
}
impl Env {
pub(crate) fn compile(&self) -> Result<HashMap<String, String>> {
let mut map = self.vars.clone().or(Some(HashMap::<_, _>::new())).unwrap();
match &self.capture {
| Some(v) => {
let regex = fancy_regex::Regex::new(v)?;
let envs = std::env::vars().collect_vec();
for e in envs {
if regex.is_match(&e.0)? {
map.insert(e.0, e.1);
}
}
},
| None => {},
}
Ok(map)
}
}
#[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
#[serde(rename_all = "snake_case", deny_unknown_fields)]
pub(crate) struct Shell {
pub program: String,
pub args: Vec<String>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
#[serde(rename_all = "snake_case", deny_unknown_fields)]
pub(crate) struct Node {
pub description: Option<String>,
pub pre: Option<Vec<String>>,
pub matrix: Option<Matrix>,
pub tasks: Vec<Task>,
pub env: Option<HashMap<String, String>>,
pub shell: Option<Shell>,
pub workdir: Option<String>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
#[serde(rename_all = "snake_case", deny_unknown_fields)]
pub(crate) enum Matrix {
Dense {
drop: Option<String>,
dimensions: Vec<Vec<MatrixCell>>,
},
Sparse {
dimensions: Vec<Vec<MatrixCell>>,
keep: Option<String>,
},
}
impl Matrix {
pub(crate) fn compile(&self) -> Result<Vec<crate::plan::Invocation>> {
let (dimensions, regex) = match self {
| Self::Dense { drop, dimensions } => (dimensions, drop),
| Self::Sparse { keep, dimensions } => (dimensions, keep),
};
let regex = match regex {
| Some(v) => Some(fancy_regex::Regex::new(&v)?),
| None => None,
};
let dims_widx = dimensions.iter().map(|d_x| {
let mut y = 0usize;
d_x.iter()
.map(|d_y| {
y += 1;
(y - 1, d_y)
})
.collect_vec()
});
let cp = dims_widx.multi_cartesian_product();
let mut v = Vec::<crate::plan::Invocation>::new();
for next in cp {
let coords = next.iter().map(|v| format!("{}", v.0)).join(",");
match self {
| Self::Dense { .. } => {
if let Some(regex) = ®ex {
if regex.is_match(&format!("{}", coords))? {
continue;
}
} else { };
},
| Self::Sparse { .. } => {
if let Some(regex) = ®ex {
if !regex.is_match(&format!("{}", coords))? {
continue;
}
} else {
continue;
};
},
}
let mut env = HashMap::<String, String>::new();
for m in next {
if let Some(e) = &m.1.env {
env.extend(e.clone());
}
}
v.push(crate::plan::Invocation { env, coords });
}
Ok(v)
}
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
#[serde(rename_all = "snake_case", deny_unknown_fields)]
pub(crate) struct MatrixCell {
pub env: Option<HashMap<String, String>>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
#[serde(rename_all = "snake_case", deny_unknown_fields)]
pub(crate) struct Task {
pub script: String,
pub env: Option<HashMap<String, String>>,
pub shell: Option<Shell>,
pub workdir: Option<String>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
#[serde(rename_all = "snake_case", deny_unknown_fields)]
pub(crate) struct WatchExec {
pub filter: String,
pub queue: bool,
#[serde(with = "serde_yaml::with::singleton_map_recursive")]
#[schemars(with = "WatchExecStep")]
pub exec: WatchExecStep,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
#[serde(rename_all = "snake_case", deny_unknown_fields)]
pub(crate) enum WatchExecStep {
Node {
#[serde(rename = "ref")]
ref_: String,
},
}