use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, HashMap};
pub mod defaults {
pub const DEFAULT_PROJECT_NAME: &str = "unnamed";
pub const DEFAULT_PROJECT_VERSION: &str = "0.1.0";
pub const DEFAULT_READINESS_PROJECT_NAME: &str = "Current Project";
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Make {
pub project: Project,
#[serde(default)]
pub workspace: Option<BTreeMap<String, Workspace>>,
#[serde(default)]
pub lifecycle: BTreeMap<String, Phase>,
#[serde(default)]
pub hooks: Option<Hooks>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Project {
pub name: String,
#[serde(rename = "type")]
pub project_type: Option<String>,
#[serde(default)]
pub version: Option<String>,
#[serde(default)]
pub description: Option<String>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Workspace {
pub path: String,
#[serde(default)]
pub framework: Option<String>,
#[serde(default)]
pub runtime: Option<String>,
#[serde(default)]
pub package_manager: Option<String>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Phase {
#[serde(default)]
pub description: Option<String>,
#[serde(default)]
pub command: Option<String>,
#[serde(default)]
pub commands: Option<Vec<String>>,
#[serde(default)]
pub watch: Option<bool>,
#[serde(default)]
pub port: Option<u16>,
#[serde(default)]
pub outputs: Option<Vec<String>>,
#[serde(default)]
pub cache: Option<bool>,
#[serde(default)]
pub workspaces: Option<Vec<String>>,
#[serde(default)]
pub parallel: Option<bool>,
}
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
pub struct Hooks {
#[serde(default)]
pub before_all: Option<Vec<String>>,
#[serde(default)]
pub after_all: Option<Vec<String>>,
#[serde(default)]
pub before_init: Option<Vec<String>>,
#[serde(default)]
pub after_init: Option<Vec<String>>,
#[serde(default)]
pub before_setup: Option<Vec<String>>,
#[serde(default)]
pub after_setup: Option<Vec<String>>,
#[serde(default)]
pub before_build: Option<Vec<String>>,
#[serde(default)]
pub after_build: Option<Vec<String>>,
#[serde(default)]
pub before_test: Option<Vec<String>>,
#[serde(default)]
pub after_test: Option<Vec<String>>,
#[serde(default)]
pub before_deploy: Option<Vec<String>>,
#[serde(default)]
pub after_deploy: Option<Vec<String>>,
#[serde(flatten)]
pub phase_hooks: HashMap<String, Vec<String>>,
}
impl Phase {
pub fn commands(&self) -> Vec<String> {
if let Some(cmd) = &self.command {
vec![cmd.clone()]
} else if let Some(cmds) = &self.commands {
cmds.clone()
} else {
vec![]
}
}
}
pub struct PhaseBuilder {
name: String,
description: Option<String>,
commands: Vec<String>,
watch: Option<bool>,
port: Option<u16>,
outputs: Option<Vec<String>>,
cache: Option<bool>,
workspaces: Option<Vec<String>>,
parallel: Option<bool>,
}
impl PhaseBuilder {
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
description: None,
commands: Vec::new(),
watch: None,
port: None,
outputs: None,
cache: None,
workspaces: None,
parallel: None,
}
}
pub fn description(mut self, description: impl Into<String>) -> Self {
self.description = Some(description.into());
self
}
pub fn command(mut self, cmd: impl Into<String>) -> Self {
self.commands = vec![cmd.into()];
self
}
pub fn commands(mut self, cmds: Vec<String>) -> Self {
self.commands = cmds;
self
}
pub fn add_command(mut self, cmd: impl Into<String>) -> Self {
self.commands.push(cmd.into());
self
}
pub fn watch(mut self, watch: bool) -> Self {
self.watch = Some(watch);
self
}
pub fn port(mut self, port: u16) -> Self {
self.port = Some(port);
self
}
pub fn outputs(mut self, outputs: Vec<String>) -> Self {
self.outputs = Some(outputs);
self
}
pub fn cache(mut self, cache: bool) -> Self {
self.cache = Some(cache);
self
}
pub fn workspaces(mut self, workspaces: Vec<String>) -> Self {
self.workspaces = Some(workspaces);
self
}
pub fn parallel(mut self, parallel: bool) -> Self {
self.parallel = Some(parallel);
self
}
pub fn build(self) -> Result<ValidatedPhase, super::error::LifecycleError> {
if self.commands.is_empty() {
return Err(super::error::LifecycleError::NoCommands {
phase: self.name.clone(),
});
}
let (command, commands) = if self.commands.len() == 1 {
(Some(self.commands[0].clone()), None)
} else {
(None, Some(self.commands))
};
Ok(ValidatedPhase {
phase: Phase {
description: self.description,
command,
commands,
watch: self.watch,
port: self.port,
outputs: self.outputs,
cache: self.cache,
workspaces: self.workspaces,
parallel: self.parallel,
},
})
}
}
#[derive(Debug, Clone)]
pub struct ValidatedPhase {
phase: Phase,
}
impl ValidatedPhase {
pub fn phase(&self) -> &Phase {
&self.phase
}
pub fn commands(&self) -> Vec<String> {
self.phase.commands()
}
pub fn name(&self) -> Option<&str> {
None }
}
impl AsRef<Phase> for ValidatedPhase {
fn as_ref(&self) -> &Phase {
&self.phase
}
}
impl Make {
pub fn phase_names(&self) -> Vec<String> {
self.lifecycle.keys().cloned().collect()
}
pub fn phase_commands(&self, phase_name: &str) -> Vec<String> {
self.lifecycle
.get(phase_name)
.map_or(vec![], |p| p.commands())
}
}