diff --git a/src/cli.rs b/src/cli.rs index f244948..60e23fe 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -43,4 +43,10 @@ pub enum GenerateAction { #[clap(short, long)] watch: bool, }, + /// Generate a new leptos component inside the `/components` folder + #[clap(aliases = vec!["c"])] + Component { + /// Name of the component to generate + name: String, + }, } diff --git a/src/generate.rs b/src/generate.rs index dec1023..2918756 100644 --- a/src/generate.rs +++ b/src/generate.rs @@ -1 +1,2 @@ +pub mod component; pub mod db; diff --git a/src/generate/component.rs b/src/generate/component.rs new file mode 100644 index 0000000..e27bab1 --- /dev/null +++ b/src/generate/component.rs @@ -0,0 +1,43 @@ +use anyhow::{Context, Result}; +use convert_case::{Case, Casing}; +use include_dir::{include_dir, Dir}; +use minijinja::{context, Environment}; +use std::path::{Path, PathBuf}; + +pub fn main(name: String) -> Result<()> { + let src_dir = Path::new("src"); + + let components_dir = src_dir.join("components"); + ensures_folder_exists(&components_dir)?; + + const TEMPLATES_DIR: Dir<'_> = include_dir!("$CARGO_MANIFEST_DIR/templates/generate"); + + let template_content = TEMPLATES_DIR + .get_file("component.rs.jinja2") + .context("Cannot get template 'component.rs.jinja2'")? + .contents_utf8() + .context("Cannot get template 'component.rs.jinja2'")? + .to_string(); + + let component_name = name.to_case(Case::Pascal); + let content = Environment::new().render_str(&template_content, context! { component_name })?; + + let file_name = name.to_case(Case::Snake); + let file_name = format!("{}.rs", file_name); + + let component_file = components_dir.join(file_name); + + std::fs::write(component_file, content)?; + + println!("Component {} successfully created", name); + + Ok(()) +} + +fn ensures_folder_exists(dir_path: &PathBuf) -> Result<()> { + if !dir_path.exists() { + fs_extra::dir::create_all(dir_path, false)?; + } + + Ok(()) +} diff --git a/src/main.rs b/src/main.rs index fa6d15d..8aa162e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -19,6 +19,7 @@ async fn main() -> Result<()> { Action::New { name, template } => new::main(name, template), Action::Generate { command } => match command { GenerateAction::Db { watch } => generate::db::main(watch), + GenerateAction::Component { name } => generate::component::main(name), }, }, } diff --git a/templates/generate/component.rs.jinja2 b/templates/generate/component.rs.jinja2 new file mode 100644 index 0000000..eae0ea1 --- /dev/null +++ b/templates/generate/component.rs.jinja2 @@ -0,0 +1,13 @@ +use leptos::*; + +#[component] +pub fn {{ component_name }}(cx: Scope) -> impl IntoView { + let (count, set_count) = create_signal(cx, 0); + let on_click = move |_| set_count.update(|count| *count += 1); + + view! { cx, + + } +} \ No newline at end of file diff --git a/tests/cli/generate.rs b/tests/cli/generate.rs index b918018..997d0da 100644 --- a/tests/cli/generate.rs +++ b/tests/cli/generate.rs @@ -1 +1,2 @@ +mod component; mod db; diff --git a/tests/cli/generate/component.rs b/tests/cli/generate/component.rs new file mode 100644 index 0000000..4173685 --- /dev/null +++ b/tests/cli/generate/component.rs @@ -0,0 +1,56 @@ +use anyhow::Result; +use assert_fs::{fixture::PathChild, prelude::PathAssert}; + +use crate::helpers::*; + +#[test] +fn generate_new_leptos_component() -> Result<()> { + let temp_dir = assert_fs::TempDir::new()?; + + { + let mut cmd = create_cmd()?; + cmd.current_dir(&temp_dir).arg("new").arg("ultime-project"); + + cmd.assert().success(); + } + + let project_dir = temp_dir.child("ultime-project"); + + let mut cmd = create_cmd()?; + cmd.current_dir(&project_dir) + .arg("generate") + .arg("component") + .arg("my-component"); + + cmd.assert() + .success() + .stdout("Component my-component successfully created\n"); + + let src_dir = project_dir.child("src"); + let components_dir = src_dir.child("components"); + + assert!(components_dir.exists()); + + let my_component_file = components_dir.child("my_component.rs"); + + assert!(my_component_file.is_file()); + my_component_file.assert( + r#"use leptos::*; + +#[component] +pub fn MyComponent(cx: Scope) -> impl IntoView { + let (count, set_count) = create_signal(cx, 0); + let on_click = move |_| set_count.update(|count| *count += 1); + + view! { cx, + + } +}"#, + ); + + temp_dir.close()?; + + Ok(()) +}