use std::path::Path;
use linked_hash_map::LinkedHashMap;
use log::trace;
use crate::actions::{Action, ActionId};
use crate::config::VariableInfo;
use crate::rules::RulesContext;
use crate::{Archetect, ArchetectError, Archetype};
use crate::vendor::tera::{Context, Value};
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct IfAction {
#[serde(flatten)]
condition: Condition,
#[serde(rename = "then", alias = "actions")]
then_actions: Vec<ActionId>,
#[serde(rename = "else", skip_serializing_if = "Option::is_none")]
else_actions: Option<Vec<ActionId>>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub enum Condition {
#[serde(rename = "all-of")]
AllOf(Vec<Condition>),
#[serde(rename = "conditions")]
Conditions(Vec<Condition>),
#[serde(rename = "equals")]
Equals(String, String),
#[serde(rename = "is-empty")]
IsEmpty(String),
#[serde(rename = "path-exists")]
PathExists(String),
#[serde(rename = "is-file")]
IsFile(String),
#[serde(rename = "is-directory")]
IsDirectory(String),
#[serde(rename = "switch-enabled")]
SwitchEnabled(String),
#[serde(rename = "not")]
Not(Box<Condition>),
#[serde(rename = "any-of")]
AnyOf(Vec<Condition>),
#[serde(rename = "is-true")]
IsTrue(String),
}
impl IfAction {
pub fn then_actions(&self) -> &Vec<ActionId> {
self.then_actions.as_ref()
}
pub fn else_actions(&self) -> Option<&Vec<ActionId>> {
self.else_actions.as_ref()
}
}
impl Condition {
pub fn evaluate<D: AsRef<Path>>(
&self,
archetect: &mut Archetect,
archetype: &Archetype,
destination: D,
context: &Context,
) -> Result<bool, ArchetectError> {
match self {
Condition::IsEmpty(input) => {
if let Some(value) = context.get(input) {
return match value {
Value::Null => Ok(true),
Value::String(string) => Ok(string.trim().is_empty()),
Value::Array(array) => Ok(array.is_empty()),
Value::Object(object) => Ok(object.is_empty()),
Value::Bool(_) | Value::Number(_) => Ok(false),
}
}
Ok(true)
}
Condition::PathExists(path) => {
let path = archetect.render_string(path, context)?;
let path = destination.as_ref().join(path);
Ok(path.exists())
}
Condition::IsFile(path) => {
let path = archetect.render_string(path, context)?;
let path = destination.as_ref().join(path);
let exists = path.exists() && path.is_file();
trace!("[File Exists] {}: {}", path.display(), exists);
Ok(exists)
}
Condition::IsDirectory(path) => {
let path = archetect.render_string(path, context)?;
let path = destination.as_ref().join(path);
Ok(path.exists() && path.is_dir())
}
Condition::SwitchEnabled(switch) => Ok(archetect.switches().contains(switch)),
Condition::Not(condition) => {
let value = condition.evaluate(archetect, archetype, destination, context)?;
Ok(!value)
}
Condition::Equals(left, right) => {
let left = archetect.render_string(left, context)?;
let right = archetect.render_string(right, context)?;
return Ok(left.eq(&right));
}
Condition::AnyOf(conditions) => {
for condition in conditions {
let value = condition.evaluate(archetect, archetype, destination.as_ref(), context)?;
if value {
return Ok(true);
}
}
Ok(false)
}
Condition::AllOf(conditions) => {
for condition in conditions {
let value = condition.evaluate(archetect, archetype, destination.as_ref(), context)?;
if !value {
return Ok(false);
}
}
Ok(true)
}
Condition::Conditions(conditions) => {
for condition in conditions {
let value = condition.evaluate(archetect, archetype, destination.as_ref(), context)?;
if !value {
return Ok(false);
}
}
Ok(true)
}
Condition::IsTrue(expression) => {
let result = archetect.render_string(expression, context)?;
return if result.trim().eq("true") {
Ok(true)
} else {
Ok(false)
}
}
}
}
}
impl Action for IfAction {
fn execute<D: AsRef<Path>>(
&self,
archetect: &mut Archetect,
archetype: &Archetype,
destination: D,
rules_context: &mut RulesContext,
answers: &LinkedHashMap<String, VariableInfo>,
context: &mut Context,
) -> Result<(), ArchetectError> {
if self.condition.evaluate(archetect, archetype, destination.as_ref(), context)? {
let action: ActionId = self.then_actions().into();
action.execute(
archetect,
archetype,
destination.as_ref(),
rules_context,
answers,
context,
)?;
} else {
if let Some(actions) = self.else_actions() {
let action: ActionId = actions[..].into();
action.execute(
archetect,
archetype,
destination.as_ref(),
rules_context,
answers,
context,
)?;
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use crate::actions::conditionals::{Condition, IfAction};
use crate::actions::render::{DirectoryOptions, RenderAction};
use crate::actions::ActionId;
#[test]
pub fn test_serialize() -> Result<(), serde_yaml::Error> {
let action = IfAction {
condition: Condition::AllOf(vec![
Condition::IsFile("example.txt".to_owned()),
Condition::IsDirectory("src/main/java".to_owned()),
Condition::PathExists("{{ service }}".to_owned()),
Condition::Equals("{{ one }}".to_owned(), "{{ two }}".to_owned()),
]),
then_actions: vec![ActionId::Render(RenderAction::Directory(DirectoryOptions::new(".")))],
else_actions: None,
};
let yaml = serde_yaml::to_string(&action)?;
println!("{}", yaml);
Ok(())
}
}