From f6c58a4ff5765df6e6ecab1037689e81988dab98 Mon Sep 17 00:00:00 2001 From: cophilot Date: Sat, 17 Feb 2024 16:59:30 +0100 Subject: [PATCH] v0.5.0 --- .phil-project | 2 +- CHANGELOG.md | 11 +++ Cargo.lock | 2 +- Cargo.toml | 2 +- README.md | 8 +- src/command_storage.rs | 41 ++++++++- src/commands.rs | 176 +++++++++++++++++++++++++++------------ src/main.rs | 2 + src/types.rs | 93 +++++++++++++++++++++ src/utils.rs | 183 +++++++++++++++++++++++++++-------------- 10 files changed, 396 insertions(+), 124 deletions(-) diff --git a/.phil-project b/.phil-project index 7d669f8..79158bc 100644 --- a/.phil-project +++ b/.phil-project @@ -1,4 +1,4 @@ logo:assets/logo.png logo_small:assets/logo.png description_translate:de -version:0.4.1 +version:0.5.0 diff --git a/CHANGELOG.md b/CHANGELOG.md index 409cf51..d0ec81c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,17 @@ --- +## [v0.5.0](https://github.com/cophilot/templify/tree/0.5.0) (2024-2-17) + +- Refactoring +- Added `reload` command +- Added `-name` flag for the `list` command +- Added `-path` flag for the `list` command +- Added `-template` flag for the `load` command +- Support for `.tpykeep` file to prevent a directory from being deleted + +--- + ## [v0.4.1](https://github.com/cophilot/templify/tree/0.4.1) (2024-2-14) - Refactoring diff --git a/Cargo.lock b/Cargo.lock index 50e33a8..a4ad0f8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -845,7 +845,7 @@ dependencies = [ [[package]] name = "templify" -version = "0.4.1" +version = "0.5.0" dependencies = [ "chrono", "reqwest", diff --git a/Cargo.toml b/Cargo.toml index 7ddd77c..7fd7f8f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "templify" -version = "0.4.1" +version = "0.5.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/README.md b/README.md index d174122..8559c76 100644 --- a/README.md +++ b/README.md @@ -213,10 +213,14 @@ tpy load https://github.com/cophilot/templify-vault/tree/main/React-ts ## [Release Notes](https://github.com/cophilot/templify/blob/master/CHANGELOG.md) -### [v0.4.1](https://github.com/cophilot/templify/tree/0.4.1) +### [v0.5.0](https://github.com/cophilot/templify/tree/0.5.0) - Refactoring -- `.source` attribute will be set in the `.templify` file when a template is loaded +- Added `reload` command +- Added `-name` flag for the `list` command +- Added `-path` flag for the `list` command +- Added `-template` flag for the `load` command +- Support for `.tpykeep` file to prevent a directory from being deleted --- diff --git a/src/command_storage.rs b/src/command_storage.rs index 6cf93cc..62f32bb 100644 --- a/src/command_storage.rs +++ b/src/command_storage.rs @@ -97,12 +97,22 @@ pub fn get_all_commands() -> Vec { // *** list *** - let list_com = Command::new( + let mut list_com = Command::new( vec!["list".to_string(), "ls".to_string()], commands::list, "List all available templates in the current project.".to_string(), ); + list_com.add_flag(Flag::new_bool_flag( + vec!["name".to_string(), "n".to_string()], + "Show only the names of the templates.".to_string(), + )); + + list_com.add_flag(Flag::new_bool_flag( + vec!["path".to_string(), "p".to_string()], + "Show the path of the templates.".to_string(), + )); + commands.push(list_com); // *** load *** @@ -110,7 +120,7 @@ pub fn get_all_commands() -> Vec { let mut load_com = Command::new( vec!["load".to_string(), "l".to_string()], commands::load, - "Load templates from a github repository.".to_string(), + "Load templates from a remote repository.".to_string(), ); load_com.add_argument(Argument::new( @@ -125,8 +135,35 @@ pub fn get_all_commands() -> Vec { "Force the load, even if the folder already exists.".to_string(), )); + load_com.add_flag(Flag::new_bool_flag( + vec!["template".to_string(), "t".to_string()], + "Load only one template.".to_string(), + )); + commands.push(load_com); + // *** reload *** + + let mut reload_com = Command::new( + vec!["reload".to_string(), "rl".to_string()], + commands::reload, + "Reload templates from a github repository.".to_string(), + ); + + reload_com.add_argument(Argument::new( + "template-name".to_string(), + 0, + false, + "The name of the template to reload (reload all if not provided).".to_string(), + )); + + reload_com.add_flag(Flag::new_bool_flag( + vec!["strict".to_string()], + "If enabled the template name must match exactly.".to_string(), + )); + + commands.push(reload_com); + // *** generate *** let mut generate_com = Command::new( diff --git a/src/commands.rs b/src/commands.rs index 215ccdf..2309913 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -1,11 +1,11 @@ use crate::{ - types::{Command, Status}, + types::{self, Command, Status}, utils, version_control, }; use chrono::{self, Datelike}; use std::fs::read_dir; -pub fn list(_command: &Command) -> Status { +pub fn list(command: &Command) -> Status { let st = utils::check_if_templify_initialized(); if !st.is_ok { return st; @@ -14,20 +14,26 @@ pub fn list(_command: &Command) -> Status { // get all folders in .templates let paths = read_dir(".templates").unwrap(); + let print_path = command.get_bool_flag("path"); + let only_name = command.get_bool_flag("name"); + println!("Available templates:"); for path in paths { let path = path.unwrap().path(); if path.is_dir() { let template_name = path.file_name().unwrap().to_str().unwrap(); - let description = - utils::parse_templify_file(&format!(".templates/{}/.templify", template_name)) - ["description"] - .clone(); - if description.is_empty() { - println!(" {}", template_name); - } else { - println!(" {} - {}", template_name, description); + + let meta = types::TemplateMeta::parse(template_name.to_string()); + + let mut print_string = template_name.to_string(); + + if !meta.get_description().is_empty() && !only_name { + print_string = format!("{} - {}", print_string, meta.get_description()); } + if print_path { + print_string = format!("{} [{}]", print_string, meta.get_path()); + } + println!(" {}", print_string); } } return Status::ok(); @@ -47,70 +53,132 @@ pub fn load(command: &Command) -> Status { let url = command.get_argument("url").value.clone(); if !url.starts_with("https://github.com") { return Status::error(format!( - "Invalid url: {}\nOnly github templates are supported at the moment.", + "Invalid url: {}\nOnly templates from GitHub are supported at the moment.", url )); } - println!("Loading template from {}...", url); - utils::load_remote_template_repo(".templates", url.as_str(), command.get_bool_flag("force")); + + let load_template = command.get_bool_flag("template"); + if load_template { + println!("Loading template from {}...", url); + let name = url.split("/").last().unwrap(); + let st = utils::load_remote_template( + format!(".templates/{}", name).as_str(), + url.as_str(), + command.get_bool_flag("force"), + ); + if !st.is_ok { + return st; + } + } else { + println!("Loading template collection from {}...", url); + let st = utils::load_remote_template_collection( + ".templates", + url.as_str(), + command.get_bool_flag("force"), + ); + if !st.is_ok { + return st; + } + } return Status::ok(); } -pub fn generate(command: &Command) -> Status { +pub fn reload(command: &Command) -> Status { + if !utils::check_internet_connection() { + println!("You need a internet connection for this command!"); + return Status::error("You need a internet connection for this command!".to_string()); + } + let st = utils::check_if_templify_initialized(); if !st.is_ok { return st; } - let strict = command.get_bool_flag("strict"); - let dry_run = command.get_bool_flag("dry-run"); - - let mut template_name = command.get_argument("template-name").value.clone(); - let parsed_template_name = template_name.clone().to_lowercase().to_string(); - let template_name_raw = template_name.clone().to_string(); + let mut name = command.get_argument("template-name").value.clone(); + if name != "" { + let st = utils::parse_template_name(&mut name, strict); + if !st.is_ok { + return st; + } + let meta = types::TemplateMeta::parse(name.clone().to_string()); + if meta.get_source().is_empty() { + return Status::error(format!("Template {} has no source.", name)); + } + println!( + "Reloading template {} from {}...", + meta.get_template_name(), + meta.get_source() + ); + let st = utils::load_remote_template( + format!(".templates/{}", name).as_str(), + meta.get_source().as_str(), + true, + ); + if !st.is_ok { + return st; + } + println!("Template {} reloaded successfully.", name); + return Status::ok(); + } - let given_name = command.get_argument("new-name").value.clone(); + let paths: std::fs::ReadDir = read_dir(".templates").unwrap(); - let paths = std::fs::read_dir(".templates").unwrap(); - let mut found = false; for path in paths { let path = path.unwrap().path(); - - let path_name = path - .file_name() - .unwrap() - .to_str() - .unwrap() - .to_string() - .clone(); - - let parsed_path_name = path_name.clone().to_lowercase().to_string(); - - if path.is_dir() && parsed_path_name.starts_with(parsed_template_name.as_str()) && !strict { - if found { - return Status::error(format!( - "Template {} is not unique. Please use a more specific name.", - template_name_raw - )); - } - template_name = path_name.clone(); - found = true; - } else if path.is_dir() && path_name == template_name && strict { - template_name = path_name.clone(); - found = true; - break; + if !path.is_dir() { + continue; + } + let template_name = path.file_name().unwrap().to_str().unwrap(); + let meta = types::TemplateMeta::parse(template_name.to_string()); + if meta.get_source().is_empty() { + continue; } + println!( + "Reloading template {} from {}...", + meta.get_template_name(), + meta.get_source() + ); + let st = utils::load_remote_template( + format!(".templates/{}", template_name).as_str(), + meta.get_source().as_str(), + true, + ); + if !st.is_ok { + println!("Error: Template {} could not be reloaded!", template_name); + println!(""); + continue; + } + println!("Template {} reloaded successfully.", template_name); + println!(""); } - if !found { - return Status::error(format!("Template {} not found.", template_name)); + return Status::ok(); +} + +pub fn generate(command: &Command) -> Status { + let st = utils::check_if_templify_initialized(); + if !st.is_ok { + return st; } - println!("Generating new files from template {}...", template_name); + let strict = command.get_bool_flag("strict"); + let dry_run = command.get_bool_flag("dry-run"); + + let mut template_name = command.get_argument("template-name").value.clone(); + let given_name = command.get_argument("new-name").value.clone(); + + let st = utils::parse_template_name(&mut template_name, strict); + if !st.is_ok { + return st; + } - let mut new_path = - utils::parse_templify_file(&format!(".templates/{}/.templify", template_name))["path"] - .clone(); + println!( + "Generating new files from template {}...", + template_name.clone() + ); + let meta = types::TemplateMeta::parse(template_name.clone().to_string()); + let mut new_path = meta.get_path(); new_path = new_path.replace("$$name$$", given_name.as_str()); new_path = new_path.replace("$$year$$", chrono::Local::now().year().to_string().as_str()); @@ -221,7 +289,7 @@ pub fn init(command: &Command) -> Status { // check if there is an internet connection if utils::check_internet_connection() && !command.get_bool_flag("offline") { println!("Loading example template from templify-vault..."); - utils::load_remote_template_repo( + utils::load_remote_template_collection( ".templates", "https://github.com/cophilot/templify-vault/tree/main/Example", true, diff --git a/src/main.rs b/src/main.rs index 3d08777..50f1156 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,6 +16,8 @@ fn main() { unsafe { env::BASE_COMMAND_NAME = args[0].clone() }; + utils::handle_dev_mode(&args); + if args.len() < 2 { println!("Welcome to templify!"); println!(""); diff --git a/src/types.rs b/src/types.rs index 6aba999..29c81bd 100644 --- a/src/types.rs +++ b/src/types.rs @@ -288,3 +288,96 @@ impl Status { } } } + +// ***TemplateMeta*** + +pub(crate) struct TemplateMeta { + template_name: String, + file_path: String, + map: std::collections::HashMap, +} + +impl TemplateMeta { + pub fn new(template_name: String) -> TemplateMeta { + let mut map = std::collections::HashMap::new(); + + map.insert("description".to_string(), "".to_string()); + map.insert("path".to_string(), ".".to_string()); + map.insert(".source".to_string(), "".to_string()); + + let file_path = format!(".templates/{}/.templify", template_name); + + let meta = TemplateMeta { + template_name: template_name.clone(), + file_path: file_path.clone(), + map: map, + }; + return meta; + } + + pub fn parse(template_name: String) -> TemplateMeta { + let mut meta = TemplateMeta::new(template_name.clone()); + + let file_content = std::fs::read_to_string(meta.file_path.clone()); + if file_content.is_err() { + return meta; + } + + let file_content = file_content.unwrap(); + + let mut divider = ":".to_string(); + + let first_line = file_content.lines().next(); + if first_line.is_none() { + return meta; + } + + let first_line = first_line.unwrap().replace(" ", ""); + if first_line.starts_with("#!") { + let new_divider = first_line.clone().replace("#!", ""); + + divider = new_divider.to_string(); + } + + for line in file_content.lines() { + let line = line.trim(); + if line.starts_with("#") || line.is_empty() { + continue; + } + + let parts: Vec<&str> = line.split(divider.as_str()).collect(); + if parts.len() < 2 { + continue; + } + let mut second_part = parts[1].to_string(); + if parts.len() > 2 { + for i in 2..parts.len() { + second_part.push_str(format!("{}{}", divider, parts[i]).as_str()); + } + } + + let key = parts[0].trim().to_string().to_lowercase(); + let value = second_part.trim().to_string(); + + meta.map.insert(key, value); + } + + return meta; + } + + pub fn get_template_name(&self) -> String { + return self.template_name.clone(); + } + + pub fn get_description(&self) -> String { + return self.map["description"].clone(); + } + + pub fn get_path(&self) -> String { + return self.map["path"].clone(); + } + + pub fn get_source(&self) -> String { + return self.map[".source"].clone(); + } +} diff --git a/src/utils.rs b/src/utils.rs index 1bb3c81..01ce8da 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -3,60 +3,65 @@ use std::{io::Write, path::Path}; use crate::types::Status; -pub fn parse_templify_file(file_path: &str) -> std::collections::HashMap { - let mut map = std::collections::HashMap::new(); +pub fn parse_template_name(name: &mut String, strict: bool) -> Status { + let template_name_raw = name.clone().to_string(); + let parsed_template_name = name.clone().to_lowercase().to_string(); - map.insert("description".to_string(), "".to_string()); - map.insert("path".to_string(), ".".to_string()); + let mut found = false; + let paths = std::fs::read_dir(".templates").unwrap(); - let file_content = std::fs::read_to_string(file_path); - if file_content.is_err() { - return map; - } - let file_content = file_content.unwrap(); - - let mut divider = ":".to_string(); + for path in paths { + let path = path.unwrap().path(); - let first_line = file_content.lines().next(); - if first_line.is_none() { - return map; + let path_name = path + .file_name() + .unwrap() + .to_str() + .unwrap() + .to_string() + .clone(); + + let parsed_path_name = path_name.clone().to_lowercase().to_string(); + + if path.is_dir() && parsed_path_name.starts_with(parsed_template_name.as_str()) && !strict { + if found { + return Status::error(format!( + "Template {} is not unique. Please use a more specific name.", + template_name_raw + )); + } + // assign path_name to name so that it can be used from the caller + *name = String::from(path_name.clone()); + found = true; + } else if path.is_dir() && path_name == name.clone() && strict { + found = true; + *name = String::from(path_name.clone()); + break; + } } - let first_line = first_line.unwrap().replace(" ", ""); - if first_line.starts_with("#!") { - let new_divider = first_line.clone().replace("#!", ""); - - divider = new_divider.to_string(); + if !found { + return Status::error(format!("Template {} not found.", name)); } + return Status::ok(); +} - for line in file_content.lines() { - let line = line.trim(); - if line.starts_with("#") || line.is_empty() { - continue; - } - - let parts: Vec<&str> = line.split(divider.as_str()).collect(); - if parts.len() < 2 { - continue; - } - - let key = parts[0].trim().to_string().to_lowercase(); - let value = parts[1].trim().to_string(); - - map.insert(key, value); +pub fn load_remote_template_collection(path: &str, url: &str, force: bool) -> Status { + let response = reqwest::blocking::get(url); + if response.is_err() { + return Status::error(format!("Failed to get template from {}", url)); } + let response = response.unwrap().json(); + if response.is_err() { + return Status::error(format!("Failed to get template from {}", url)); + } + let response: serde_json::Value = response.unwrap(); - return map; -} - -pub fn load_remote_template_repo(path: &str, url: &str, force: bool) { - let response = reqwest::blocking::get(url).unwrap(); - let response: serde_json::Value = response.json().unwrap(); let items = response["payload"]["tree"]["items"].as_array().unwrap(); for item in items { if item["contentType"] == "directory" { - load_remote_template( + let st = load_remote_template( format!("{}/{}", path, item["name"]) .replace("\"", "") .as_str(), @@ -65,30 +70,41 @@ pub fn load_remote_template_repo(path: &str, url: &str, force: bool) { .as_str(), force, ); + if !st.is_ok { + return st; + } } } + return Status::ok(); } -fn load_remote_template(path: &str, url: &str, force: bool) { +pub fn load_remote_template(path: &str, url: &str, force: bool) -> Status { if !force && Path::new(path).exists() { - println!( + return Status::error(format!( "Template {} already exists...", path.replace(".templates/", "") - ); - return; + )); } if !Path::new(path).exists() { std::fs::create_dir(path).unwrap(); } - let response = reqwest::blocking::get(url).unwrap(); - let response: serde_json::Value = response.json().unwrap(); + let response = reqwest::blocking::get(url); + if response.is_err() { + return Status::error(format!("Failed to get template from {}", url)); + } + let response = response.unwrap().json(); + if response.is_err() { + return Status::error(format!("Failed to get template from {}", url)); + } + let response: serde_json::Value = response.unwrap(); + let items = response["payload"]["tree"]["items"].as_array().unwrap(); for item in items { if item["contentType"] == "directory" { - load_remote_template_dir( + let st = load_remote_template_dir( format!("{}/{}", path, item["name"]) .replace("\"", "") .as_str(), @@ -97,10 +113,13 @@ fn load_remote_template(path: &str, url: &str, force: bool) { .as_str(), force, ); + if !st.is_ok { + return st; + } continue; } - load_remote_template_file( + let st = load_remote_template_file( format!("{}/{}", path, item["name"]) .replace("\"", "") .as_str(), @@ -109,6 +128,9 @@ fn load_remote_template(path: &str, url: &str, force: bool) { .as_str(), force, ); + if !st.is_ok { + return st; + } } let temp_file = format!("{}/.templify", path); @@ -128,7 +150,7 @@ fn load_remote_template(path: &str, url: &str, force: bool) { // check if url already exists in .templify file let file_content = std::fs::read_to_string(format!("{}/.templify", path).as_str()); if file_content.is_err() { - return; + return Status::ok(); } let file_content = file_content.unwrap(); if !file_content.contains(".source") { @@ -137,23 +159,35 @@ fn load_remote_template(path: &str, url: &str, force: bool) { } println!("Loaded template: {}", path.replace(".templates/", "")); + return Status::ok(); } -fn load_remote_template_dir(path: &str, url: &str, force: bool) { +fn load_remote_template_dir(path: &str, url: &str, force: bool) -> Status { if !force && Path::new(path).exists() { - println!("Directory {} already exists...", path); - return; + return Status::error(format!( + "Directory {} already exists...", + path.replace(".templates/", "") + )); } - std::fs::create_dir(path).unwrap(); + if !Path::new(path).exists() { + std::fs::create_dir(path).unwrap(); + } - let response = reqwest::blocking::get(url).unwrap(); - let response: serde_json::Value = response.json().unwrap(); + let response = reqwest::blocking::get(url); + if response.is_err() { + return Status::error(format!("Failed to get template from {}", url)); + } + let response = response.unwrap().json(); + if response.is_err() { + return Status::error(format!("Failed to get template from {}", url)); + } + let response: serde_json::Value = response.unwrap(); let items = response["payload"]["tree"]["items"].as_array().unwrap(); for item in items { if item["contentType"] == "directory" { - load_remote_template_dir( + let st = load_remote_template_dir( format!("{}/{}", path, item["name"]) .replace("\"", "") .as_str(), @@ -162,6 +196,9 @@ fn load_remote_template_dir(path: &str, url: &str, force: bool) { .as_str(), force, ); + if !st.is_ok { + return st; + } continue; } @@ -175,16 +212,26 @@ fn load_remote_template_dir(path: &str, url: &str, force: bool) { force, ); } + return Status::ok(); } -fn load_remote_template_file(path: &str, url: &str, force: bool) { +fn load_remote_template_file(path: &str, url: &str, force: bool) -> Status { if Path::new(path).exists() && !force { - println!("File {} already exists.", path); - return; + return Status::error(format!( + "File {} already exists...", + path.replace(".templates/", "") + )); } - let response = reqwest::blocking::get(url).unwrap(); - let response: serde_json::Value = response.json().unwrap(); + let response = reqwest::blocking::get(url); + if response.is_err() { + return Status::error(format!("Failed to get template from {}", url)); + } + let response = response.unwrap().json(); + if response.is_err() { + return Status::error(format!("Failed to get template from {}", url)); + } + let response: serde_json::Value = response.unwrap(); let text = response["payload"]["blob"]["rawLines"].as_array().unwrap(); let mut text = text @@ -204,6 +251,7 @@ fn load_remote_template_file(path: &str, url: &str, force: bool) { new_file.write_all(text.as_bytes()).unwrap(); println!("Created file {}", path); + return Status::ok(); } pub fn generate_template_dir(path: &str, new_path: &str, given_name: &str, dry_run: bool) -> bool { @@ -212,7 +260,7 @@ pub fn generate_template_dir(path: &str, new_path: &str, given_name: &str, dry_r let path = path.unwrap().path(); let file_name = path.file_name().unwrap().to_str().unwrap(); - if file_name == ".templify" { + if file_name == ".templify" || file_name == ".tpykeep" || file_name == ".templifykeep" { continue; } @@ -307,3 +355,12 @@ pub fn get_git_name() -> String { } return name; } + +pub fn handle_dev_mode(args: &Vec) { + if args.contains(&"-dev".to_string()) { + if !Path::new("dev").exists() { + std::fs::create_dir("dev").unwrap(); + } + std::env::set_current_dir("dev").unwrap(); + } +}