From c5644d3497c8e9d3b565a77f07aab1c025f88d93 Mon Sep 17 00:00:00 2001 From: Petter Rasmussen Date: Sun, 28 Aug 2022 12:36:21 +0300 Subject: [PATCH] Add 'add page' command --- Cargo.lock | 7 +++ Cargo.toml | 1 + src/main.rs | 32 ++++++++++ src/project.rs | 160 ++++++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 197 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cbf9527..17b4443 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -154,6 +154,12 @@ dependencies = [ "os_str_bytes", ] +[[package]] +name = "convert_case" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb4a24b1aaf0fd0ce8b45161144d6f42cd91677fd5940fd431183eb023b3a2b8" + [[package]] name = "cpufeatures" version = "0.2.4" @@ -509,6 +515,7 @@ name = "polyester-cli" version = "0.1.0" dependencies = [ "clap", + "convert_case", "data-encoding", "fs_extra", "gitignored", diff --git a/Cargo.toml b/Cargo.toml index bc6e211..944235e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ path = "src/main.rs" [dependencies] clap = { version = "3.2.17", features = ["derive"] } +convert_case = "0.5.0" data-encoding = "2.3.2" fs_extra = "1.2.0" gitignored = "0.4.0" diff --git a/src/main.rs b/src/main.rs index 08ece47..c1087a5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -41,6 +41,11 @@ enum Commands { name: String, }, + Add { + #[clap(subcommand)] + command: AddCommand, + }, + /// Build the project #[clap(arg_required_else_help = false)] Build { @@ -66,6 +71,16 @@ enum Commands { }, } +#[derive(Debug, Subcommand)] +enum AddCommand { + /// Create a new project + #[clap(arg_required_else_help = true)] + Page { + /// Page name + name: String, + }, +} + fn main() { let args = Cli::parse(); @@ -82,6 +97,23 @@ fn main() { println!("{:?}", res); } + Commands::Add { command } => { + // fmt + match command { + AddCommand::Page { name } => { + let current_dir = get_current_dir(); + let project_info = ProjectInfo::from_dir(¤t_dir).unwrap(); + let project = Project::new(project::Config { + current_dir: current_dir.clone(), + name: project_info.project_name.clone(), + template: project::Template::CounterTailwind, + }); + let res = project.add_page(&project_info, &name); + println!("{:?}", res); + } + } + } + Commands::Build { script, release, diff --git a/src/project.rs b/src/project.rs index f3bab17..3ab8755 100644 --- a/src/project.rs +++ b/src/project.rs @@ -1,4 +1,7 @@ +use crate::project_info; +use crate::project_info::ProjectInfo; use crate::util::file_util; +use convert_case::{Case, Casing}; use std::convert::identity; use std::fs; use std::io; @@ -30,6 +33,10 @@ pub enum Error { RenameDir(io::Error), CopyToDestination(fs_extra::error::Error), RenameTemplateDir(io::Error), + TemplateProjectInfo(project_info::Error), + ReadCoreHomePage(io::Error), + WriteCoreHomePage(io::Error), + ReadLibFile(io::Error), } impl Project { @@ -39,17 +46,67 @@ impl Project { pub fn create(&self) -> Result<(), Error> { validate_name(&self.config.name)?; - let template_info = self.config.template.info(); let temp_dir = tempfile::tempdir().map_err(Error::TempDir)?; + let template_dir = self.prepare_template(&temp_dir)?; + copy_to_dest(&self.config.name, &template_dir, &self.config.current_dir)?; + + Ok(()) + } + + pub fn add_page(&self, project_info: &ProjectInfo, name: &str) -> Result<(), Error> { + let page_name = PageName::new(name); + let template_page_name = self.config.template.info().default_page_name; + let temp_dir = tempfile::tempdir().map_err(Error::TempDir)?; + let template_dir = self.prepare_template(&temp_dir)?; + let template_project_info = + ProjectInfo::from_dir(&template_dir).map_err(Error::TemplateProjectInfo)?; + + // Add page to core project + copy_page_template( + &template_project_info.core_project_path, + &template_page_name, + &project_info.core_project_path, + &page_name, + "rs", + )?; + + // Add page to core lib + add_page_to_lib(&project_info.core_project_path, &page_name)?; + + // Add page to wasm project + copy_page_template( + &template_project_info.wasm_project_path, + &template_page_name, + &project_info.wasm_project_path, + &page_name, + "rs", + )?; + + // Add page to wasm lib + add_page_to_lib(&project_info.wasm_project_path, &page_name)?; + + // Add page to web project + copy_page_template( + &template_project_info.web_project_path, + &template_page_name, + &project_info.web_project_path, + &page_name, + "ts", + )?; + + Ok(()) + } + + fn prepare_template(&self, temp_dir: &tempfile::TempDir) -> Result { + let template_info = self.config.template.info(); let temp_dir_path = temp_dir.path(); let template_dir = temp_dir_path.join(&template_info.path); let bytes = download_file(&template_info)?; extract_zip(bytes, temp_dir_path)?; replace_placeholders(&self.config.name, &template_info, &template_dir)?; - copy_to_dest(&self.config.name, &template_dir, &self.config.current_dir)?; - Ok(()) + Ok(template_dir) } } @@ -69,6 +126,7 @@ pub struct TemplateInfo { url: String, path: String, placeholder: String, + default_page_name: PageName, } impl Template { @@ -80,6 +138,7 @@ impl Template { url: "https://github.com/polyester-web/polyester-templates/archive/refs/heads/main.zip".to_string(), path: "counter-tailwind".to_string(), placeholder: "myapp".to_string(), + default_page_name: PageName::new("home_page"), } } @@ -228,6 +287,62 @@ fn copy_to_dest(project_name: &str, template_dir: &PathBuf, dest: &PathBuf) -> R Ok(()) } +fn copy_page_template( + template_base_path: &PathBuf, + template_page_name: &PageName, + base_path: &PathBuf, + page_name: &PageName, + file_ext: &str, +) -> Result<(), Error> { + let rel_src_file_path = format!("src/{}.{}", template_page_name.snake_case(), file_ext); + + let template_home_page_path = template_base_path.join(&rel_src_file_path); + + let template_file = + file_util::read(&template_home_page_path).map_err(Error::ReadCoreHomePage)?; + + let new_content = replace_page_name(&template_file.content, &template_page_name, &page_name); + + let page_file = file_util::FileData { + content: new_content, + permissions: template_file.permissions, + }; + + let new_file_name = format!("src/{}.{}", page_name.snake_case(), file_ext); + let new_file_path = base_path.join(new_file_name); + + file_util::write(&new_file_path, page_file).map_err(Error::WriteCoreHomePage)?; + + Ok(()) +} + +fn add_page_to_lib(base_path: &PathBuf, page_name: &PageName) -> Result<(), Error> { + let lib_path = base_path.join("src/lib.rs"); + let lib_file = file_util::read(&lib_path).map_err(Error::ReadLibFile)?; + let page_module = format!("pub mod {};", page_name.snake_case()); + + let mut new_content = lib_file.content; + if !new_content.ends_with('\n') { + new_content.push_str("\n"); + } + + if !new_content.contains(&page_module) { + new_content.push_str(&page_module); + new_content.push_str("\n"); + } + + file_util::write( + &lib_path, + file_util::FileData { + content: new_content, + permissions: lib_file.permissions, + }, + ) + .map_err(Error::WriteFile)?; + + Ok(()) +} + fn validate_name(name: &str) -> Result<(), Error> { let not_empty = !name.is_empty(); let has_valid_chars = name.chars().all(|c| c.is_ascii_lowercase() || c == '_'); @@ -243,3 +358,42 @@ fn validate_name(name: &str) -> Result<(), Error> { .then_some(()) .ok_or(Error::InvalidProjectName) } + +fn replace_page_name(content: &str, from: &PageName, to: &PageName) -> String { + content + .replace(&from.snake_case(), &to.snake_case()) + .replace(&from.pascal_case(), &to.pascal_case()) + .replace(&from.camel_case(), &to.camel_case()) + .replace(&from.title_case(), &to.title_case()) +} + +#[derive(Debug, Clone)] +pub struct PageName(String); + +impl PageName { + pub fn new(name: &str) -> PageName { + let mut snake_case_name = name.to_case(Case::Snake); + + if !snake_case_name.ends_with("page") { + snake_case_name.push_str("_page"); + } + + PageName(snake_case_name) + } + + pub fn snake_case(&self) -> String { + self.0.clone() + } + + pub fn pascal_case(&self) -> String { + self.0.from_case(Case::Snake).to_case(Case::Pascal) + } + + pub fn camel_case(&self) -> String { + self.0.from_case(Case::Snake).to_case(Case::Camel) + } + + pub fn title_case(&self) -> String { + self.0.from_case(Case::Snake).to_case(Case::Title) + } +}