From 45c99278d92b870729e551eabce4030bb4cbd688 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Qian=20Qian=20=22Cubik=22=E2=80=8E?= Date: Fri, 12 Jul 2024 20:52:57 -0400 Subject: [PATCH 01/12] docs(dev/design): Add Article Template Definition MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Qian Qian "Cubik"‎ --- docs/dev/design/repo-rule.md | 57 +++++++++++++++++++++++++++++++----- 1 file changed, 50 insertions(+), 7 deletions(-) diff --git a/docs/dev/design/repo-rule.md b/docs/dev/design/repo-rule.md index 0e7e618..874e26d 100644 --- a/docs/dev/design/repo-rule.md +++ b/docs/dev/design/repo-rule.md @@ -20,19 +20,59 @@ Here's how the exact rule file is defined. The repository rule file will be written to a TOML file, following the structure below: ```toml +# The article template to use when creating a new source file for an article. +article_template = """--- +title: {{title}} +date: <发布时间> +author: + - fosscope-translation-team + - {{translator}} + - {{proofreader}} +banner: {{cover_image}} +cover: {{cover_image}} +categories: + - 翻译 + - <类型> +tags: + - <标签> +authorInfo: | + via: {{via}} + + 作者:[{{author}}]({{author_link}}) + 选题:[{{selector}}](https://github.com/{{selector}}) + 译者:[{{translator}}](https://github.com/{{translator}}) + 校对:[{{proofreader}}](https://github.com/{{proofreader}}) + + 本文由 [FOSScope翻译组](https://github.com/FOSScope/TranslateProject) 原创编译,[开源观察](https://fosscope.com/) 荣誉推出 +--- + + + +{{summary}} + + + +{{content}} +""" + + [[articles]] # Each `[[articles]]` block defines a type of article available to contribute to. type = "news" # The type of article. description = "News Articles" # The description of the article type. -directory = "{step}/news" # The directory where the articles of this type are stored. +directory = "{{step}}/news" # The directory where the articles of this type are stored. # `{step}` is the placeholder for the directory where the article will be moved from # step to step (e.g. "source", "translated", "published", etc.) +# If needed, a specific article template can be defined for this article type. +# Otherwise, the default article template will be used. +# template = """ +# """ # Multiple article types can be defined. [[articles]] type = "tech" description = "Tech Articles" -directory = "{step}/tech" +directory = "{{step}}/tech" # [[articles]] # ... @@ -41,7 +81,7 @@ directory = "{step}/tech" # Each `[[actions]]` block defines an action that can be made in the contribution process. action = "select" # The action name. description = "Select an article to translate." # The description of the action. -command = "TOUCH source/{article}.md" # The command to execute when the action is made. +command = "TOUCH source/{{article_id}}.md" # The command to execute when the action is made. # The command follows a *nix shell command syntax, but is defined, parsed, and executed by the core component of Toolkit software. # In this case, {article} is the placeholder for the article name. @@ -49,7 +89,7 @@ command = "TOUCH source/{article}.md" # The command to execute when the action [[actions]] action = "translate" description = "Translate the article." -command = "MV source/{article}.md translated/{article}.md" +command = "MV source/{{article_id}}.md translated/{{article_id}}.md" # [[actions]] # ... @@ -58,9 +98,12 @@ command = "MV source/{article}.md translated/{article}.md" # This section defines how git conventions applies in different steps. # `{action}`, `{type}`, and `{article}` are placeholders for the action's name, article type, and article name respectively. # Other placeholders can be used as well. -branch_naming = "{action}/{type}/{article}" # The branch naming rule. -commit_message = "[{action.desc}][{type.desc}]: {article.title}" # The commit message rule. +branch_naming = "{{action_name}}/{{type_name}}/{{article_id}}" # The branch naming rule. +commit_message = "[{{action_desc}}][{{type_desc}}]: {{article_title}}" # The commit message rule. ``` > [!NOTE] -> In general, placeholders like `{step}` can be used anywhere, and placerholders other than what's shown in the example above can be defined and used as well. +> +> In general, placeholders like `{{title}}` can be used anywhere, and the template engine will replace them with the actual value when generating the file. +> +> Place holder other than what's shown above may be defined and used. From 8a2dcc4550a495c534b9470bce0b9d7173bd2de1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Qian=20Qian=20=22Cubik=22=E2=80=8E?= Date: Fri, 12 Jul 2024 21:46:53 -0400 Subject: [PATCH 02/12] refactor(core): Add Article Template definition in Repo Rule MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Qian Qian "Cubik"‎ --- docs/dev/design/repo-rule.md | 2 +- src-rust/toolkit-core/src/models/repo_rule.rs | 21 +++++++++++++------ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/docs/dev/design/repo-rule.md b/docs/dev/design/repo-rule.md index 874e26d..2971dcc 100644 --- a/docs/dev/design/repo-rule.md +++ b/docs/dev/design/repo-rule.md @@ -65,7 +65,7 @@ directory = "{{step}}/news" # The directory where the articles of this type ar # step to step (e.g. "source", "translated", "published", etc.) # If needed, a specific article template can be defined for this article type. # Otherwise, the default article template will be used. -# template = """ +# article_template = """ # """ # Multiple article types can be defined. diff --git a/src-rust/toolkit-core/src/models/repo_rule.rs b/src-rust/toolkit-core/src/models/repo_rule.rs index ba7d8c1..1f4ae3e 100644 --- a/src-rust/toolkit-core/src/models/repo_rule.rs +++ b/src-rust/toolkit-core/src/models/repo_rule.rs @@ -3,7 +3,9 @@ /// # Fields /// - `article_type`(`type` in TOML file): The type of the article. e.g. `news`, `tech`. /// - `description`: The description of the article type. e.g. `News Articles`, `Tech Articles`. -/// - `directory`: The directory where the article type is stored. e.g. `{step}/news`, `{step}/tech`. +/// - `directory`: The directory where the article type is stored. e.g. `{{step}}/news`, `{{step}}/tech`. +/// - `article_template`: An optional article template to use when creating a new source file for an article. +/// If not provided, the `article_template` from the [`RepoRule`](struct.RepoRule.html) will be used. /// /// Check the [related design documentation](https://github.com/FOSScope/Toolkit/blob/main/docs/dev/design/repo-rule.md) /// and [RepoRule](struct.RepoRule.html) definition for more information. @@ -16,14 +18,17 @@ pub struct Article { pub description: String, /// The directory where the article type is stored. pub directory: String, + /// An optional article template to use when creating a new source file for an article. + pub article_template: Option, } impl Article { - pub fn new(article_type: String, description: String, directory: String) -> Self { + pub fn new(article_type: String, description: String, directory: String, article_template: Option) -> Self { Self { article_type, description, directory, + article_template, } } } @@ -33,7 +38,7 @@ impl Article { /// # Fields /// - `action`: The action name. e.g. `select`, `translate`, `review`. /// - `description`: The description of the action. e.g. `Select an article to translate`. -/// - `command`: The command that should be executed when the action is performed. e.g. `TOUCH source/{article}.md`. +/// - `command`: The command that should be executed when the action is performed. e.g. `TOUCH source/{{article_id}}.md`. /// /// Check the [related design documentation](https://github.com/FOSScope/Toolkit/blob/main/docs/dev/design/repo-rule.md) /// and [RepoRule](struct.RepoRule.html) definition for more information. @@ -64,8 +69,8 @@ impl Action { /// - `commit_message`: The commit message template. Which is a string containing placeholders that will be replaced with the actual values. /// /// # Example -/// - `branch_naming`: `{action}/{type}/{article}` -/// - `commit_message`: `[{action.desc}][{type.desc}]: {article.title}` +/// - `branch_naming`: `{{action_name}}/{{type_name}}/{{article_id}}` +/// - `commit_message`: `[{{action_desc}}][{{type_desc}}]: {{article_title}}` /// /// Check the [related design documentation](https://github.com/FOSScope/Toolkit/blob/main/docs/dev/design/repo-rule.md) /// and [RepoRule](struct.RepoRule.html) definition for more information. @@ -91,6 +96,7 @@ impl GitRule { /// The rule includes a list of articles, a list of actions, and a Git rule. /// /// # Fields +/// - `article_template`(String): The article template to use when creating a new source file for an article. /// - `articles`([Article](struct.Article.html)): A list of types of articles that can be found in the repository. /// - `actions`([Action](struct.Action.html)): : A list of actions that can be performed on the repository. /// - `git`([GitRule](struct.GitRule.html)): The Git rule that defines how the repository should be managed. @@ -98,6 +104,8 @@ impl GitRule { /// Check the [related design documentation](https://github.com/FOSScope/Toolkit/blob/main/docs/dev/design/repo-rule.md) for more information. #[derive(PartialEq, Eq, Debug, serde::Deserialize)] pub struct RepoRule { + /// The article template to use when creating a new source file for an article. + pub article_template: String, /// The list of types of articles that can be found in the repository. pub articles: Vec
, /// The list of actions that can be performed on the repository. @@ -107,8 +115,9 @@ pub struct RepoRule { } impl RepoRule { - pub fn new(articles: Vec
, actions: Vec, git: GitRule) -> Self { + pub fn new(article_template: String, articles: Vec
, actions: Vec, git: GitRule) -> Self { Self { + article_template, articles, actions, git, From c557556ad84bb174642be0e6a071b8dad5e24bb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Qian=20Qian=20=22Cubik=22=E2=80=8E?= Date: Sat, 13 Jul 2024 17:03:23 -0400 Subject: [PATCH 03/12] test: Update Repo Rule Test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Qian Qian "Cubik"‎ --- src-rust/toolkit-core/tests/repo_rule_test.rs | 43 +++++++++++++------ 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/src-rust/toolkit-core/tests/repo_rule_test.rs b/src-rust/toolkit-core/tests/repo_rule_test.rs index f2c8251..4fcf5b2 100644 --- a/src-rust/toolkit-core/tests/repo_rule_test.rs +++ b/src-rust/toolkit-core/tests/repo_rule_test.rs @@ -1,29 +1,41 @@ #[cfg(test)] mod tests { use fosscopetoolkit_core::get_repo_rule; - use fosscopetoolkit_core::models::{Article, Action, GitRule, RepoRule}; + use fosscopetoolkit_core::models::repo_rule::{Article, Action, GitRule, RepoRule}; #[test] fn deserialize() { - let rule = r#"[[articles]] + let rule = r#"# The article template to use when creating a new source file for an article. +article_template = """--- +General Article Template +---""" + + +[[articles]] # Each `[[articles]]` block defines a type of article available to contribute to. type = "news" # The type of article. description = "News Articles" # The description of the article type. -directory = "{step}/news" # The directory where the articles of this type are stored. +directory = "{{step}}/news" # The directory where the articles of this type are stored. # `{step}` is the placeholder for the directory where the article will be moved from # step to step (e.g. "source", "translated", "published", etc.) +article_template = """--- +News Article Template +---""" # Multiple article types can be defined. [[articles]] type = "tech" description = "Tech Articles" -directory = "{step}/tech" +directory = "{{step}}/tech" + +# [[articles]] +# ... [[actions]] # Each `[[actions]]` block defines an action that can be made in the contribution process. action = "select" # The action name. description = "Select an article to translate." # The description of the action. -command = "TOUCH source/{article}.md" # The command to execute when the action is made. +command = "TOUCH source/{{article_id}}.md" # The command to execute when the action is made. # The command follows a *nix shell command syntax, but is defined, parsed, and executed by the core component of Toolkit software. # In this case, {article} is the placeholder for the article name. @@ -31,24 +43,27 @@ command = "TOUCH source/{article}.md" # The command to execute when the action [[actions]] action = "translate" description = "Translate the article." -command = "MV source/{article}.md translated/{article}.md" +command = "MV source/{{article_id}}.md translated/{{article_id}}.md" + +# [[actions]] +# ... [git] # This section defines how git conventions applies in different steps. # `{action}`, `{type}`, and `{article}` are placeholders for the action's name, article type, and article name respectively. # Other placeholders can be used as well. -branch_naming = "{action}/{type}/{article}" # The branch naming rule. -commit_message = "[{action.desc}][{type.desc}]: {article.title}" # The commit message rule."#; +branch_naming = "{{action_name}}/{{type_name}}/{{article_id}}" # The branch naming rule. +commit_message = "[{{action_desc}}][{{type_desc}}]: {{article_title}}" # The commit message rule."#; let deserialized = get_repo_rule(rule).unwrap(); - let news: Article = Article::new("news".to_string(), "News Articles".to_string(), "{step}/news".to_string()); - let tech: Article = Article::new("tech".to_string(), "Tech Articles".to_string(), "{step}/tech".to_string()); - let select: Action = Action::new("select".to_string(), "Select an article to translate.".to_string(), "TOUCH source/{article}.md".to_string()); - let translate: Action = Action::new("translate".to_string(), "Translate the article.".to_string(), "MV source/{article}.md translated/{article}.md".to_string()); - let git_rule: GitRule = GitRule::new("{action}/{type}/{article}".to_string(), "[{action.desc}][{type.desc}]: {article.title}".to_string()); + let news: Article = Article::new("news".to_string(), "News Articles".to_string(), "{{step}}/news".to_string(), Some("---\nNews Article Template\n---".to_string())); + let tech: Article = Article::new("tech".to_string(), "Tech Articles".to_string(), "{{step}}/tech".to_string(), None); + let select: Action = Action::new("select".to_string(), "Select an article to translate.".to_string(), "TOUCH source/{{article_id}}.md".to_string()); + let translate: Action = Action::new("translate".to_string(), "Translate the article.".to_string(), "MV source/{{article_id}}.md translated/{{article_id}}.md".to_string()); + let git_rule: GitRule = GitRule::new("{{action_name}}/{{type_name}}/{{article_id}}".to_string(), "[{{action_desc}}][{{type_desc}}]: {{article_title}}".to_string()); - let expected = RepoRule::new(vec![news, tech], vec![select, translate], git_rule); + let expected = RepoRule::new("---\nGeneral Article Template\n---".to_string(), vec![news, tech], vec![select, translate], git_rule); assert_eq!(deserialized, expected); } From b2b94f1dfc8fdf9cf6ba220b920004427a4bf711 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Qian=20Qian=20=22Cubik=22=E2=80=8E?= Date: Sat, 13 Jul 2024 17:23:43 -0400 Subject: [PATCH 04/12] refactor(core): Add Custom Deserializer for Action Commands MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Qian Qian "Cubik"‎ --- .../toolkit-core/src/models/action_command.rs | 32 +++++++++++++++++++ src-rust/toolkit-core/src/models/mod.rs | 1 + src-rust/toolkit-core/src/models/repo_rule.rs | 6 ++-- src-rust/toolkit-core/tests/repo_rule_test.rs | 9 ++++-- 4 files changed, 44 insertions(+), 4 deletions(-) create mode 100644 src-rust/toolkit-core/src/models/action_command.rs diff --git a/src-rust/toolkit-core/src/models/action_command.rs b/src-rust/toolkit-core/src/models/action_command.rs new file mode 100644 index 0000000..993cfed --- /dev/null +++ b/src-rust/toolkit-core/src/models/action_command.rs @@ -0,0 +1,32 @@ +#[derive(PartialEq, Eq, Debug)] +pub struct ActionCommand { + pub command: String, + pub args: Vec, +} + +impl<'de> serde::Deserialize<'de> for ActionCommand { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let content = String::deserialize(deserializer)?; + let mut parts = content.split_whitespace(); + + let command = parts.next().unwrap().to_string(); + let args = parts.map(|s| s.to_string()).collect(); + + Ok(Self { + command, + args, + }) + } +} + +impl ActionCommand { + pub fn new(command: String, args: Vec) -> Self { + Self { + command, + args, + } + } +} diff --git a/src-rust/toolkit-core/src/models/mod.rs b/src-rust/toolkit-core/src/models/mod.rs index 7e8cc33..f81f065 100644 --- a/src-rust/toolkit-core/src/models/mod.rs +++ b/src-rust/toolkit-core/src/models/mod.rs @@ -1,3 +1,4 @@ pub mod github_repo; pub mod repo_rule; pub mod html_filter_rule; +pub mod action_command; diff --git a/src-rust/toolkit-core/src/models/repo_rule.rs b/src-rust/toolkit-core/src/models/repo_rule.rs index 1f4ae3e..e773cf5 100644 --- a/src-rust/toolkit-core/src/models/repo_rule.rs +++ b/src-rust/toolkit-core/src/models/repo_rule.rs @@ -1,3 +1,5 @@ +use crate::models::action_command::ActionCommand; + /// A representation of an article type in the FOSScope repository rule, which defines the types of articles that can be found in the repository. /// /// # Fields @@ -49,11 +51,11 @@ pub struct Action { /// The description of the action. pub description: String, /// The command that should be executed when the action is performed. - pub command: String, + pub command: ActionCommand, } impl Action { - pub fn new(action: String, description: String, command: String) -> Self { + pub fn new(action: String, description: String, command: ActionCommand) -> Self { Self { action, description, diff --git a/src-rust/toolkit-core/tests/repo_rule_test.rs b/src-rust/toolkit-core/tests/repo_rule_test.rs index 4fcf5b2..f7f82b7 100644 --- a/src-rust/toolkit-core/tests/repo_rule_test.rs +++ b/src-rust/toolkit-core/tests/repo_rule_test.rs @@ -1,6 +1,7 @@ #[cfg(test)] mod tests { use fosscopetoolkit_core::get_repo_rule; + use fosscopetoolkit_core::models::action_command::ActionCommand; use fosscopetoolkit_core::models::repo_rule::{Article, Action, GitRule, RepoRule}; #[test] @@ -59,8 +60,12 @@ commit_message = "[{{action_desc}}][{{type_desc}}]: {{article_title}}" # The co let news: Article = Article::new("news".to_string(), "News Articles".to_string(), "{{step}}/news".to_string(), Some("---\nNews Article Template\n---".to_string())); let tech: Article = Article::new("tech".to_string(), "Tech Articles".to_string(), "{{step}}/tech".to_string(), None); - let select: Action = Action::new("select".to_string(), "Select an article to translate.".to_string(), "TOUCH source/{{article_id}}.md".to_string()); - let translate: Action = Action::new("translate".to_string(), "Translate the article.".to_string(), "MV source/{{article_id}}.md translated/{{article_id}}.md".to_string()); + let select: Action = Action::new("select".to_string(), "Select an article to translate.".to_string(), + ActionCommand::new("TOUCH".to_string(), vec!["source/{{article_id}}.md".to_string()]) + ); + let translate: Action = Action::new("translate".to_string(), "Translate the article.".to_string(), + ActionCommand::new("MV".to_string(), vec!["source/{{article_id}}.md".to_string(), "translated/{{article_id}}.md".to_string()]) + ); let git_rule: GitRule = GitRule::new("{{action_name}}/{{type_name}}/{{article_id}}".to_string(), "[{{action_desc}}][{{type_desc}}]: {{article_title}}".to_string()); let expected = RepoRule::new("---\nGeneral Article Template\n---".to_string(), vec![news, tech], vec![select, translate], git_rule); From 5756e9f08ca8d3083b83feacdf4a3d0a16f864de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Qian=20Qian=20=22Cubik=22=E2=80=8E?= Date: Sat, 13 Jul 2024 17:27:54 -0400 Subject: [PATCH 05/12] feat(core): Add some command definition to Action Command MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Qian Qian "Cubik"‎ --- .../toolkit-core/src/models/action_command.rs | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src-rust/toolkit-core/src/models/action_command.rs b/src-rust/toolkit-core/src/models/action_command.rs index 993cfed..24b55c6 100644 --- a/src-rust/toolkit-core/src/models/action_command.rs +++ b/src-rust/toolkit-core/src/models/action_command.rs @@ -29,4 +29,41 @@ impl ActionCommand { args, } } + + pub fn execute(&self) { + match self.command.as_str() { + // Copy a file or a directory + "CP" => { + let src = self.args.get(0).unwrap(); + let dest = self.args.get(1).unwrap(); + std::fs::copy(src, dest).unwrap(); + } + // Create a directory + "MKDIR" => { + let path = self.args.get(0).unwrap(); + std::fs::create_dir_all(path).unwrap(); + } + // Move a file or a directory + "MV" => { + let src = self.args.get(0).unwrap(); + let dest = self.args.get(1).unwrap(); + std::fs::rename(src, dest).unwrap(); + } + // Remove a file or a directory + "RM" => { + let path = self.args.get(0).unwrap(); + if std::fs::metadata(path).unwrap().is_dir() { + std::fs::remove_dir_all(path).unwrap(); + } else { + std::fs::remove_file(path).unwrap(); + } + } + // Create a file + "TOUCH" => { + let path = self.args.get(0).unwrap(); + std::fs::File::create(path).unwrap(); + } + _ => {} + } + } } From b02aa9637013db457a68ccc890cee1a070cbe4f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Qian=20Qian=20=22Cubik=22=E2=80=8E?= Date: Sat, 13 Jul 2024 17:29:16 -0400 Subject: [PATCH 06/12] feat(core): Add Undefined Action Command Handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Qian Qian "Cubik"‎ --- src-rust/toolkit-core/src/models/action_command.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src-rust/toolkit-core/src/models/action_command.rs b/src-rust/toolkit-core/src/models/action_command.rs index 24b55c6..b87b554 100644 --- a/src-rust/toolkit-core/src/models/action_command.rs +++ b/src-rust/toolkit-core/src/models/action_command.rs @@ -30,24 +30,27 @@ impl ActionCommand { } } - pub fn execute(&self) { + pub fn execute(&self) -> Result<(), &str> { match self.command.as_str() { // Copy a file or a directory "CP" => { let src = self.args.get(0).unwrap(); let dest = self.args.get(1).unwrap(); std::fs::copy(src, dest).unwrap(); + Ok(()) } // Create a directory "MKDIR" => { let path = self.args.get(0).unwrap(); std::fs::create_dir_all(path).unwrap(); + Ok(()) } // Move a file or a directory "MV" => { let src = self.args.get(0).unwrap(); let dest = self.args.get(1).unwrap(); std::fs::rename(src, dest).unwrap(); + Ok(()) } // Remove a file or a directory "RM" => { @@ -57,13 +60,15 @@ impl ActionCommand { } else { std::fs::remove_file(path).unwrap(); } + Ok(()) } // Create a file "TOUCH" => { let path = self.args.get(0).unwrap(); std::fs::File::create(path).unwrap(); + Ok(()) } - _ => {} + _ => Err("Unknown command"), } } } From 43f6a94124b83ecfd8562fa3f0dedb082cffe530 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Qian=20Qian=20=22Cubik=22=E2=80=8E?= Date: Sat, 13 Jul 2024 18:13:01 -0400 Subject: [PATCH 07/12] =?UTF-8?q?fix(core):=20Fix=20directory=20coping=20&?= =?UTF-8?q?=20Copy=20when=20dest=20dir=20doesn=E2=80=99t=20exist?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Qian Qian "Cubik"‎ --- .gitignore | 3 ++ .../toolkit-core/src/models/action_command.rs | 33 ++++++++++++++- .../toolkit-core/tests/action_command_test.rs | 40 +++++++++++++++++++ 3 files changed, 74 insertions(+), 2 deletions(-) create mode 100644 src-rust/toolkit-core/tests/action_command_test.rs diff --git a/.gitignore b/.gitignore index a547bf3..aec01c1 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,6 @@ dist-ssr *.njsproj *.sln *.sw? + +# Test +.test/ diff --git a/src-rust/toolkit-core/src/models/action_command.rs b/src-rust/toolkit-core/src/models/action_command.rs index b87b554..8723b5e 100644 --- a/src-rust/toolkit-core/src/models/action_command.rs +++ b/src-rust/toolkit-core/src/models/action_command.rs @@ -36,8 +36,23 @@ impl ActionCommand { "CP" => { let src = self.args.get(0).unwrap(); let dest = self.args.get(1).unwrap(); - std::fs::copy(src, dest).unwrap(); - Ok(()) + + let src_dir = std::path::Path::new(src); + if src_dir.is_dir() { + let r = copy_dir_all(src, dest); + match r { + Ok(_) => Ok(()), + Err(_) => Err("Error copying directory"), + } + } else { + let dest_dir = std::path::Path::new(dest); + if !dest_dir.exists() { + std::fs::create_dir_all(dest_dir.parent().unwrap()).unwrap(); + } + + std::fs::copy(src, dest).unwrap(); + Ok(()) + } } // Create a directory "MKDIR" => { @@ -72,3 +87,17 @@ impl ActionCommand { } } } + +fn copy_dir_all(src: impl AsRef, dst: impl AsRef) -> std::io::Result<()> { + std::fs::create_dir_all(&dst)?; + for entry in std::fs::read_dir(src)? { + let entry = entry?; + let ty = entry.file_type()?; + if ty.is_dir() { + copy_dir_all(entry.path(), dst.as_ref().join(entry.file_name()))?; + } else { + std::fs::copy(entry.path(), dst.as_ref().join(entry.file_name()))?; + } + } + Ok(()) +} diff --git a/src-rust/toolkit-core/tests/action_command_test.rs b/src-rust/toolkit-core/tests/action_command_test.rs new file mode 100644 index 0000000..d49fbc1 --- /dev/null +++ b/src-rust/toolkit-core/tests/action_command_test.rs @@ -0,0 +1,40 @@ +mod tests { + use fosscopetoolkit_core::models::action_command::ActionCommand; + + #[test] + fn cp() { + // Create Test Directory + let _ = std::fs::create_dir(".test"); + + // Create Test Directory + let _ = std::fs::create_dir(".test/source"); + // Create Test File + let _ = std::fs::write(".test/source/test.md", "Test File"); + + // Test The Command + let command = ActionCommand::new( + "CP".to_string(), + vec![ + ".test/source/test.md".to_string(), + ".test/copied/test.md".to_string(), + ], + ); + + let r = command.execute(); + assert!(r.is_ok()); + + let _ = std::fs::create_dir(".test/source_dir"); + let _ = std::fs::write(".test/source_dir/test.md", "Dir Test File"); + + let command = ActionCommand::new( + "CP".to_string(), + vec![ + ".test/source_dir".to_string(), + ".test/copied_dir".to_string(), + ], + ); + + let r = command.execute(); + assert!(r.is_ok()); + } +} From 6406c32bb137713fdf8d3675b0ba1c26882d3fee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Qian=20Qian=20=22Cubik=22=E2=80=8E?= Date: Sat, 13 Jul 2024 18:18:06 -0400 Subject: [PATCH 08/12] feat(core): Add `echo` command MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Qian Qian "Cubik"‎ --- src-rust/toolkit-core/src/models/action_command.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src-rust/toolkit-core/src/models/action_command.rs b/src-rust/toolkit-core/src/models/action_command.rs index 8723b5e..4343ddd 100644 --- a/src-rust/toolkit-core/src/models/action_command.rs +++ b/src-rust/toolkit-core/src/models/action_command.rs @@ -54,6 +54,13 @@ impl ActionCommand { Ok(()) } } + // Write a content to a file + "ECHO" => { + let path = self.args.get(0).unwrap(); + let content = self.args.get(1).unwrap(); + std::fs::write(path, content).unwrap(); + Ok(()) + } // Create a directory "MKDIR" => { let path = self.args.get(0).unwrap(); From c366c0b7edc053fc0ebaf252fb06585e397bc467 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Qian=20Qian=20=22Cubik=22=E2=80=8E?= Date: Sat, 13 Jul 2024 18:47:28 -0400 Subject: [PATCH 09/12] test(core): Add Tests for Action Commands MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Qian Qian "Cubik"‎ --- .../toolkit-core/src/models/action_command.rs | 6 ++ .../toolkit-core/tests/action_command_test.rs | 100 ++++++++++++++++++ 2 files changed, 106 insertions(+) diff --git a/src-rust/toolkit-core/src/models/action_command.rs b/src-rust/toolkit-core/src/models/action_command.rs index 4343ddd..a0add9c 100644 --- a/src-rust/toolkit-core/src/models/action_command.rs +++ b/src-rust/toolkit-core/src/models/action_command.rs @@ -71,6 +71,12 @@ impl ActionCommand { "MV" => { let src = self.args.get(0).unwrap(); let dest = self.args.get(1).unwrap(); + + let dest_dir = std::path::Path::new(dest); + if !dest_dir.exists() { + std::fs::create_dir_all(dest_dir.parent().unwrap()).unwrap(); + } + std::fs::rename(src, dest).unwrap(); Ok(()) } diff --git a/src-rust/toolkit-core/tests/action_command_test.rs b/src-rust/toolkit-core/tests/action_command_test.rs index d49fbc1..6fc83ac 100644 --- a/src-rust/toolkit-core/tests/action_command_test.rs +++ b/src-rust/toolkit-core/tests/action_command_test.rs @@ -37,4 +37,104 @@ mod tests { let r = command.execute(); assert!(r.is_ok()); } + + #[test] + fn echo() { + // Create Test Directory + let _ = std::fs::create_dir(".test"); + + // Test The Command + let command = ActionCommand::new( + "ECHO".to_string(), + vec![ + ".test/echo.md".to_string(), + "Echo Test".to_string(), + ], + ); + + let r = command.execute(); + assert!(r.is_ok()); + } + + #[test] + fn mkdir() { + // Create Test Directory + let _ = std::fs::create_dir(".test"); + + // Test The Command + let command = ActionCommand::new( + "MKDIR".to_string(), + vec![ + ".test/mkdir".to_string(), + ], + ); + + let r = command.execute(); + assert!(r.is_ok()); + } + + #[test] + fn mv() { + // Create Test Directory + let _ = std::fs::create_dir(".test"); + + // Create Test Directory + let _ = std::fs::create_dir(".test/mv-src"); + // Create Test File + let _ = std::fs::write(".test/mv-src/test.md", "Test File"); + + // Test The Command + let command = ActionCommand::new( + "MV".to_string(), + vec![ + ".test/mv-src/test.md".to_string(), + ".test/moved/test.md".to_string(), + ], + ); + + let r = command.execute(); + assert!(r.is_ok()); + + let _ = std::fs::write(".test/mv-src/test.md", "Dir Test File"); + + let command = ActionCommand::new( + "MV".to_string(), + vec![ + ".test/mv-src".to_string(), + ".test/moved_dir".to_string(), + ], + ); + + let r = command.execute(); + assert!(r.is_ok()); + } + + #[test] + fn touch() { + // Create Test Directory + let _ = std::fs::create_dir(".test"); + + // Test The Command + let command = ActionCommand::new( + "TOUCH".to_string(), + vec![ + ".test/touch.md".to_string(), + ], + ); + + let r = command.execute(); + assert!(r.is_ok()); + } + + #[test] + fn unknown() { + // Test The Command + let command = ActionCommand::new( + "UNKNOWN".to_string(), + vec![], + ); + + let r = command.execute(); + assert!(r.is_err()); + } } From f486004a8be356a6a2249e0554a12acf7316b876 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Qian=20Qian=20=22Cubik=22=E2=80=8E?= Date: Sat, 13 Jul 2024 21:38:05 -0400 Subject: [PATCH 10/12] build(core): Add `handlebars` to dependencies MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Qian Qian "Cubik"‎ --- src-rust/Cargo.lock | 66 ++++++++++++++++++++++++++++++++ src-rust/toolkit-core/Cargo.toml | 1 + 2 files changed, 67 insertions(+) diff --git a/src-rust/Cargo.lock b/src-rust/Cargo.lock index f6515c2..e15d7b7 100644 --- a/src-rust/Cargo.lock +++ b/src-rust/Cargo.lock @@ -814,6 +814,7 @@ dependencies = [ name = "fosscopetoolkit-core" version = "0.1.0-dev" dependencies = [ + "handlebars", "html2md", "libhtmlfilter", "octocrab", @@ -1273,6 +1274,20 @@ dependencies = [ "tracing", ] +[[package]] +name = "handlebars" +version = "5.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d08485b96a0e6393e9e4d1b8d48cf74ad6c063cd905eb33f42c1ce3f0377539b" +dependencies = [ + "log", + "pest", + "pest_derive", + "serde", + "serde_json", + "thiserror", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -2383,6 +2398,51 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +[[package]] +name = "pest" +version = "2.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd53dff83f26735fdc1ca837098ccf133605d794cdae66acfc2bfac3ec809d95" +dependencies = [ + "memchr", + "thiserror", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a548d2beca6773b1c244554d36fcf8548a8a58e74156968211567250e48e49a" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c93a82e8d145725dcbaf44e5ea887c8a869efdcc28706df2d08c69e17077183" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn 2.0.66", +] + +[[package]] +name = "pest_meta" +version = "2.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a941429fea7e08bedec25e4f6785b6ffaacc6b755da98df5ef3e7dcf4a124c4f" +dependencies = [ + "once_cell", + "pest", + "sha2", +] + [[package]] name = "phf" version = "0.8.0" @@ -4172,6 +4232,12 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +[[package]] +name = "ucd-trie" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" + [[package]] name = "unicode-bidi" version = "0.3.15" diff --git a/src-rust/toolkit-core/Cargo.toml b/src-rust/toolkit-core/Cargo.toml index bfcbdd7..1014108 100644 --- a/src-rust/toolkit-core/Cargo.toml +++ b/src-rust/toolkit-core/Cargo.toml @@ -25,6 +25,7 @@ octocrab = "0.38.0" # GitHub API html2md = { workspace = true } # HTML to Markdown libhtmlfilter = "0.1.2" # HTML Filter openai_api_rust = "0.1.9" # OpenAI API +handlebars = "5.1.2" # Template Engine [dev-dependencies] wiremock = "0.6.0" From 89a1e194608b5a48f7bb032c4eabb32f9c222c0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Qian=20Qian=20=22Cubik=22=E2=80=8E?= Date: Sat, 13 Jul 2024 23:52:31 -0400 Subject: [PATCH 11/12] feat(core): Allow variables for Action Command `CP` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Qian Qian "Cubik"‎ --- .../toolkit-core/src/models/action_command.rs | 19 +++++--- .../toolkit-core/tests/action_command_test.rs | 44 +++++++++++++++---- 2 files changed, 50 insertions(+), 13 deletions(-) diff --git a/src-rust/toolkit-core/src/models/action_command.rs b/src-rust/toolkit-core/src/models/action_command.rs index a0add9c..77143be 100644 --- a/src-rust/toolkit-core/src/models/action_command.rs +++ b/src-rust/toolkit-core/src/models/action_command.rs @@ -1,3 +1,6 @@ +use std::collections::HashMap; +use handlebars::Handlebars; + #[derive(PartialEq, Eq, Debug)] pub struct ActionCommand { pub command: String, @@ -30,14 +33,20 @@ impl ActionCommand { } } - pub fn execute(&self) -> Result<(), &str> { + pub fn execute(&self, vars: Option>) -> Result<(), &str> { match self.command.as_str() { // Copy a file or a directory "CP" => { - let src = self.args.get(0).unwrap(); - let dest = self.args.get(1).unwrap(); + let mut src = String::from(self.args.get(0).unwrap()); + let mut dest = String::from(self.args.get(1).unwrap()); + + if vars.is_some() { + let handlebars = Handlebars::new(); + src = handlebars.render_template(&*src, &vars).unwrap(); + dest = handlebars.render_template(&*dest, &vars).unwrap(); + } - let src_dir = std::path::Path::new(src); + let src_dir = std::path::Path::new(&src); if src_dir.is_dir() { let r = copy_dir_all(src, dest); match r { @@ -45,7 +54,7 @@ impl ActionCommand { Err(_) => Err("Error copying directory"), } } else { - let dest_dir = std::path::Path::new(dest); + let dest_dir = std::path::Path::new(&dest); if !dest_dir.exists() { std::fs::create_dir_all(dest_dir.parent().unwrap()).unwrap(); } diff --git a/src-rust/toolkit-core/tests/action_command_test.rs b/src-rust/toolkit-core/tests/action_command_test.rs index 6fc83ac..b087717 100644 --- a/src-rust/toolkit-core/tests/action_command_test.rs +++ b/src-rust/toolkit-core/tests/action_command_test.rs @@ -1,4 +1,5 @@ mod tests { + use std::collections::HashMap; use fosscopetoolkit_core::models::action_command::ActionCommand; #[test] @@ -20,7 +21,7 @@ mod tests { ], ); - let r = command.execute(); + let r = command.execute(None); assert!(r.is_ok()); let _ = std::fs::create_dir(".test/source_dir"); @@ -34,7 +35,7 @@ mod tests { ], ); - let r = command.execute(); + let r = command.execute(None); assert!(r.is_ok()); } @@ -52,7 +53,7 @@ mod tests { ], ); - let r = command.execute(); + let r = command.execute(None); assert!(r.is_ok()); } @@ -69,7 +70,7 @@ mod tests { ], ); - let r = command.execute(); + let r = command.execute(None); assert!(r.is_ok()); } @@ -92,7 +93,7 @@ mod tests { ], ); - let r = command.execute(); + let r = command.execute(None); assert!(r.is_ok()); let _ = std::fs::write(".test/mv-src/test.md", "Dir Test File"); @@ -105,7 +106,7 @@ mod tests { ], ); - let r = command.execute(); + let r = command.execute(None); assert!(r.is_ok()); } @@ -122,7 +123,7 @@ mod tests { ], ); - let r = command.execute(); + let r = command.execute(None); assert!(r.is_ok()); } @@ -134,7 +135,34 @@ mod tests { vec![], ); - let r = command.execute(); + let r = command.execute(None); assert!(r.is_err()); } + + #[test] + fn cp_with_env() { + // Create Test Directory + let _ = std::fs::create_dir(".test/with_template_engine"); + + // Create Test Directory + let _ = std::fs::create_dir(".test/with_template_engine/source"); + // Create Test File + let _ = std::fs::write(".test/with_template_engine/source/test.md", "Test File"); + + // Test The Command + let command = ActionCommand::new( + "CP".to_string(), + vec![ + ".test/{{ from }}/test.md".to_string(), + ".test/{{ to }}/test.md".to_string(), + ], + ); + + let mut data = HashMap::new(); + data.insert("from", "with_template_engine/source"); + data.insert("to", "with_template_engine/copied"); + + let r = command.execute(Some(data)); + assert!(r.is_ok()); + } } From 60d3fbbef8b1def7908b2f774353bb4b842593e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Qian=20Qian=20=22Cubik=22=E2=80=8E?= Date: Sun, 14 Jul 2024 00:10:03 -0400 Subject: [PATCH 12/12] feat(core): Apply var to all Action Command MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Qian Qian "Cubik"‎ --- .../toolkit-core/src/models/action_command.rs | 34 ++++++++++--------- .../toolkit-core/tests/action_command_test.rs | 6 ++-- 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/src-rust/toolkit-core/src/models/action_command.rs b/src-rust/toolkit-core/src/models/action_command.rs index 77143be..5f98dde 100644 --- a/src-rust/toolkit-core/src/models/action_command.rs +++ b/src-rust/toolkit-core/src/models/action_command.rs @@ -33,18 +33,20 @@ impl ActionCommand { } } - pub fn execute(&self, vars: Option>) -> Result<(), &str> { + pub fn execute(&self, vars: Option<&HashMap<&str, &str>>) -> Result<(), &str> { + let mut args = self.args.clone(); + if vars.is_some() { + let handlebars = Handlebars::new(); + args = args.iter().map( + |arg| handlebars.render_template(&*arg, &vars).unwrap() + ).collect(); + } + match self.command.as_str() { // Copy a file or a directory "CP" => { - let mut src = String::from(self.args.get(0).unwrap()); - let mut dest = String::from(self.args.get(1).unwrap()); - - if vars.is_some() { - let handlebars = Handlebars::new(); - src = handlebars.render_template(&*src, &vars).unwrap(); - dest = handlebars.render_template(&*dest, &vars).unwrap(); - } + let src = args.get(0).unwrap(); + let dest = args.get(1).unwrap(); let src_dir = std::path::Path::new(&src); if src_dir.is_dir() { @@ -65,21 +67,21 @@ impl ActionCommand { } // Write a content to a file "ECHO" => { - let path = self.args.get(0).unwrap(); - let content = self.args.get(1).unwrap(); + let path = args.get(0).unwrap(); + let content = args.get(1).unwrap(); std::fs::write(path, content).unwrap(); Ok(()) } // Create a directory "MKDIR" => { - let path = self.args.get(0).unwrap(); + let path = args.get(0).unwrap(); std::fs::create_dir_all(path).unwrap(); Ok(()) } // Move a file or a directory "MV" => { - let src = self.args.get(0).unwrap(); - let dest = self.args.get(1).unwrap(); + let src = args.get(0).unwrap(); + let dest = args.get(1).unwrap(); let dest_dir = std::path::Path::new(dest); if !dest_dir.exists() { @@ -91,7 +93,7 @@ impl ActionCommand { } // Remove a file or a directory "RM" => { - let path = self.args.get(0).unwrap(); + let path = args.get(0).unwrap(); if std::fs::metadata(path).unwrap().is_dir() { std::fs::remove_dir_all(path).unwrap(); } else { @@ -101,7 +103,7 @@ impl ActionCommand { } // Create a file "TOUCH" => { - let path = self.args.get(0).unwrap(); + let path = args.get(0).unwrap(); std::fs::File::create(path).unwrap(); Ok(()) } diff --git a/src-rust/toolkit-core/tests/action_command_test.rs b/src-rust/toolkit-core/tests/action_command_test.rs index b087717..759b421 100644 --- a/src-rust/toolkit-core/tests/action_command_test.rs +++ b/src-rust/toolkit-core/tests/action_command_test.rs @@ -142,10 +142,10 @@ mod tests { #[test] fn cp_with_env() { // Create Test Directory - let _ = std::fs::create_dir(".test/with_template_engine"); + let _ = std::fs::create_dir_all(".test/with_template_engine"); // Create Test Directory - let _ = std::fs::create_dir(".test/with_template_engine/source"); + let _ = std::fs::create_dir_all(".test/with_template_engine/source"); // Create Test File let _ = std::fs::write(".test/with_template_engine/source/test.md", "Test File"); @@ -162,7 +162,7 @@ mod tests { data.insert("from", "with_template_engine/source"); data.insert("to", "with_template_engine/copied"); - let r = command.execute(Some(data)); + let r = command.execute(Some(&data)); assert!(r.is_ok()); } }