cuddle-v2/crates/cuddle/src/plan.rs
kjuulh 1ba6cf79c0
feat: fix minor bugs
Signed-off-by: kjuulh <contact@kjuulh.io>
2024-08-24 14:55:26 +02:00

228 lines
5.9 KiB
Rust

use std::path::{Path, PathBuf};
use fs_extra::dir::CopyOptions;
use serde::Deserialize;
use crate::project::{self, ProjectPlan};
pub const CUDDLE_PLAN_FOLDER: &str = "plan";
pub const CUDDLE_PROJECT_WORKSPACE: &str = ".cuddle";
pub const CUDDLE_PLAN_FILE: &str = "cuddle.plan.toml";
pub trait PlanPathExt {
fn plan_path(&self) -> PathBuf;
}
impl PlanPathExt for project::ProjectPlan {
fn plan_path(&self) -> PathBuf {
self.root
.join(CUDDLE_PROJECT_WORKSPACE)
.join(CUDDLE_PLAN_FOLDER)
}
}
pub struct RawPlan {
pub config: RawPlanConfig,
pub root: PathBuf,
}
impl RawPlan {
pub fn new(config: RawPlanConfig, root: &Path) -> Self {
Self {
config,
root: root.to_path_buf(),
}
}
pub fn from_file(content: &str, root: &Path) -> anyhow::Result<Self> {
let config: RawPlanConfig = toml::from_str(content)?;
Ok(Self::new(config, root))
}
pub async fn from_path(path: &Path) -> anyhow::Result<Self> {
let cuddle_file = path.join(CUDDLE_PLAN_FILE);
tracing::trace!(
path = cuddle_file.display().to_string(),
"searching for cuddle.toml project file"
);
if !cuddle_file.exists() {
anyhow::bail!("no cuddle.toml project file found");
}
let cuddle_plan_file = tokio::fs::read_to_string(cuddle_file).await?;
Self::from_file(&cuddle_plan_file, path)
}
}
#[derive(Debug, Clone, Deserialize, PartialEq)]
pub struct RawPlanConfig {
pub plan: RawPlanConfigSection,
}
#[derive(Debug, Clone, Deserialize, PartialEq)]
pub struct RawPlanConfigSection {
pub schema: Option<RawPlanSchema>,
}
#[derive(Debug, Clone, Deserialize, PartialEq)]
#[serde(untagged)]
pub enum RawPlanSchema {
Nickel { nickel: PathBuf },
JsonSchema { jsonschema: String },
}
pub struct Plan {}
impl Plan {
pub fn new() -> Self {
Self {}
}
pub async fn clone_from_project(
&self,
project: &ProjectPlan,
) -> anyhow::Result<Option<ClonedPlan>> {
if !project.plan_path().exists() {
if project.has_plan() {
self.prepare_plan(project).await?;
}
match project.get_plan() {
project::Plan::None => Ok(None),
project::Plan::Git(url) => Ok(Some(self.git_plan(project, url).await?)),
project::Plan::Folder(folder) => {
Ok(Some(self.folder_plan(project, &folder).await?))
}
}
} else {
match project.get_plan() {
project::Plan::Folder(folder) => {
self.clean_plan(project).await?;
self.prepare_plan(project).await?;
Ok(Some(self.folder_plan(project, &folder).await?))
}
project::Plan::Git(_git) => Ok(Some(ClonedPlan {})),
project::Plan::None => Ok(None),
}
}
}
async fn prepare_plan(&self, project: &ProjectPlan) -> anyhow::Result<()> {
tracing::trace!("preparing workspace");
tokio::fs::create_dir_all(project.plan_path()).await?;
Ok(())
}
async fn clean_plan(&self, project: &ProjectPlan) -> anyhow::Result<()> {
tracing::trace!("clean plan");
tokio::fs::remove_dir_all(project.plan_path()).await?;
Ok(())
}
async fn git_plan(&self, project: &ProjectPlan, url: String) -> anyhow::Result<ClonedPlan> {
let mut cmd = tokio::process::Command::new("git");
cmd.args(["clone", &url, &project.plan_path().display().to_string()]);
tracing::debug!(url = url, "cloning git plan");
let output = cmd.output().await?;
if !output.status.success() {
anyhow::bail!(
"failed to clone: {}, output: {} {}",
url,
std::str::from_utf8(&output.stdout)?,
std::str::from_utf8(&output.stderr)?,
)
}
Ok(ClonedPlan {})
}
async fn folder_plan(&self, project: &ProjectPlan, path: &Path) -> anyhow::Result<ClonedPlan> {
tracing::trace!(
src = path.display().to_string(),
dest = project.plan_path().display().to_string(),
"copying src into plan dest"
);
let mut items_stream = tokio::fs::read_dir(path).await?;
let mut items = Vec::new();
while let Some(item) = items_stream.next_entry().await? {
items.push(item.path());
}
fs_extra::copy_items(
&items,
project.plan_path(),
&CopyOptions::default()
.overwrite(true)
.depth(0)
.copy_inside(false),
)?;
Ok(ClonedPlan {})
}
}
pub struct ClonedPlan {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_can_parse_schema_plan() -> anyhow::Result<()> {
let plan = RawPlan::from_file(
r##"
[plan]
schema = {nickel = "contract.ncl"}
"##,
&PathBuf::new(),
)?;
assert_eq!(
RawPlanConfig {
plan: RawPlanConfigSection {
schema: Some(RawPlanSchema::Nickel {
nickel: "contract.ncl".into()
}),
}
},
plan.config,
);
Ok(())
}
#[test]
fn test_can_parse_json_schema() -> anyhow::Result<()> {
let plan = RawPlan::from_file(
r##"
[plan]
schema = {jsonschema = "schema.json"}
"##,
&PathBuf::new(),
)?;
assert_eq!(
RawPlanConfig {
plan: RawPlanConfigSection {
schema: Some(RawPlanSchema::JsonSchema {
jsonschema: "schema.json".into()
}),
}
},
plan.config,
);
Ok(())
}
}