From b7360ed8259e88fc42fe17bb0d11c5c0294ffc06 Mon Sep 17 00:00:00 2001 From: RounakBytes Date: Mon, 9 Sep 2024 10:04:18 +0530 Subject: [PATCH 1/8] Github Method Extracted and Enum Declared for URL Type - 1.0.0 --- src/commands/init.rs | 2 + src/commands/load.rs | 24 +++++++- src/utils/template_handler.rs | 111 ++++++++++++++++++++++++---------- 3 files changed, 101 insertions(+), 36 deletions(-) diff --git a/src/commands/init.rs b/src/commands/init.rs index a281246..4b52a61 100644 --- a/src/commands/init.rs +++ b/src/commands/init.rs @@ -1,3 +1,4 @@ +use crate::commands::load::URLType; use crate::log; use crate::types::command::Command; use crate::types::flag::Flag; @@ -52,6 +53,7 @@ pub(crate) fn init(command: &Command) -> Status { ".templates", "https://github.com/cophilot/templify-vault/tree/main/Example", true, + &URLType::GitHub, ); } log!("templify initialized successfully."); diff --git a/src/commands/load.rs b/src/commands/load.rs index fbe70be..169a762 100644 --- a/src/commands/load.rs +++ b/src/commands/load.rs @@ -5,6 +5,11 @@ use crate::types::flag::Flag; use crate::types::status::Status; use crate::utils; +pub(crate) enum URLType { + GitHub, + GitLab, +} + /// The definition of the load command. pub(crate) fn definition() -> Command { let mut load_command = Command::new( @@ -46,12 +51,23 @@ pub(crate) fn load(command: &Command) -> Status { } let url = command.get_argument("url").value.clone(); - if !url.starts_with("https://github.com") { + // if !url.starts_with("https://github.com") && !url.starts_with("https://gitlab.com") { + // return Status::error(format!( + // "Invalid url: {}\nOnly templates from GitHub and Gitlab are supported at the moment.", + // url + // )); + // } + + let url_type = if url.starts_with("https://github.com") { + URLType::GitHub + } else if url.starts_with("https://gitlab.com") { + URLType::GitLab + } else { return Status::error(format!( - "Invalid url: {}\nOnly templates from GitHub are supported at the moment.", + "Invalid url: {}\nOnly templates from GitHub and Gitlab are supported at the moment.", url )); - } + }; let load_template = command.get_bool_flag("template"); if load_template { @@ -61,6 +77,7 @@ pub(crate) fn load(command: &Command) -> Status { format!(".templates/{}", name).as_str(), url.as_str(), command.get_bool_flag("force"), + &url_type, ); if !st.is_ok { return st; @@ -71,6 +88,7 @@ pub(crate) fn load(command: &Command) -> Status { ".templates", url.as_str(), command.get_bool_flag("force"), + &url_type, ); if !st.is_ok { return st; diff --git a/src/utils/template_handler.rs b/src/utils/template_handler.rs index 63ac26b..77445f2 100644 --- a/src/utils/template_handler.rs +++ b/src/utils/template_handler.rs @@ -1,3 +1,4 @@ +use crate::commands::load::URLType; use crate::log; use crate::types::status::Status; use crate::types::template_meta::TemplateMeta; @@ -80,10 +81,24 @@ pub(crate) fn reload_template(name: String, strict: bool, reset: bool) -> Status std::fs::rename(&dir, &backup_dir).unwrap(); } + let url = meta.get_source(); + + let url_type = if url.starts_with("https://github.com") { + URLType::GitHub + } else if url.starts_with("https://gitlab.com") { + URLType::GitLab + } else { + return Status::error(format!( + "Invalid url: {}\nOnly templates from GitHub and Gitlab are supported at the moment.", + url + )); + }; + let st = load_remote_template( format!(".templates/{}", name).as_str(), - meta.get_source().as_str(), + url.as_str(), true, + &url_type, ); if !st.is_ok { if reset { @@ -101,7 +116,12 @@ pub(crate) fn reload_template(name: String, strict: bool, reset: bool) -> Status } /// Load a collection of templates from a remote repository -pub(crate) fn load_remote_template_collection(path: &str, url: &str, force: bool) -> Status { +pub(crate) fn load_remote_template_collection( + path: &str, + url: &str, + force: bool, + url_type: &URLType, +) -> Status { let response = rest::json_call(url); if response.is_err() { return Status::error(format!( @@ -129,6 +149,7 @@ pub(crate) fn load_remote_template_collection(path: &str, url: &str, force: bool .replace('"', "") .as_str(), force, + &url_type, ); if !st.is_ok { return st; @@ -138,35 +159,13 @@ pub(crate) fn load_remote_template_collection(path: &str, url: &str, force: bool Status::ok() } -/// Load a template from a remote repository -pub(crate) fn load_remote_template(path: &str, url: &str, force: bool) -> Status { - if !force && Path::new(path).exists() { - return Status::error(format!( - "Template {} already exists...", - path.replace(".templates/", "") - )); - } - - if !Path::new(path).exists() { - std::fs::create_dir(path).unwrap(); - } - - let response = rest::json_call(url); - if response.is_err() { - return Status::error(format!( - "Failed to get template from {}: Request failed", - url - )); - } - let response = response.unwrap().json(); - if response.is_err() { - return Status::error(format!( - "Failed to get template from {}: JSON parse error", - url - )); - } - let response: serde_json::Value = response.unwrap(); - +/// Load a template from a github repository +pub(crate) fn load_github_template( + response: serde_json::Value, + path: &str, + url: &str, + force: bool, +) -> Option { let items = response["payload"]["tree"]["items"].as_array().unwrap(); for item in items { @@ -181,7 +180,7 @@ pub(crate) fn load_remote_template(path: &str, url: &str, force: bool) -> Status force, ); if !st.is_ok { - return st; + return Some(st); } continue; } @@ -196,10 +195,56 @@ pub(crate) fn load_remote_template(path: &str, url: &str, force: bool) -> Status force, ); if !st.is_ok { - return st; + return Some(st); } } + return None; +} + +/// Load a template from a remote repository +pub(crate) fn load_remote_template( + path: &str, + url: &str, + force: bool, + url_type: &URLType, +) -> Status { + if !force && Path::new(path).exists() { + return Status::error(format!( + "Template {} already exists...", + path.replace(".templates/", "") + )); + } + + if !Path::new(path).exists() { + std::fs::create_dir(path).unwrap(); + } + + let response = rest::json_call(url); + if response.is_err() { + return Status::error(format!( + "Failed to get template from {}: Request failed", + url + )); + } + let response = response.unwrap().json(); + if response.is_err() { + return Status::error(format!( + "Failed to get template from {}: JSON parse error", + url + )); + } + let response: serde_json::Value = response.unwrap(); + + let status = match url_type { + URLType::GitHub => load_github_template(response, path, url, force), + URLType::GitLab => None, + }; + + if status.is_some() { + return status.unwrap(); + } + let temp_file = format!("{}/.templify", path); if !Path::new(temp_file.as_str()).exists() { From 1a7b0142a74ffe3f8992fdba0fb4d694004d0401 Mon Sep 17 00:00:00 2001 From: RounakBytes Date: Tue, 10 Sep 2024 22:46:52 +0530 Subject: [PATCH 2/8] Added Load File From Gitlab - 1.0.0 - 1.0.0 --- Cargo.lock | 1 + Cargo.toml | 1 + src/utils/template_handler.rs | 201 ++++++++++++++++++++++++++++++++-- 3 files changed, 191 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 591e789..45d3996 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -913,6 +913,7 @@ dependencies = [ name = "templify" version = "1.0.0" dependencies = [ + "base64", "chrono", "reqwest", "self-replace", diff --git a/Cargo.toml b/Cargo.toml index b076fef..3c42053 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,3 +13,4 @@ reqwest = { version = "0.12", features = ["blocking", "json"] } self-replace = "1.3.6" serde_json = "1.0.1" chrono = "0.4.19" +base64 = "0.21.7" \ No newline at end of file diff --git a/src/utils/template_handler.rs b/src/utils/template_handler.rs index 77445f2..e5b879d 100644 --- a/src/utils/template_handler.rs +++ b/src/utils/template_handler.rs @@ -1,3 +1,5 @@ +use base64::{engine::general_purpose::STANDARD, Engine as _}; + use crate::commands::load::URLType; use crate::log; use crate::types::status::Status; @@ -138,10 +140,30 @@ pub(crate) fn load_remote_template_collection( } let response: serde_json::Value = response.unwrap(); - let items = response["payload"]["tree"]["items"].as_array().unwrap(); + let status = match url_type { + URLType::GitHub => load_github_template(response, path, url, force), + URLType::GitLab => load_gitlab_template(response, path, url, force), + }; + + if !status.is_ok { + return status; + } + + Status::ok() +} + +/// Load a template from a gitlab repository +pub(crate) fn load_gitlab_template( + response: serde_json::Value, + path: &str, + url: &str, + force: bool, +) -> Status { + let items = response.as_array().unwrap(); + for item in items { - if item["contentType"] == "directory" { - let st = load_remote_template( + if item["type"] == "tree" { + let st = load_remote_gitlab_template_dir( format!("{}/{}", path, item["name"]) .replace('"', "") .as_str(), @@ -149,14 +171,34 @@ pub(crate) fn load_remote_template_collection( .replace('"', "") .as_str(), force, - &url_type, ); if !st.is_ok { return st; } + continue; + } + + let base_url = url.split("/tree").next().unwrap_or(""); + + if base_url.is_empty() { + return Status::error(format!("Invalid url: {}\n", url)); + } + + let st = load_remote_gitlab_template_file( + format!("{}/{}", path, item["name"]) + .replace('"', "") + .as_str(), + format!("{}/blobs/{}", base_url, item["id"]) + .replace('"', "") + .as_str(), + force, + ); + if !st.is_ok { + return st; } } - Status::ok() + + return Status::ok(); } /// Load a template from a github repository @@ -165,7 +207,7 @@ pub(crate) fn load_github_template( path: &str, url: &str, force: bool, -) -> Option { +) -> Status { let items = response["payload"]["tree"]["items"].as_array().unwrap(); for item in items { @@ -180,7 +222,7 @@ pub(crate) fn load_github_template( force, ); if !st.is_ok { - return Some(st); + return st; } continue; } @@ -195,11 +237,11 @@ pub(crate) fn load_github_template( force, ); if !st.is_ok { - return Some(st); + return st; } } - return None; + return Status::ok(); } /// Load a template from a remote repository @@ -238,11 +280,11 @@ pub(crate) fn load_remote_template( let status = match url_type { URLType::GitHub => load_github_template(response, path, url, force), - URLType::GitLab => None, + URLType::GitLab => load_gitlab_template(response, path, url, force), }; - if status.is_some() { - return status.unwrap(); + if !status.is_ok { + return status; } let temp_file = format!("{}/.templify", path); @@ -333,6 +375,71 @@ fn load_remote_template_dir(path: &str, url: &str, force: bool) -> Status { Status::ok() } +fn load_remote_gitlab_template_dir(path: &str, url: &str, force: bool) -> Status { + if !force && Path::new(path).exists() { + return Status::error(format!( + "Directory {} already exists...", + path.replace(".templates/", "") + )); + } + + if !Path::new(path).exists() { + std::fs::create_dir(path).unwrap(); + } + + let response = rest::json_call(url); + if response.is_err() { + return Status::error(format!( + "Failed to get template from {}: : Request failed", + url + )); + } + let response = response.unwrap().json(); + if response.is_err() { + return Status::error(format!( + "Failed to get template from {}: JSON parse error", + url + )); + } + let response: serde_json::Value = response.unwrap(); + let items = response.as_array().unwrap(); + + for item in items { + if item["contentType"] == "tree" { + let st = load_remote_gitlab_template_dir( + format!("{}/{}", path, item["name"]) + .replace('"', "") + .as_str(), + format!("{}/{}", url, item["name"]) + .replace('"', "") + .as_str(), + force, + ); + if !st.is_ok { + return st; + } + continue; + } + + let base_url = url.split("/tree").next().unwrap_or(""); + + if base_url.is_empty() { + return Status::error(format!("Invalid url: {}\n", url)); + } + + load_remote_gitlab_template_file( + format!("{}/{}", path, item["name"]) + .replace('"', "") + .as_str(), + format!("{}/blobs/{}", base_url, item["id"]) + .replace('"', "") + .as_str(), + force, + ); + } + Status::ok() +} + /// Load a file from a remote repository fn load_remote_template_file(path: &str, url: &str, force: bool) -> Status { if Path::new(path).exists() && !force { @@ -379,6 +486,76 @@ fn load_remote_template_file(path: &str, url: &str, force: bool) -> Status { Status::ok() } +/// Load a file from gitlab remote repository +fn load_remote_gitlab_template_file(path: &str, url: &str, force: bool) -> Status { + if Path::new(path).exists() && !force { + return Status::error(format!( + "File {} already exists...", + path.replace(".templates/", "") + )); + } + + let response = rest::json_call(url); + if response.is_err() { + return Status::error(format!( + "Failed to get template from {}: Request failed", + url + )); + } + let response = response.unwrap().json(); + if response.is_err() { + return Status::error(format!( + "Failed to get template from {}: JSON parse error", + url + )); + } + let response: serde_json::Value = response.unwrap(); + + let content = response["content"].as_str(); + let encoding = response["encoding"].as_str(); + + if encoding.unwrap_or("") != "base64" || content.is_none() { + return Status::error(format!( + "Failed to get template from {}: Decoding Error", + url + )); + } + + let mut text = match STANDARD.decode(content.unwrap()) { + Ok(decoded) => { + // Convert the decoded bytes to a string + match String::from_utf8(decoded) { + Ok(message) => message, + Err(_e) => { + return Status::error(format!( + "Failed to get template from {}: Decoding Error", + url + )) + } + } + } + Err(_e) => { + return Status::error(format!( + "Failed to get template from {}: Decoding Error", + url + )) + } + }; + + text = text.replace("\\n", "\n"); + + // create all subdirs if they don't exist + let path_dir = path.split('/').collect::>(); + let path_dir = path_dir[..path_dir.len() - 1].join("/"); + std::fs::create_dir_all(path_dir.clone()).unwrap(); + + let mut new_file = std::fs::File::create(path).unwrap(); + new_file.write_all(text.as_bytes()).unwrap(); + + log!("Created file {}", path); + Status::ok() +} + /// Generate a template from a template pub(crate) fn generate_template_dir( path: &str, From 8c222c8d4708420f1d5339fb2ed63e699dc014e8 Mon Sep 17 00:00:00 2001 From: RounakJoshi09 Date: Wed, 11 Sep 2024 01:05:50 +0530 Subject: [PATCH 3/8] Load Collection Function Updated for Gitlab - 1.0.0 - 1.0.0 --- src/utils/template_handler.rs | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/src/utils/template_handler.rs b/src/utils/template_handler.rs index e5b879d..29b5580 100644 --- a/src/utils/template_handler.rs +++ b/src/utils/template_handler.rs @@ -140,13 +140,32 @@ pub(crate) fn load_remote_template_collection( } let response: serde_json::Value = response.unwrap(); - let status = match url_type { - URLType::GitHub => load_github_template(response, path, url, force), - URLType::GitLab => load_gitlab_template(response, path, url, force), + let items = match url_type { + URLType::GitHub => response["payload"]["tree"]["items"].as_array().unwrap(), + URLType::GitLab => response.as_array().unwrap(), }; - if !status.is_ok { - return status; + for item in items { + let check_collection = match url_type { + URLType::GitHub => item["contentType"] == "directory", + URLType::GitLab => item["type"] == "tree", + }; + + if check_collection { + let st = load_remote_template( + format!("{}/{}", path, item["name"]) + .replace('"', "") + .as_str(), + format!("{}/{}", url, item["name"]) + .replace('"', "") + .as_str(), + force, + &url_type, + ); + if !st.is_ok { + return st; + } + } } Status::ok() From a4c9298bdaa08e541ce284ff78fdb5f5057eb6ed Mon Sep 17 00:00:00 2001 From: RounakJoshi09 Date: Wed, 11 Sep 2024 01:31:19 +0530 Subject: [PATCH 4/8] Code Refactoring - 1.0.0 --- src/commands/load.rs | 7 +------ src/utils/template_handler.rs | 22 +++++++++------------- 2 files changed, 10 insertions(+), 19 deletions(-) diff --git a/src/commands/load.rs b/src/commands/load.rs index 169a762..4318238 100644 --- a/src/commands/load.rs +++ b/src/commands/load.rs @@ -5,6 +5,7 @@ use crate::types::flag::Flag; use crate::types::status::Status; use crate::utils; +/// This enum is used to define type of URL (.i.e.. GitHub, GitLab) pub(crate) enum URLType { GitHub, GitLab, @@ -51,12 +52,6 @@ pub(crate) fn load(command: &Command) -> Status { } let url = command.get_argument("url").value.clone(); - // if !url.starts_with("https://github.com") && !url.starts_with("https://gitlab.com") { - // return Status::error(format!( - // "Invalid url: {}\nOnly templates from GitHub and Gitlab are supported at the moment.", - // url - // )); - // } let url_type = if url.starts_with("https://github.com") { URLType::GitHub diff --git a/src/utils/template_handler.rs b/src/utils/template_handler.rs index 29b5580..df40ca6 100644 --- a/src/utils/template_handler.rs +++ b/src/utils/template_handler.rs @@ -1,10 +1,9 @@ -use base64::{engine::general_purpose::STANDARD, Engine as _}; - use crate::commands::load::URLType; use crate::log; use crate::types::status::Status; use crate::types::template_meta::TemplateMeta; use crate::utils::formater; +use base64::{engine::general_purpose::STANDARD, Engine as _}; use std::io::Write; use std::path::Path; @@ -541,18 +540,15 @@ fn load_remote_gitlab_template_file(path: &str, url: &str, force: bool) -> Statu } let mut text = match STANDARD.decode(content.unwrap()) { - Ok(decoded) => { - // Convert the decoded bytes to a string - match String::from_utf8(decoded) { - Ok(message) => message, - Err(_e) => { - return Status::error(format!( - "Failed to get template from {}: Decoding Error", - url - )) - } + Ok(decoded) => match String::from_utf8(decoded) { + Ok(message) => message, + Err(_e) => { + return Status::error(format!( + "Failed to get template from {}: Decoding Error", + url + )) } - } + }, Err(_e) => { return Status::error(format!( "Failed to get template from {}: Decoding Error", From 3dff8b03b0444124d7ac54e723357f5165e61a76 Mon Sep 17 00:00:00 2001 From: RounakJoshi09 Date: Sun, 13 Oct 2024 12:21:04 +0530 Subject: [PATCH 5/8] Test Created for Gitlab Feature - 1.0.0 --- src/commands/load.rs | 2 +- src/utils/template_handler.rs | 2 +- tests/command_tests/load_test.rs | 95 ++++++++++++++++++++++++++++++++ 3 files changed, 97 insertions(+), 2 deletions(-) diff --git a/src/commands/load.rs b/src/commands/load.rs index 4318238..397329f 100644 --- a/src/commands/load.rs +++ b/src/commands/load.rs @@ -23,7 +23,7 @@ pub(crate) fn definition() -> Command { "url".to_string(), 0, true, - "The url of the github repository.".to_string(), + "The url of the github or gitlab repository.".to_string(), )); load_command.add_flag(Flag::new_bool_flag( diff --git a/src/utils/template_handler.rs b/src/utils/template_handler.rs index df40ca6..be9eaaa 100644 --- a/src/utils/template_handler.rs +++ b/src/utils/template_handler.rs @@ -423,7 +423,7 @@ fn load_remote_gitlab_template_dir(path: &str, url: &str, force: bool) -> Status let items = response.as_array().unwrap(); for item in items { - if item["contentType"] == "tree" { + if item["type"] == "tree" { let st = load_remote_gitlab_template_dir( format!("{}/{}", path, item["name"]) .replace('"', "") diff --git a/tests/command_tests/load_test.rs b/tests/command_tests/load_test.rs index f78b4c0..143c9ca 100644 --- a/tests/command_tests/load_test.rs +++ b/tests/command_tests/load_test.rs @@ -3,6 +3,44 @@ include!("../common/fs.rs"); include!("../common/log.rs"); pub fn test() { + check_github_load(); + check_gitlab_load(); +} + +pub fn check_gitlab_load() { + utils::init_tpy(); + + utils::run_successfully("tpy load https://gitlab.com/api/v4/projects/cophilot%2Ftemplify-vault/repository/tree?path=Test"); + check_gitlab_test_structure(); + utils::run_failure("tpy load https://gitlab.com/api/v4/projects/cophilot%2Ftemplify-vault/repository/tree?path=Test"); + + // check -force flag + utils::run_successfully( + "tpy load https://gitlab.com/api/v4/projects/cophilot%2Ftemplify-vault/repository/tree?path=Test -f", + ); + check_gitlab_test_structure(); + + utils::reset_dir(); + utils::init_tpy(); + + // check -template flag + utils::run_successfully( + "tpy load https://gitlab.com/api/v4/projects/cophilot%2Ftemplify-vault/repository/tree?path=Test/Test1 -t", + ); + check_gitlab_test_1_structure(); + fs::templates_dir().dir("Test2").check_not_exists(); + + utils::reset_dir(); + utils::init_tpy(); + + utils::run_successfully( + "tpy load https://gitlab.com/api/v4/projects/cophilot%2Ftemplify-vault/repository/tree?path=Test/Test2 -t", + ); + check_gitlab_test_2_structure(); + fs::templates_dir().dir("Test1").check_not_exists(); +} + +pub fn check_github_load() { utils::init_tpy(); utils::run_successfully("tpy load https://github.com/cophilot/templify-vault/tree/main/Test"); @@ -33,6 +71,14 @@ pub fn test() { ); check_test_2_structure(); fs::templates_dir().dir("Test1").check_not_exists(); + + utils::reset_dir(); +} + +pub fn check_gitlab_test_structure() { + check_gitlab_test_1_structure(); + check_gitlab_test_2_structure(); + check_gitlab_my_test_structure(); } pub fn check_test_structure() { @@ -89,3 +135,52 @@ pub fn check_my_test_structure() { ".source:https://github.com/cophilot/templify-vault/tree/main/Test/MyTest", ); } + +pub fn check_gitlab_test_1_structure() { + fs::templates_dir() + .dir("Test1") + .file("Test1$$name$$.txt") + .contains_string("$$name$$") + .contains_string( + "Paddington loves to eat marmalade sandwiches and he is a very polite bear.", + ); + fs::templates_dir() + .dir("Test1") + .file(".templify") + .contains_string("description:This is used to test templify") + .contains_string("path:src") + .contains_string(".source:https://gitlab.com/api/v4/projects/cophilot%2Ftemplify-vault/repository/tree?path=Test/Test1"); +} + +pub fn check_gitlab_test_2_structure() { + let mut base = fs::templates_dir().dir("Test2"); + + base.file(".templify") + .contains_string("description:This is used to test templify") + .contains_string("path:src") + .contains_string(".source:https://gitlab.com/api/v4/projects/cophilot%2Ftemplify-vault/repository/tree?path=Test/Test2"); + base.file("file.txt") + .contains_string("A elephant can eat 300 pounds of food in a day."); + base.dir("subdir") + .file("file.txt") + .contains_string("Apollo 11 started its journey to the moon on July 16, 1969."); + base.dir("subdir") + .dir("subdir") + .file(".tpykeep") + .check_all_exists(); +} + +pub fn check_gitlab_my_test_structure() { + fs::templates_dir() + .dir("MyTest") + .file("file.txt") + .contains_string("Nebraska has the largest indoor rainforest in the world."); + fs::templates_dir() + .dir("MyTest") + .file(".templify") + .contains_string("description:This is used to test templify") + .contains_string("path:src") + .contains_string( + ".source:https://gitlab.com/api/v4/projects/cophilot%2Ftemplify-vault/repository/tree?path=Test/MyTest", + ); +} From a5e3e0595bcc83dbde743d6bc45b65a7ddb0b64c Mon Sep 17 00:00:00 2001 From: RounakJoshi09 Date: Fri, 1 Nov 2024 20:21:09 +0530 Subject: [PATCH 6/8] Code Refactored - 1.0.0 --- .gitignore | 3 +- Cargo.lock | 39 +++++ Cargo.toml | 3 +- src/commands/init.rs | 2 - src/commands/load.rs | 15 +- src/utils/template_handler.rs | 248 +++++++++++++++---------------- tests/command_tests/load_test.rs | 2 + 7 files changed, 163 insertions(+), 149 deletions(-) diff --git a/.gitignore b/.gitignore index 4b8b7d8..16eb10c 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ ideas.md templify-example dev/ -tpy-test-dir \ No newline at end of file +tpy-test-dir +command.md \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 45d3996..427f4ae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,15 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + [[package]] name = "android-tzdata" version = "0.1.1" @@ -671,6 +680,35 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + [[package]] name = "reqwest" version = "0.12.2" @@ -915,6 +953,7 @@ version = "1.0.0" dependencies = [ "base64", "chrono", + "regex", "reqwest", "self-replace", "serde_json", diff --git a/Cargo.toml b/Cargo.toml index 3c42053..dadbe98 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,4 +13,5 @@ reqwest = { version = "0.12", features = ["blocking", "json"] } self-replace = "1.3.6" serde_json = "1.0.1" chrono = "0.4.19" -base64 = "0.21.7" \ No newline at end of file +base64 = "0.21.7" +regex = "1.11.1" diff --git a/src/commands/init.rs b/src/commands/init.rs index 4b52a61..a281246 100644 --- a/src/commands/init.rs +++ b/src/commands/init.rs @@ -1,4 +1,3 @@ -use crate::commands::load::URLType; use crate::log; use crate::types::command::Command; use crate::types::flag::Flag; @@ -53,7 +52,6 @@ pub(crate) fn init(command: &Command) -> Status { ".templates", "https://github.com/cophilot/templify-vault/tree/main/Example", true, - &URLType::GitHub, ); } log!("templify initialized successfully."); diff --git a/src/commands/load.rs b/src/commands/load.rs index 397329f..c268ddb 100644 --- a/src/commands/load.rs +++ b/src/commands/load.rs @@ -6,6 +6,7 @@ use crate::types::status::Status; use crate::utils; /// This enum is used to define type of URL (.i.e.. GitHub, GitLab) +#[derive(Clone)] pub(crate) enum URLType { GitHub, GitLab, @@ -53,17 +54,6 @@ pub(crate) fn load(command: &Command) -> Status { let url = command.get_argument("url").value.clone(); - let url_type = if url.starts_with("https://github.com") { - URLType::GitHub - } else if url.starts_with("https://gitlab.com") { - URLType::GitLab - } else { - return Status::error(format!( - "Invalid url: {}\nOnly templates from GitHub and Gitlab are supported at the moment.", - url - )); - }; - let load_template = command.get_bool_flag("template"); if load_template { log!("Loading template from {}...", url); @@ -72,7 +62,7 @@ pub(crate) fn load(command: &Command) -> Status { format!(".templates/{}", name).as_str(), url.as_str(), command.get_bool_flag("force"), - &url_type, + None, ); if !st.is_ok { return st; @@ -83,7 +73,6 @@ pub(crate) fn load(command: &Command) -> Status { ".templates", url.as_str(), command.get_bool_flag("force"), - &url_type, ); if !st.is_ok { return st; diff --git a/src/utils/template_handler.rs b/src/utils/template_handler.rs index be9eaaa..2c52efd 100644 --- a/src/utils/template_handler.rs +++ b/src/utils/template_handler.rs @@ -4,6 +4,7 @@ use crate::types::status::Status; use crate::types::template_meta::TemplateMeta; use crate::utils::formater; use base64::{engine::general_purpose::STANDARD, Engine as _}; +use regex::Regex; use std::io::Write; use std::path::Path; @@ -84,22 +85,11 @@ pub(crate) fn reload_template(name: String, strict: bool, reset: bool) -> Status let url = meta.get_source(); - let url_type = if url.starts_with("https://github.com") { - URLType::GitHub - } else if url.starts_with("https://gitlab.com") { - URLType::GitLab - } else { - return Status::error(format!( - "Invalid url: {}\nOnly templates from GitHub and Gitlab are supported at the moment.", - url - )); - }; - let st = load_remote_template( format!(".templates/{}", name).as_str(), url.as_str(), true, - &url_type, + None, ); if !st.is_ok { if reset { @@ -117,12 +107,12 @@ pub(crate) fn reload_template(name: String, strict: bool, reset: bool) -> Status } /// Load a collection of templates from a remote repository -pub(crate) fn load_remote_template_collection( - path: &str, - url: &str, - force: bool, - url_type: &URLType, -) -> Status { +pub(crate) fn load_remote_template_collection(path: &str, url: &str, force: bool) -> Status { + let url_type = match determine_url_type(url) { + Ok(url_type) => url_type, + Err(st) => return st, + }; + let response = rest::json_call(url); if response.is_err() { return Status::error(format!( @@ -159,7 +149,7 @@ pub(crate) fn load_remote_template_collection( .replace('"', "") .as_str(), force, - &url_type, + Some(&url_type), ); if !st.is_ok { return st; @@ -170,104 +160,12 @@ pub(crate) fn load_remote_template_collection( Status::ok() } -/// Load a template from a gitlab repository -pub(crate) fn load_gitlab_template( - response: serde_json::Value, - path: &str, - url: &str, - force: bool, -) -> Status { - let items = response.as_array().unwrap(); - - for item in items { - if item["type"] == "tree" { - let st = load_remote_gitlab_template_dir( - format!("{}/{}", path, item["name"]) - .replace('"', "") - .as_str(), - format!("{}/{}", url, item["name"]) - .replace('"', "") - .as_str(), - force, - ); - if !st.is_ok { - return st; - } - continue; - } - - let base_url = url.split("/tree").next().unwrap_or(""); - - if base_url.is_empty() { - return Status::error(format!("Invalid url: {}\n", url)); - } - - let st = load_remote_gitlab_template_file( - format!("{}/{}", path, item["name"]) - .replace('"', "") - .as_str(), - format!("{}/blobs/{}", base_url, item["id"]) - .replace('"', "") - .as_str(), - force, - ); - if !st.is_ok { - return st; - } - } - - return Status::ok(); -} - -/// Load a template from a github repository -pub(crate) fn load_github_template( - response: serde_json::Value, - path: &str, - url: &str, - force: bool, -) -> Status { - let items = response["payload"]["tree"]["items"].as_array().unwrap(); - - for item in items { - if item["contentType"] == "directory" { - let st = load_remote_template_dir( - format!("{}/{}", path, item["name"]) - .replace('"', "") - .as_str(), - format!("{}/{}", url, item["name"]) - .replace('"', "") - .as_str(), - force, - ); - if !st.is_ok { - return st; - } - continue; - } - - let st = load_remote_template_file( - format!("{}/{}", path, item["name"]) - .replace('"', "") - .as_str(), - format!("{}/{}", url, item["name"]) - .replace('"', "") - .as_str(), - force, - ); - if !st.is_ok { - return st; - } - } - - return Status::ok(); -} - /// Load a template from a remote repository pub(crate) fn load_remote_template( path: &str, url: &str, force: bool, - url_type: &URLType, + url_type: Option<&URLType>, ) -> Status { if !force && Path::new(path).exists() { return Status::error(format!( @@ -276,6 +174,14 @@ pub(crate) fn load_remote_template( )); } + let url_type_info: URLType = match url_type { + Some(ut) => ut.clone(), + None => match determine_url_type(url) { + Ok(ut) => ut, + Err(st) => return st, + }, + }; + if !Path::new(path).exists() { std::fs::create_dir(path).unwrap(); } @@ -296,7 +202,7 @@ pub(crate) fn load_remote_template( } let response: serde_json::Value = response.unwrap(); - let status = match url_type { + let status = match url_type_info { URLType::GitHub => load_github_template(response, path, url, force), URLType::GitLab => load_gitlab_template(response, path, url, force), }; @@ -333,6 +239,64 @@ pub(crate) fn load_remote_template( Status::ok() } +/// Load a template from a gitlab repository +fn load_gitlab_template(response: serde_json::Value, path: &str, url: &str, force: bool) -> Status { + let items = response.as_array().unwrap(); + + for item in items { + let formatted_path = &format_path_or_url(path, item); + let formatted_url = &format_path_or_url(url, item); + + if item["type"] == "tree" { + let st = load_remote_gitlab_template_dir(formatted_path, formatted_url, force); + if !st.is_ok { + return st; + } + continue; + } + + let base_url = url.split("/tree").next().unwrap_or(""); + + if base_url.is_empty() { + return Status::error(format!("Invalid url: {}\n", url)); + } + + let formatted_blob_url = &format_blob_url(base_url, &item); + + let st = load_remote_gitlab_template_file(formatted_path, formatted_blob_url, force); + if !st.is_ok { + return st; + } + } + + return Status::ok(); +} + +/// Load a template from a github repository +fn load_github_template(response: serde_json::Value, path: &str, url: &str, force: bool) -> Status { + let items = response["payload"]["tree"]["items"].as_array().unwrap(); + + for item in items { + let formatted_path = &format_path_or_url(path, item); + let formatted_url = &format_path_or_url(url, item); + + if item["contentType"] == "directory" { + let st = load_remote_template_dir(formatted_path, formatted_url, force); + if !st.is_ok { + return st; + } + continue; + } + + let st = load_remote_template_file(formatted_path, formatted_url, force); + if !st.is_ok { + return st; + } + } + + return Status::ok(); +} + /// Load a directory from a remote repository fn load_remote_template_dir(path: &str, url: &str, force: bool) -> Status { if !force && Path::new(path).exists() { @@ -408,7 +372,7 @@ fn load_remote_gitlab_template_dir(path: &str, url: &str, force: bool) -> Status let response = rest::json_call(url); if response.is_err() { return Status::error(format!( - "Failed to get template from {}: : Request failed", + "Failed to get template from {}: Request failed", url )); } @@ -423,16 +387,11 @@ fn load_remote_gitlab_template_dir(path: &str, url: &str, force: bool) -> Status let items = response.as_array().unwrap(); for item in items { + let formatted_path = &format_path_or_url(path, item); + let formatted_url = &format_path_or_url(url, item); + if item["type"] == "tree" { - let st = load_remote_gitlab_template_dir( - format!("{}/{}", path, item["name"]) - .replace('"', "") - .as_str(), - format!("{}/{}", url, item["name"]) - .replace('"', "") - .as_str(), - force, - ); + let st = load_remote_gitlab_template_dir(formatted_path, formatted_url, force); if !st.is_ok { return st; } @@ -444,16 +403,9 @@ fn load_remote_gitlab_template_dir(path: &str, url: &str, force: bool) -> Status if base_url.is_empty() { return Status::error(format!("Invalid url: {}\n", url)); } + let formatted_blob_url = &format_blob_url(base_url, &item); - load_remote_gitlab_template_file( - format!("{}/{}", path, item["name"]) - .replace('"', "") - .as_str(), - format!("{}/blobs/{}", base_url, item["id"]) - .replace('"', "") - .as_str(), - force, - ); + load_remote_gitlab_template_file(formatted_path, formatted_blob_url, force); } Status::ok() } @@ -657,3 +609,35 @@ pub(crate) fn generate_template_file( log!("Created file {}", abs_path.to_str().unwrap()); true } + +/// Format Path for loading Gitlab Template +fn format_path_or_url(path_or_url: &str, item: &serde_json::Value) -> String { + return format!("{}/{}", path_or_url, item["name"].as_str().unwrap_or("")).replace('"', ""); +} + +/// Format URL for loading Gitlab File Blob +fn format_blob_url(base_url: &str, item: &serde_json::Value) -> String { + return format!("{}/blobs/{}", base_url, item["id"].as_str().unwrap_or("")).replace('"', ""); +} + +/// Check for valid gitlab url +fn is_valid_gitlab_url(url: &str) -> bool { + let gitlab_url_pattern = Regex::new(r"^https:\/\/(?:[\w-]+\.)*gitlab\.com(?:\/.*)?$").unwrap(); + gitlab_url_pattern.is_match(url) +} + +/// Determine the URL Type (Gitlab , Github) from the url +fn determine_url_type(url: &str) -> Result { + let url_type = if url.starts_with("https://github.com") { + URLType::GitHub + } else if is_valid_gitlab_url(url) { + URLType::GitLab + } else { + return Err(Status::error(format!( + "Invalid url: {}\nOnly templates from GitHub and Gitlab are supported at the moment.", + url + ))); + }; + + Ok(url_type) +} diff --git a/tests/command_tests/load_test.rs b/tests/command_tests/load_test.rs index 143c9ca..bb54863 100644 --- a/tests/command_tests/load_test.rs +++ b/tests/command_tests/load_test.rs @@ -73,6 +73,8 @@ pub fn check_github_load() { fs::templates_dir().dir("Test1").check_not_exists(); utils::reset_dir(); + + utils::run_failure("tpy load https://my-gitlab.company.com"); } pub fn check_gitlab_test_structure() { From 19d19b28ea8941ec8567091b79a653e5837d5993 Mon Sep 17 00:00:00 2001 From: RounakJoshi09 Date: Fri, 1 Nov 2024 21:49:28 +0530 Subject: [PATCH 7/8] Linting and Comment Check done - 1.0.0 - 1.0.0 --- src/utils/template_handler.rs | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/utils/template_handler.rs b/src/utils/template_handler.rs index 2c52efd..26c64a8 100644 --- a/src/utils/template_handler.rs +++ b/src/utils/template_handler.rs @@ -261,7 +261,7 @@ fn load_gitlab_template(response: serde_json::Value, path: &str, url: &str, forc return Status::error(format!("Invalid url: {}\n", url)); } - let formatted_blob_url = &format_blob_url(base_url, &item); + let formatted_blob_url = &format_blob_url(base_url, item); let st = load_remote_gitlab_template_file(formatted_path, formatted_blob_url, force); if !st.is_ok { @@ -269,7 +269,7 @@ fn load_gitlab_template(response: serde_json::Value, path: &str, url: &str, forc } } - return Status::ok(); + Status::ok() } /// Load a template from a github repository @@ -294,7 +294,7 @@ fn load_github_template(response: serde_json::Value, path: &str, url: &str, forc } } - return Status::ok(); + Status::ok() } /// Load a directory from a remote repository @@ -357,6 +357,7 @@ fn load_remote_template_dir(path: &str, url: &str, force: bool) -> Status { Status::ok() } +/// Load Gitlab Template Directory fn load_remote_gitlab_template_dir(path: &str, url: &str, force: bool) -> Status { if !force && Path::new(path).exists() { return Status::error(format!( @@ -403,7 +404,7 @@ fn load_remote_gitlab_template_dir(path: &str, url: &str, force: bool) -> Status if base_url.is_empty() { return Status::error(format!("Invalid url: {}\n", url)); } - let formatted_blob_url = &format_blob_url(base_url, &item); + let formatted_blob_url = &format_blob_url(base_url, item); load_remote_gitlab_template_file(formatted_path, formatted_blob_url, force); } @@ -612,18 +613,12 @@ pub(crate) fn generate_template_file( /// Format Path for loading Gitlab Template fn format_path_or_url(path_or_url: &str, item: &serde_json::Value) -> String { - return format!("{}/{}", path_or_url, item["name"].as_str().unwrap_or("")).replace('"', ""); + format!("{}/{}", path_or_url, item["name"].as_str().unwrap_or("")).replace('"', "") } /// Format URL for loading Gitlab File Blob fn format_blob_url(base_url: &str, item: &serde_json::Value) -> String { - return format!("{}/blobs/{}", base_url, item["id"].as_str().unwrap_or("")).replace('"', ""); -} - -/// Check for valid gitlab url -fn is_valid_gitlab_url(url: &str) -> bool { - let gitlab_url_pattern = Regex::new(r"^https:\/\/(?:[\w-]+\.)*gitlab\.com(?:\/.*)?$").unwrap(); - gitlab_url_pattern.is_match(url) + format!("{}/blobs/{}", base_url, item["id"].as_str().unwrap_or("")).replace('"', "") } /// Determine the URL Type (Gitlab , Github) from the url @@ -641,3 +636,9 @@ fn determine_url_type(url: &str) -> Result { Ok(url_type) } + +/// Check for valid gitlab url +fn is_valid_gitlab_url(url: &str) -> bool { + let gitlab_url_pattern = Regex::new(r"^https:\/\/(?:[\w-]+\.)*gitlab\.com(?:\/.*)?$").unwrap(); + gitlab_url_pattern.is_match(url) +} From 94b725075d7096a5629f50e3286254d9370df3d2 Mon Sep 17 00:00:00 2001 From: RounakJoshi09 Date: Sun, 3 Nov 2024 12:24:14 +0530 Subject: [PATCH 8/8] url Type Extracted - 1.0.0 --- src/commands/load.rs | 7 ------- src/types/load_types.rs | 6 ++++++ src/types/mod.rs | 1 + src/utils/template_handler.rs | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) create mode 100644 src/types/load_types.rs diff --git a/src/commands/load.rs b/src/commands/load.rs index c268ddb..46ce5c8 100644 --- a/src/commands/load.rs +++ b/src/commands/load.rs @@ -5,13 +5,6 @@ use crate::types::flag::Flag; use crate::types::status::Status; use crate::utils; -/// This enum is used to define type of URL (.i.e.. GitHub, GitLab) -#[derive(Clone)] -pub(crate) enum URLType { - GitHub, - GitLab, -} - /// The definition of the load command. pub(crate) fn definition() -> Command { let mut load_command = Command::new( diff --git a/src/types/load_types.rs b/src/types/load_types.rs new file mode 100644 index 0000000..92e4646 --- /dev/null +++ b/src/types/load_types.rs @@ -0,0 +1,6 @@ +/// This enum is used to define type of URL (.i.e.. GitHub, GitLab) +#[derive(Clone)] +pub(crate) enum URLType { + GitHub, + GitLab, +} diff --git a/src/types/mod.rs b/src/types/mod.rs index 0d2a42f..384c641 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -2,6 +2,7 @@ pub mod argument; pub mod command; pub mod flag; pub mod global_flag; +pub mod load_types; pub mod status; pub mod template_meta; pub mod var_placeholder; diff --git a/src/utils/template_handler.rs b/src/utils/template_handler.rs index 26c64a8..87fbc39 100644 --- a/src/utils/template_handler.rs +++ b/src/utils/template_handler.rs @@ -1,5 +1,5 @@ -use crate::commands::load::URLType; use crate::log; +use crate::types::load_types::URLType; use crate::types::status::Status; use crate::types::template_meta::TemplateMeta; use crate::utils::formater;