use semver::Version;
use serde::{Deserialize, Serialize, de::Deserializer};
use std::collections::HashMap;
#[cfg(kani)]
use std::{collections::hash_map::DefaultHasher, hash::BuildHasherDefault};
#[cfg(not(kani))]
pub type Vars = HashMap<String, serde_json::Value>;
#[cfg(kani)]
pub type Vars = HashMap<String, serde_json::Value, BuildHasherDefault<DefaultHasher>>;
fn deserialize_actions<'de, D>(deserializer: D) -> Result<Vec<Target>, D::Error>
where
D: Deserializer<'de>,
{
let mut actions = Vec::<Target>::deserialize(deserializer)?;
for action in &mut actions {
action.phony = true;
}
Ok(actions)
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct MacroDefinition {
pub signature: String,
pub body: String,
}
#[derive(Debug, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct NetsukeManifest {
pub netsuke_version: Version,
#[serde(default)]
pub vars: Vars,
#[serde(default)]
pub macros: Vec<MacroDefinition>,
#[serde(default)]
pub rules: Vec<Rule>,
#[serde(default, deserialize_with = "deserialize_actions")]
pub actions: Vec<Target>,
pub targets: Vec<Target>,
#[serde(default)]
pub defaults: Vec<String>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct Rule {
pub name: String,
#[serde(flatten)]
pub recipe: Recipe,
pub description: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize)]
pub enum Recipe {
Command {
command: String,
},
Script {
script: String,
},
Rule {
rule: StringOrList,
},
}
#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields)]
struct RawRecipe {
command: Option<String>,
script: Option<String>,
rule: Option<StringOrList>,
}
impl<'de> Deserialize<'de> for Recipe {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let raw = RawRecipe::deserialize(deserializer)?;
let RawRecipe {
command: command_field,
script: script_field,
rule: rule_field,
} = raw;
match (command_field, script_field, rule_field) {
(Some(command), None, None) => Ok(Self::Command { command }),
(None, Some(script), None) => Ok(Self::Script { script }),
(None, None, Some(rule)) => Ok(Self::Rule { rule }),
(None, None, None) => Err(serde::de::Error::custom(
"missing one of command, script, or rule",
)),
(command_opt, script_opt, rule_opt) => {
let present: Vec<&str> = [
("command", command_opt.is_some()),
("script", script_opt.is_some()),
("rule", rule_opt.is_some()),
]
.into_iter()
.filter_map(|(name, is_present)| is_present.then_some(name))
.collect();
Err(serde::de::Error::custom(format!(
"fields {} are mutually exclusive",
present.join(", ")
)))
}
}
}
}
#[derive(Debug, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct Target {
pub name: StringOrList,
#[serde(flatten)]
pub recipe: Recipe,
#[serde(default)]
pub sources: StringOrList,
#[serde(default)]
pub deps: StringOrList,
#[serde(default)]
pub order_only_deps: StringOrList,
#[serde(default)]
pub vars: Vars,
#[serde(default)]
pub phony: bool,
#[serde(default)]
pub always: bool,
}
#[derive(Debug, Deserialize, Serialize, Default, Clone, PartialEq)]
#[serde(untagged)]
pub enum StringOrList {
#[default]
Empty,
String(String),
List(Vec<String>),
}