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(())
+}