From 5e88ffdbc952bcadfe89a380a487bf56ba576190 Mon Sep 17 00:00:00 2001 From: kjuulh Date: Sat, 24 Aug 2024 15:40:44 +0200 Subject: [PATCH] feat: add cli Signed-off-by: kjuulh --- crates/cuddle/src/cuddle_state.rs | 68 ++++++++++ crates/cuddle/src/main.rs | 127 ++++++++++--------- crates/cuddle/src/state.rs | 85 +------------ crates/cuddle/src/state/validated_project.rs | 76 +++++++++++ 4 files changed, 214 insertions(+), 142 deletions(-) create mode 100644 crates/cuddle/src/cuddle_state.rs create mode 100644 crates/cuddle/src/state/validated_project.rs diff --git a/crates/cuddle/src/cuddle_state.rs b/crates/cuddle/src/cuddle_state.rs new file mode 100644 index 0000000..10b8eeb --- /dev/null +++ b/crates/cuddle/src/cuddle_state.rs @@ -0,0 +1,68 @@ +use crate::plan::{ClonedPlan, Plan}; +use crate::project::ProjectPlan; +use crate::state::{self, ValidatedState}; + +pub struct Start {} +pub struct PrepareProject { + project: Option, +} + +pub struct PreparePlan { + project: Option, + plan: Option, +} + +pub struct Cuddle { + pub state: S, +} + +// Cuddle maintains the context for cuddle to use +// Stage 1 figure out which state to display +// Stage 2 prepare plan +// Stage 3 validate settings, build actions, prepare + +impl Cuddle { + pub fn default() -> Self { + Self { state: Start {} } + } + + pub async fn prepare_project(&self) -> anyhow::Result> { + let project = ProjectPlan::from_current_path().await?; + + Ok(Cuddle { + state: PrepareProject { project }, + }) + } +} + +impl Cuddle { + pub async fn prepare_plan(&self) -> anyhow::Result> { + let plan = if let Some(project) = &self.state.project { + Plan::new().clone_from_project(project).await? + } else { + None + }; + + Ok(Cuddle { + state: PreparePlan { + project: self.state.project.clone(), + plan, + }, + }) + } +} + +impl Cuddle { + pub async fn build_state(&self) -> anyhow::Result> { + let state = if let Some(project) = &self.state.project { + let state = state::State::new(); + let raw_state = state.build_state(project, &self.state.plan).await?; + + state.validate_state(&raw_state).await? + } else { + ValidatedState::default() + }; + + Ok(Cuddle { state }) + } +} diff --git a/crates/cuddle/src/main.rs b/crates/cuddle/src/main.rs index 12a5764..d6c2fdf 100644 --- a/crates/cuddle/src/main.rs +++ b/crates/cuddle/src/main.rs @@ -1,7 +1,7 @@ -use plan::{ClonedPlan, Plan}; -use project::ProjectPlan; -use state::ValidatedState; +use cuddle_state::Cuddle; +use state::{validated_project::Project, ValidatedState}; +mod cuddle_state; mod plan; mod project; mod schema_validator; @@ -12,7 +12,7 @@ async fn main() -> anyhow::Result<()> { dotenv::dotenv().ok(); tracing_subscriber::fmt::init(); - let _cuddle = Cuddle::default() + let cuddle = Cuddle::default() .prepare_project() .await? .prepare_plan() @@ -20,71 +20,76 @@ async fn main() -> anyhow::Result<()> { .build_state() .await?; + Cli::new(cuddle).setup().await?.execute().await?; + Ok(()) } -struct Start {} -struct PrepareProject { - project: Option, +pub struct Cli { + cli: clap::Command, + cuddle: Cuddle, } -struct PreparePlan { - project: Option, - plan: Option, -} +impl Cli { + pub fn new(cuddle: Cuddle) -> Self { + let cli = clap::Command::new("cuddle").subcommand_required(true); -struct Cuddle { - state: S, -} - -// Cuddle maintains the context for cuddle to use -// Stage 1 figure out which state to display -// Stage 2 prepare plan -// Stage 3 validate settings, build actions, prepare -impl Cuddle {} - -impl Cuddle { - pub fn default() -> Self { - Self { state: Start {} } + Self { cli, cuddle } } - pub async fn prepare_project(&self) -> anyhow::Result> { - let project = ProjectPlan::from_current_path().await?; + pub async fn setup(mut self) -> anyhow::Result { + let s = self + .add_default() + .await? + .add_project_commands() + .await? + .add_plan_commands() + .await?; - Ok(Cuddle { - state: PrepareProject { project }, - }) - } -} - -impl Cuddle { - pub async fn prepare_plan(&self) -> anyhow::Result> { - let plan = if let Some(project) = &self.state.project { - Plan::new().clone_from_project(project).await? - } else { - None - }; - - Ok(Cuddle { - state: PreparePlan { - project: self.state.project.clone(), - plan, - }, - }) - } -} - -impl Cuddle { - pub async fn build_state(&self) -> anyhow::Result> { - let state = if let Some(project) = &self.state.project { - let state = state::State::new(); - let raw_state = state.build_state(project, &self.state.plan).await?; - - state.validate_state(&raw_state).await? - } else { - ValidatedState::default() - }; - - Ok(Cuddle { state }) + // TODO: Add global + // TODO: Add components + + Ok(s) + } + + pub async fn execute(mut self) -> anyhow::Result<()> { + match self + .cli + .get_matches_from(std::env::args()) + .subcommand() + .ok_or(anyhow::anyhow!("failed to find subcommand"))? + { + ("do", args) => { + tracing::debug!("executing do"); + } + _ => {} + } + + Ok(()) + } + + async fn add_default(mut self) -> anyhow::Result { + self.cli = self + .cli + .subcommand(clap::Command::new("do").alias("x")) + .subcommand(clap::Command::new("get")); + + Ok(self) + } + + async fn add_project_commands(mut self) -> anyhow::Result { + if let Some(project) = self.cuddle.state.project.as_ref() { + // Add project level commands + } + + Ok(self) + } + + async fn add_plan_commands(mut self) -> anyhow::Result { + if let Some(plan) = self.cuddle.state.plan.as_ref() { + // Add plan level commands + } + + Ok(self) } } diff --git a/crates/cuddle/src/state.rs b/crates/cuddle/src/state.rs index 0f0a846..4e7f8f5 100644 --- a/crates/cuddle/src/state.rs +++ b/crates/cuddle/src/state.rs @@ -6,6 +6,8 @@ use crate::{ schema_validator::SchemaValidator, }; +pub mod validated_project; + pub struct State {} impl State { pub fn new() -> Self { @@ -51,87 +53,8 @@ pub struct RawState { #[derive(Default)] pub struct ValidatedState { - project: Option, - plan: Option, -} - -mod validated_project { - use std::{ - collections::BTreeMap, - path::{Path, PathBuf}, - }; - - use anyhow::anyhow; - use toml::Table; - - use crate::project::CUDDLE_PROJECT_FILE; - - pub struct Project { - value: Value, - pub root: PathBuf, - } - - impl Project { - pub fn new(value: Value, root: &Path) -> Self { - Self { - value, - root: root.to_path_buf(), - } - } - - pub fn from_file(content: &str, root: &Path) -> anyhow::Result { - let table: Table = toml::from_str(content)?; - let config = Config::default(); - - let project = table - .get("project") - .ok_or(anyhow!("cuddle.toml doesn't provide a [project] table"))?; - - let value: Value = project.into(); - - Ok(Self::new(value, root)) - } - - pub async fn from_path(path: &Path) -> anyhow::Result { - let cuddle_file = path.join(CUDDLE_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(Default)] - struct Config { - project: BTreeMap, - } - - pub enum Value { - String(String), - Bool(bool), - Array(Vec), - Map(BTreeMap), - } - - impl From<&toml::Value> for Value { - fn from(value: &toml::Value) -> Self { - match value { - toml::Value::String(s) => Self::String(s.clone()), - toml::Value::Integer(i) => Self::String(i.to_string()), - toml::Value::Float(f) => Self::String(f.to_string()), - toml::Value::Boolean(b) => Self::Bool(*b), - toml::Value::Datetime(dt) => Self::String(dt.to_string()), - toml::Value::Array(array) => Self::Array(array.iter().map(|i| i.into()).collect()), - toml::Value::Table(tbl) => { - Self::Map(tbl.iter().map(|(k, v)| (k.clone(), v.into())).collect()) - } - } - } - } + pub project: Option, + pub plan: Option, } pub struct Plan {} diff --git a/crates/cuddle/src/state/validated_project.rs b/crates/cuddle/src/state/validated_project.rs new file mode 100644 index 0000000..a05b6e2 --- /dev/null +++ b/crates/cuddle/src/state/validated_project.rs @@ -0,0 +1,76 @@ +use std::{ + collections::BTreeMap, + path::{Path, PathBuf}, +}; + +use anyhow::anyhow; +use toml::Table; + +use crate::project::CUDDLE_PROJECT_FILE; + +pub struct Project { + value: Value, + pub root: PathBuf, +} + +impl Project { + pub fn new(value: Value, root: &Path) -> Self { + Self { + value, + root: root.to_path_buf(), + } + } + + pub fn from_file(content: &str, root: &Path) -> anyhow::Result { + let table: Table = toml::from_str(content)?; + let config = Config::default(); + + let project = table + .get("project") + .ok_or(anyhow!("cuddle.toml doesn't provide a [project] table"))?; + + let value: Value = project.into(); + + Ok(Self::new(value, root)) + } + + pub async fn from_path(path: &Path) -> anyhow::Result { + let cuddle_file = path.join(CUDDLE_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(Default)] +struct Config { + project: BTreeMap, +} + +pub enum Value { + String(String), + Bool(bool), + Array(Vec), + Map(BTreeMap), +} + +impl From<&toml::Value> for Value { + fn from(value: &toml::Value) -> Self { + match value { + toml::Value::String(s) => Self::String(s.clone()), + toml::Value::Integer(i) => Self::String(i.to_string()), + toml::Value::Float(f) => Self::String(f.to_string()), + toml::Value::Boolean(b) => Self::Bool(*b), + toml::Value::Datetime(dt) => Self::String(dt.to_string()), + toml::Value::Array(array) => Self::Array(array.iter().map(|i| i.into()).collect()), + toml::Value::Table(tbl) => { + Self::Map(tbl.iter().map(|(k, v)| (k.clone(), v.into())).collect()) + } + } + } +}