use std::{ env::current_dir, path::{Path, PathBuf}, }; use serde::Deserialize; pub const CUDDLE_PROJECT_FILE: &str = "cuddle.toml"; pub struct RawProject { pub config: RawConfig, pub root: PathBuf, } impl RawProject { pub fn new(config: RawConfig, root: &Path) -> Self { Self { config, root: root.to_path_buf(), } } pub fn from_file(content: &str, root: &Path) -> anyhow::Result { let config: RawConfig = toml::from_str(content)?; Ok(Self::new(config, root)) } pub async fn from_path(path: &Path) -> anyhow::Result { let cuddle_file = path.join(CUDDLE_PROJECT_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_project_file = tokio::fs::read_to_string(cuddle_file).await?; Self::from_file(&cuddle_project_file, path) } } #[derive(Clone)] pub struct ProjectPlan { config: ProjectPlanConfig, pub root: PathBuf, } impl ProjectPlan { pub fn new(config: ProjectPlanConfig, root: PathBuf) -> Self { Self { config, root } } pub fn from_file(content: &str, root: PathBuf) -> anyhow::Result { let config: ProjectPlanConfig = toml::from_str(content)?; Ok(Self::new(config, root)) } pub async fn from_current_path() -> anyhow::Result> { let cur_dir = current_dir()?; let cuddle_file = cur_dir.join(CUDDLE_PROJECT_FILE); tracing::trace!( path = cuddle_file.display().to_string(), "searching for cuddle.toml project file" ); if !cuddle_file.exists() { tracing::debug!("no cuddle.toml project file found"); // We may want to recursively search for the file (towards root) return Ok(None); } let cuddle_project_file = tokio::fs::read_to_string(cuddle_file).await?; Ok(Some(Self::from_file(&cuddle_project_file, cur_dir)?)) } pub fn has_plan(&self) -> bool { self.config.plan.is_some() } pub fn get_plan(&self) -> Plan { match &self.config.plan { Some(PlanConfig::Bare(git)) | Some(PlanConfig::Git { git }) => Plan::Git(git.clone()), Some(PlanConfig::Folder { path }) => Plan::Folder(path.clone()), None => Plan::None, } } } pub enum Plan { None, Git(String), Folder(PathBuf), } #[derive(Debug, Clone, Deserialize, PartialEq)] pub struct RawConfig { project: ProjectConfig, } #[derive(Debug, Clone, Deserialize, PartialEq)] pub struct ProjectConfig { name: String, } #[derive(Debug, Clone, Deserialize, PartialEq)] pub struct ProjectPlanConfig { plan: Option, } #[derive(Debug, Clone, Deserialize, PartialEq)] #[serde(untagged)] pub enum PlanConfig { Bare(String), Git { git: String }, Folder { path: PathBuf }, } #[cfg(test)] mod tests { use super::*; #[test] fn test_can_parse_simple_file() -> anyhow::Result<()> { let project = ProjectPlan::from_file( r##" [plan] git = "https://github.com/kjuulh/some-cuddle-project" "##, PathBuf::new(), )?; assert_eq!( ProjectPlanConfig { plan: Some(PlanConfig::Git { git: "https://github.com/kjuulh/some-cuddle-project".into() }) }, project.config ); Ok(()) } #[test] fn test_can_parse_simple_file_bare() -> anyhow::Result<()> { let project = ProjectPlan::from_file( r##" plan = "https://github.com/kjuulh/some-cuddle-project" "##, PathBuf::new(), )?; assert_eq!( ProjectPlanConfig { plan: Some(PlanConfig::Bare( "https://github.com/kjuulh/some-cuddle-project".into() )) }, project.config ); Ok(()) } #[test] fn test_can_parse_simple_file_none() -> anyhow::Result<()> { let project = ProjectPlan::from_file(r##""##, PathBuf::new())?; assert_eq!(ProjectPlanConfig { plan: None }, project.config); Ok(()) } }