diff --git a/cli/src/commands/add.rs b/cli/src/commands/add.rs index 9fedeaf3..45f0cb32 100644 --- a/cli/src/commands/add.rs +++ b/cli/src/commands/add.rs @@ -22,14 +22,10 @@ use engine::handlers::add::AddRequest; /// - **`--update, -u`**: Add modified and deleted files only. Conflicts with `--all`. /// - **`--dry-run, -n`**: Show which files *would* be added, without actually staging them. /// - **`--verbose, -v`**: Increase verbosity of output. +#[derive(Default)] pub struct AddCommand; impl AddCommand { - /// Creates a new instance of the `AddCommand`. - pub fn new() -> Self { - Self - } - /// Path argument key. const ARG_PATH: &'static str = "path"; diff --git a/cli/src/commands/branch.rs b/cli/src/commands/branch.rs index bdd840d4..640cce57 100644 --- a/cli/src/commands/branch.rs +++ b/cli/src/commands/branch.rs @@ -13,14 +13,10 @@ use miette::{IntoDiagnostic, Result}; /// Allows listing, creating, deleting, and renaming branches within the repository. /// It handles argument parsing to dispatch the appropriate request (Create, Delete, /// Rename, or List) to the [`BranchHandler`]. +#[derive(Default)] pub struct BranchCommand; impl BranchCommand { - /// Creates a new instance of the [`BranchCommand`]. - pub fn new() -> Self { - Self - } - /// First positional argument: branch name (creation/deletion) or old branch name (renaming). const ARG_NAME: &'static str = "branch-name"; diff --git a/cli/src/commands/clone.rs b/cli/src/commands/clone.rs index 1da87eee..7991bfdb 100644 --- a/cli/src/commands/clone.rs +++ b/cli/src/commands/clone.rs @@ -11,14 +11,10 @@ use url::Url; /// Clones a remote repository into a new directory, creates a /// tracking connection to the remote repository (origin), and checks out /// the default branch. +#[derive(Default)] pub struct CloneCommand; impl CloneCommand { - /// Creates a new instance of the [`CloneCommand`]. - pub fn new() -> Self { - Self {} - } - /// Argument name for specifying the remote repository URL. const ARG_REPOSITORY: &'static str = "repository"; diff --git a/cli/src/commands/commit.rs b/cli/src/commands/commit.rs index 38d7f314..eaa1069c 100644 --- a/cli/src/commands/commit.rs +++ b/cli/src/commands/commit.rs @@ -15,14 +15,10 @@ use owo_colors::OwoColorize; /// along with a user-supplied message and optional metadata (author, date, etc.). /// Supports automatically staging changes, amending the last commit, or performing a dry run /// to preview the commit without persisting it. +#[derive(Default)] pub struct CommitCommand; impl CommitCommand { - /// Creates a new instance of the `CommitCommand`. - pub fn new() -> Self { - Self - } - /// Argument key for specifying the commit message. /// Corresponds to the `-m, --message ` option. const ARG_MESSAGE: &'static str = "message"; diff --git a/cli/src/commands/config.rs b/cli/src/commands/config.rs index b6c2021f..63da9401 100644 --- a/cli/src/commands/config.rs +++ b/cli/src/commands/config.rs @@ -12,15 +12,9 @@ use subcommands::*; /// /// Serves as a namespace for configuration-related subcommands: /// list, get, set, unset, and edit settings at global/local/file scopes. +#[derive(Default)] pub struct ConfigCommand; -impl ConfigCommand { - /// Creates a new instance of the `ConfigCommand`. - pub fn new() -> Self { - Self - } -} - #[async_trait] impl MevaCommand for ConfigCommand { type Container = MevaContainer; @@ -47,6 +41,7 @@ impl MevaCommand for ConfigCommand { Box::new(ConfigSetCommand), Box::new(ConfigUnsetCommand), Box::new(ConfigEditCommand), + Box::new(ConfigCreateCommand), ] } @@ -63,7 +58,7 @@ mod tests { #[rstest] fn test_command_name_about_version() { - let cmd = ConfigCommand::new(); + let cmd = ConfigCommand; assert_eq!(cmd.name(), "config"); assert_eq!( cmd.about(), diff --git a/cli/src/commands/config/subcommands.rs b/cli/src/commands/config/subcommands.rs index 998cd35e..18411eea 100644 --- a/cli/src/commands/config/subcommands.rs +++ b/cli/src/commands/config/subcommands.rs @@ -1,9 +1,11 @@ +pub mod create; pub mod edit; pub mod get; pub mod list; pub mod set; pub mod unset; +pub use create::ConfigCreateCommand; pub use edit::ConfigEditCommand; pub use get::ConfigGetCommand; pub use list::ConfigListCommand; diff --git a/cli/src/commands/config/subcommands/create.rs b/cli/src/commands/config/subcommands/create.rs new file mode 100644 index 00000000..ffe397a5 --- /dev/null +++ b/cli/src/commands/config/subcommands/create.rs @@ -0,0 +1,70 @@ +use std::path::PathBuf; + +use async_trait::async_trait; +use clap::{ArgMatches, Command}; +use engine::EngineContainer; +use engine::engine_container::MevaContainer; +use engine::handlers::config::CreateRequest; +use miette::IntoDiagnostic; + +use crate::commands::MevaCommand; +use crate::extensions::WithFile; + +/// Implements the `create` subcommand for Meva configuration management. +/// +/// Creates a new configuration file at the specified path or the default global location. +#[derive(Default)] +pub struct ConfigCreateCommand; + +#[async_trait] +impl MevaCommand for ConfigCreateCommand { + type Container = MevaContainer; + + fn name(&self) -> &'static str { + "create" + } + + fn about(&self) -> &'static str { + "Create a new configuration file" + } + + fn version(&self) -> &'static str { + "1.0.0" + } + + fn build_command(&self) -> Command { + self.build_base_command() + .with_file_arg("Path to the configuration file to create") + } + + async fn execute( + &self, + matches: &ArgMatches, + container: &Self::Container, + ) -> miette::Result<()> { + let file = matches.get_one::(Command::ARG_FILE); + + let request = CreateRequest { + file: file.cloned(), + }; + + let config_handler = container.config_handler().into_diagnostic()?; + config_handler.handle_create(request).into_diagnostic()?; + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use pretty_assertions::assert_eq; + use rstest::rstest; + + #[rstest] + fn test_command_name_about_version() { + let cmd = ConfigCreateCommand; + assert_eq!(cmd.name(), "create"); + assert_eq!(cmd.about(), "Create a new configuration file"); + assert_eq!(cmd.version(), "1.0.0"); + } +} diff --git a/cli/src/commands/config/subcommands/edit.rs b/cli/src/commands/config/subcommands/edit.rs index 72ee6904..d8f00da9 100644 --- a/cli/src/commands/config/subcommands/edit.rs +++ b/cli/src/commands/config/subcommands/edit.rs @@ -12,16 +12,9 @@ use crate::extensions::{LocationSelection, WithLocations}; /// /// Opens the chosen configuration file in the user's preferred editor, /// respecting any `core.editor` override in config or falling back to OS defaults. +#[derive(Default)] pub struct ConfigEditCommand; -impl ConfigEditCommand { - /// Creates a new instance of the `ConfigGetCommand`. - #[allow(dead_code)] - pub fn new() -> Self { - Self - } -} - #[async_trait] impl MevaCommand for ConfigEditCommand { type Container = MevaContainer; @@ -75,7 +68,7 @@ mod tests { #[rstest] fn test_command_name_about_version() { - let cmd = ConfigEditCommand::new(); + let cmd = ConfigEditCommand; assert_eq!(cmd.name(), "edit"); assert_eq!(cmd.about(), "Open the config file in your default editor"); assert_eq!(cmd.version(), "1.0.0"); diff --git a/cli/src/commands/config/subcommands/get.rs b/cli/src/commands/config/subcommands/get.rs index d3e0b9dc..1ab693ea 100644 --- a/cli/src/commands/config/subcommands/get.rs +++ b/cli/src/commands/config/subcommands/get.rs @@ -8,6 +8,7 @@ use miette::IntoDiagnostic; use crate::commands::MevaCommand; use crate::extensions::{LocationSelection, WithLocations}; +#[derive(Default)] pub struct ConfigGetCommand; /// Implements the `get` subcommand for Meva configuration management. @@ -15,12 +16,6 @@ pub struct ConfigGetCommand; /// Retrieves the value of a specified TOML key from the chosen /// configuration scope, with an optional default fallback if the key is missing. impl ConfigGetCommand { - /// Creates a new instance of the `ConfigGetCommand`. - #[allow(dead_code)] - pub fn new() -> Self { - Self - } - const ARG_KEY: &'static str = "key"; const ARG_DEFAULT: &'static str = "default"; @@ -101,7 +96,7 @@ mod tests { #[rstest] fn test_command_name_about_version() { - let cmd = ConfigGetCommand::new(); + let cmd = ConfigGetCommand; assert_eq!(cmd.name(), "get"); assert_eq!(cmd.about(), "Get the value for a given configuration key"); assert_eq!(cmd.version(), "1.0.0"); diff --git a/cli/src/commands/config/subcommands/list.rs b/cli/src/commands/config/subcommands/list.rs index 6f571e49..717ad0ef 100644 --- a/cli/src/commands/config/subcommands/list.rs +++ b/cli/src/commands/config/subcommands/list.rs @@ -13,16 +13,9 @@ use crate::extensions::{LocationSelection, WithLocations}; /// /// Retrieves and displays all key/value pairs from the selected /// configuration scope. +#[derive(Default)] pub struct ConfigListCommand; -impl ConfigListCommand { - /// Creates a new instance of the `ConfigListCommand`. - #[allow(dead_code)] - pub fn new() -> Self { - Self - } -} - #[async_trait] impl MevaCommand for ConfigListCommand { type Container = MevaContainer; @@ -95,7 +88,7 @@ mod tests { #[rstest] fn test_command_name_about_version() { - let cmd = ConfigListCommand::new(); + let cmd = ConfigListCommand; assert_eq!(cmd.name(), "list"); assert_eq!(cmd.about(), "Print all available configuration entries"); assert_eq!(cmd.version(), "1.0.0"); diff --git a/cli/src/commands/config/subcommands/set.rs b/cli/src/commands/config/subcommands/set.rs index 09eda3ea..21ee442b 100644 --- a/cli/src/commands/config/subcommands/set.rs +++ b/cli/src/commands/config/subcommands/set.rs @@ -13,15 +13,10 @@ use crate::extensions::{LocationSelection, WithKey, WithLocations}; /// /// Adds or updates a specified TOML key in the chosen configuration scope /// with a provided value. +#[derive(Default)] pub struct ConfigSetCommand; impl ConfigSetCommand { - /// Creates a new instance of the `ConfigSetCommand`. - #[allow(dead_code)] - pub fn new() -> Self { - Self - } - const ARG_VALUE: &'static str = "value"; } @@ -99,7 +94,7 @@ mod tests { #[rstest] fn test_command_name_about_version() { - let cmd = ConfigSetCommand::new(); + let cmd = ConfigSetCommand; assert_eq!(cmd.name(), "set"); assert_eq!(cmd.about(), "Set the value for a given configuration key"); assert_eq!(cmd.version(), "1.0.0"); diff --git a/cli/src/commands/config/subcommands/unset.rs b/cli/src/commands/config/subcommands/unset.rs index 16691668..76aa1bc6 100644 --- a/cli/src/commands/config/subcommands/unset.rs +++ b/cli/src/commands/config/subcommands/unset.rs @@ -11,16 +11,9 @@ use crate::extensions::{LocationSelection, WithKey, WithLocations}; /// Implements the `unset` subcommand for Meva configuration management. /// /// Removes a specified TOML key from the chosen configuration scope. +#[derive(Default)] pub struct ConfigUnsetCommand; -impl ConfigUnsetCommand { - /// Creates a new instance of the `ConfigUnsetCommand`. - #[allow(dead_code)] - pub fn new() -> Self { - Self - } -} - #[async_trait] impl MevaCommand for ConfigUnsetCommand { type Container = MevaContainer; @@ -81,7 +74,7 @@ mod tests { #[rstest] fn test_command_name_about_version() { - let cmd = ConfigUnsetCommand::new(); + let cmd = ConfigUnsetCommand; assert_eq!(cmd.name(), "unset"); assert_eq!(cmd.about(), "Remove a configuration entry"); assert_eq!(cmd.version(), "1.0.0"); diff --git a/cli/src/commands/diff.rs b/cli/src/commands/diff.rs index 4b435010..91288fac 100644 --- a/cli/src/commands/diff.rs +++ b/cli/src/commands/diff.rs @@ -18,14 +18,10 @@ use crate::commands::MevaCommand; /// Implements the `diff` command for Meva DVCS. /// /// Shows changes between commits, the index, and the working tree. +#[derive(Default)] pub struct DiffCommand; impl DiffCommand { - /// Creates a new instance of the `DiffCommand`. - pub fn new() -> Self { - Self {} - } - /// Argument name for the `--cached` flag. /// When set, the command compares the index to the specified commit (default: HEAD). const ARG_CACHED: &'static str = "cached"; @@ -185,7 +181,7 @@ mod tests { #[rstest] fn test_command_name_about_version() { - let cmd = DiffCommand::new(); + let cmd = DiffCommand; assert_eq!(cmd.name(), "diff"); assert_eq!( cmd.about(), diff --git a/cli/src/commands/fetch.rs b/cli/src/commands/fetch.rs index 1a531cf6..f7f7bbcd 100644 --- a/cli/src/commands/fetch.rs +++ b/cli/src/commands/fetch.rs @@ -11,14 +11,10 @@ use crate::{commands::MevaCommand, extensions::WithVerbose}; /// This command is responsible for downloading objects and references from a remote /// repository. It serves as the entry point for the fetch logic, handling argument /// parsing and delegating the execution to the appropriate engine handler. +#[derive(Default)] pub struct FetchCommand; impl FetchCommand { - /// Creates a new instance of the [`FetchCommand`]. - pub fn new() -> Self { - Self {} - } - /// The name of the argument used to specify the prune flag. const ARG_PRUNE: &'static str = "prune"; @@ -106,7 +102,7 @@ mod tests { #[rstest] fn test_command_name_about_version() { - let cmd = FetchCommand::new(); + let cmd = FetchCommand; assert_eq!(cmd.name(), "fetch"); assert_eq!( cmd.about(), diff --git a/cli/src/commands/ignore.rs b/cli/src/commands/ignore.rs index de78b4a8..0554c370 100644 --- a/cli/src/commands/ignore.rs +++ b/cli/src/commands/ignore.rs @@ -12,15 +12,9 @@ use subcommands::*; /// /// Serves as a namespace for subcommands managing ignore patterns: /// add, remove, check, and edit ignore rules for files and directories. +#[derive(Default)] pub struct IgnoreCommand; -impl IgnoreCommand { - /// Creates a new instance of the `IgnoreCommand`. - pub fn new() -> Self { - Self - } -} - #[async_trait] impl MevaCommand for IgnoreCommand { type Container = MevaContainer; @@ -62,7 +56,7 @@ mod tests { #[rstest] fn test_command_name_about_version() { - let cmd = IgnoreCommand::new(); + let cmd = IgnoreCommand; assert_eq!(cmd.name(), "ignore"); assert_eq!( cmd.about(), diff --git a/cli/src/commands/ignore/subcommands/add.rs b/cli/src/commands/ignore/subcommands/add.rs index eee0132d..e3278915 100644 --- a/cli/src/commands/ignore/subcommands/add.rs +++ b/cli/src/commands/ignore/subcommands/add.rs @@ -18,16 +18,9 @@ use crate::{ /// /// Adds the specified pattern to the chosen ignore file, /// ensuring the ignore rule is appended without duplicates or formatting errors. +#[derive(Default)] pub struct IgnoreAddCommand; -impl IgnoreAddCommand { - /// Creates a new instance of the `IgnoreAddCommand`. - #[allow(dead_code)] - pub fn new() -> Self { - Self - } -} - #[async_trait] impl MevaCommand for IgnoreAddCommand { type Container = MevaContainer; @@ -81,7 +74,7 @@ mod tests { #[rstest] fn test_command_name_about_version() { - let cmd = IgnoreAddCommand::new(); + let cmd = IgnoreAddCommand; assert_eq!(cmd.name(), "add"); assert_eq!(cmd.about(), "Appends a pattern to the ignore file"); assert_eq!(cmd.version(), "1.0.0"); diff --git a/cli/src/commands/ignore/subcommands/check.rs b/cli/src/commands/ignore/subcommands/check.rs index e36ad510..542b5443 100644 --- a/cli/src/commands/ignore/subcommands/check.rs +++ b/cli/src/commands/ignore/subcommands/check.rs @@ -14,15 +14,10 @@ use crate::{commands::MevaCommand, extensions::WithFile}; /// /// Checks whether the specified path is ignored according to the rules of the chosen ignore file, /// optionally providing an explanation of which patterns matched. +#[derive(Default)] pub struct IgnoreCheckCommand; impl IgnoreCheckCommand { - /// Creates a new instance of the `IgnoreCheckCommand`. - #[allow(dead_code)] - pub fn new() -> Self { - Self - } - const ARG_PATH: &'static str = "path"; const ARG_EXPLAIN: &'static str = "explain"; @@ -123,7 +118,7 @@ mod tests { #[rstest] fn test_command_name_about_version() { - let cmd = IgnoreCheckCommand::new(); + let cmd = IgnoreCheckCommand; assert_eq!(cmd.name(), "check"); assert_eq!( cmd.about(), diff --git a/cli/src/commands/ignore/subcommands/edit.rs b/cli/src/commands/ignore/subcommands/edit.rs index 33207e91..350c8963 100644 --- a/cli/src/commands/ignore/subcommands/edit.rs +++ b/cli/src/commands/ignore/subcommands/edit.rs @@ -15,16 +15,9 @@ use crate::{commands::MevaCommand, extensions::WithFile}; /// /// Opens the chosen configuration file in the user's preferred editor, /// respecting any `core.editor` override in config or falling back to OS defaults. +#[derive(Default)] pub struct IgnoreEditCommand; -impl IgnoreEditCommand { - /// Creates a new instance of the `IgnoreEditCommand`. - #[allow(dead_code)] - pub fn new() -> Self { - Self - } -} - #[async_trait] impl MevaCommand for IgnoreEditCommand { type Container = MevaContainer; @@ -78,7 +71,7 @@ mod tests { #[rstest] fn test_command_name_about_version() { - let cmd = IgnoreEditCommand::new(); + let cmd = IgnoreEditCommand; assert_eq!(cmd.name(), "edit"); assert_eq!(cmd.about(), "Open the ignore file in your default editor"); assert_eq!(cmd.version(), "1.0.0"); diff --git a/cli/src/commands/ignore/subcommands/remove.rs b/cli/src/commands/ignore/subcommands/remove.rs index 562d5836..f0b8d408 100644 --- a/cli/src/commands/ignore/subcommands/remove.rs +++ b/cli/src/commands/ignore/subcommands/remove.rs @@ -18,16 +18,9 @@ use crate::{ /// /// Removes the specified pattern from the chosen ignore file, /// reporting how many lines were deleted or if the pattern was not present. +#[derive(Default)] pub struct IgnoreRemoveCommand; -impl IgnoreRemoveCommand { - /// Creates a new instance of the `IgnoreRemoveCommand`. - #[allow(dead_code)] - pub fn new() -> Self { - Self - } -} - #[async_trait] impl MevaCommand for IgnoreRemoveCommand { type Container = MevaContainer; @@ -93,7 +86,7 @@ mod tests { #[rstest] fn test_command_name_about_version() { - let cmd = IgnoreRemoveCommand::new(); + let cmd = IgnoreRemoveCommand; assert_eq!(cmd.name(), "remove"); assert_eq!(cmd.about(), "Remove a pattern from the ignore file"); assert_eq!(cmd.version(), "1.0.0"); diff --git a/cli/src/commands/init.rs b/cli/src/commands/init.rs index b50194b7..53ad4cbf 100644 --- a/cli/src/commands/init.rs +++ b/cli/src/commands/init.rs @@ -11,14 +11,10 @@ use crate::commands::MevaCommand; /// /// Initializes a new repository at a specified path, /// optionally setting the initial branch name. -pub struct InitCommand {} +#[derive(Default)] +pub struct InitCommand; impl InitCommand { - /// Creates a new instance of the `InitCommand`. - pub fn new() -> Self { - Self {} - } - /// Argument name for specifying the initial branch. const ARG_INITIAL_BRANCH: &'static str = "initial-branch"; @@ -115,13 +111,13 @@ mod tests { use std::path::PathBuf; fn get_matches_from(args: &[&str]) -> ArgMatches { - let cmd = InitCommand::new(); + let cmd = InitCommand; cmd.build_command().try_get_matches_from(args).unwrap() } #[rstest] fn test_command_name_about_version() { - let cmd = InitCommand::new(); + let cmd = InitCommand; assert_eq!(cmd.name(), "init"); assert_eq!(cmd.about(), "Create an empty Meva repository"); assert_eq!(cmd.version(), "1.0.0"); @@ -129,7 +125,7 @@ mod tests { #[rstest] fn test_command_builds_with_expected_args() { - let cmd = InitCommand::new(); + let cmd = InitCommand; let clap_cmd = cmd.build_command(); // Check command name diff --git a/cli/src/commands/log.rs b/cli/src/commands/log.rs index faa81eea..9f471ee2 100644 --- a/cli/src/commands/log.rs +++ b/cli/src/commands/log.rs @@ -16,14 +16,10 @@ use regex::Regex; /// messages, and optional change statistics. /// Supports flexible output formatting and filtering options, such as compact one-line view, /// commit range limits, time-based filters, and pattern matching on commit messages. +#[derive(Default)] pub struct LogCommand; impl LogCommand { - /// Creates a new instance of the `LogCommand`. - pub fn new() -> Self { - Self {} - } - /// Flag for displaying commits in a condensed, one-line format. const ARG_ONELINE: &'static str = "oneline"; diff --git a/cli/src/commands/ls_files.rs b/cli/src/commands/ls_files.rs index a4aff6b7..4548cf0a 100644 --- a/cli/src/commands/ls_files.rs +++ b/cli/src/commands/ls_files.rs @@ -12,14 +12,10 @@ use crate::commands::MevaCommand; /// Implements the `ls-files` command for Meva DVCS. /// /// Displays information about files in the index and working directory. -pub struct LsFilesCommand {} +#[derive(Default)] +pub struct LsFilesCommand; impl LsFilesCommand { - /// Creates a new instance of the `LsFilesCommand`. - pub fn new() -> Self { - Self {} - } - /// Argument name for `--cached` flag. /// When provided, shows only files that are tracked (present in the index). const ARG_CACHED: &'static str = "cached"; @@ -157,7 +153,7 @@ mod tests { #[rstest] fn test_command_name_about_version() { - let cmd = LsFilesCommand::new(); + let cmd = LsFilesCommand; assert_eq!(cmd.name(), "ls-files"); assert_eq!( cmd.about(), diff --git a/cli/src/commands/ls_tree.rs b/cli/src/commands/ls_tree.rs index 33a52b7d..ab7cc05c 100644 --- a/cli/src/commands/ls_tree.rs +++ b/cli/src/commands/ls_tree.rs @@ -19,14 +19,10 @@ use std::path::PathBuf; /// - **name-only view (`--name-only`)** – prints only filenames and directories. /// /// Can be used to inspect historical snapshots, branches, or arbitrary commit hashes. +#[derive(Default)] pub struct LsTreeCommand; impl LsTreeCommand { - /// Creates a new instance of the `LsTreeCommand`. - pub fn new() -> Self { - Self {} - } - /// Argument key for specifying the commit, tag, or branch to inspect. /// Corresponds to the `` argument (e.g., `HEAD`, branch name, or hash). const ARG_SNAPSHOT_ID: &'static str = "snapshot_id"; diff --git a/cli/src/commands/plugins.rs b/cli/src/commands/plugins.rs index 6266217b..f6f35615 100644 --- a/cli/src/commands/plugins.rs +++ b/cli/src/commands/plugins.rs @@ -13,15 +13,9 @@ use subcommands::*; /// The `plugins` command serves as a namespace for all plugin-related operations /// such as listing, editing, registering, unregistering, or retrieving information /// about plugins. +#[derive(Default)] pub struct PluginsCommand; -impl PluginsCommand { - /// Creates a new instance of the `PluginsCommand`. - pub fn new() -> Self { - Self - } -} - #[async_trait] impl MevaCommand for PluginsCommand { type Container = MevaContainer; @@ -64,7 +58,7 @@ mod tests { #[rstest] fn test_command_name_about_version() { - let cmd = PluginsCommand::new(); + let cmd = PluginsCommand; assert_eq!(cmd.name(), "plugins"); assert_eq!(cmd.about(), "Manage plugins"); assert_eq!(cmd.version(), "1.0.0"); diff --git a/cli/src/commands/plugins/subcommands/edit.rs b/cli/src/commands/plugins/subcommands/edit.rs index 47d89d21..7818f3e8 100644 --- a/cli/src/commands/plugins/subcommands/edit.rs +++ b/cli/src/commands/plugins/subcommands/edit.rs @@ -14,14 +14,10 @@ use crate::{ extensions::{WithCommandPlugin, WithScope}, }; +#[derive(Default)] pub struct PluginsEditCommand; impl PluginsEditCommand { - #[allow(dead_code)] - pub fn new() -> Self { - Self - } - const ARG_ENABLE: &'static str = "enable"; const ARG_DISABLE: &'static str = "disable"; @@ -119,7 +115,7 @@ mod tests { #[rstest] fn test_command_name_about_version() { - let cmd = PluginsEditCommand::new(); + let cmd = PluginsEditCommand; assert_eq!(cmd.name(), "edit"); assert_eq!( cmd.about(), diff --git a/cli/src/commands/plugins/subcommands/info.rs b/cli/src/commands/plugins/subcommands/info.rs index ae524407..4720af8c 100644 --- a/cli/src/commands/plugins/subcommands/info.rs +++ b/cli/src/commands/plugins/subcommands/info.rs @@ -13,15 +13,9 @@ use crate::{ extensions::{WithCommandPlugin, WithScope}, }; +#[derive(Default)] pub struct PluginsInfoCommand; -impl PluginsInfoCommand { - #[allow(dead_code)] - pub fn new() -> Self { - Self - } -} - #[async_trait] impl MevaCommand for PluginsInfoCommand { type Container = MevaContainer; @@ -82,7 +76,7 @@ mod tests { #[rstest] fn test_command_name_about_version() { - let cmd = PluginsInfoCommand::new(); + let cmd = PluginsInfoCommand; assert_eq!(cmd.name(), "info"); assert_eq!( cmd.about(), diff --git a/cli/src/commands/plugins/subcommands/list.rs b/cli/src/commands/plugins/subcommands/list.rs index 6cb2648d..b7b0fa35 100644 --- a/cli/src/commands/plugins/subcommands/list.rs +++ b/cli/src/commands/plugins/subcommands/list.rs @@ -10,14 +10,10 @@ use plugins::{CommandType, EventType, ScopeType}; use crate::{commands::MevaCommand, extensions::WithScope}; +#[derive(Default)] pub struct PluginsListCommand; impl PluginsListCommand { - #[allow(dead_code)] - pub fn new() -> Self { - Self - } - const ARG_COMMAND: &'static str = "command"; const ARG_EVENT: &'static str = "event"; @@ -120,7 +116,7 @@ mod tests { #[rstest] fn test_command_name_about_version() { - let cmd = PluginsListCommand::new(); + let cmd = PluginsListCommand; assert_eq!(cmd.name(), "list"); assert_eq!(cmd.about(), "List registered plugins with optional filters"); assert_eq!(cmd.version(), "1.0.0"); diff --git a/cli/src/commands/plugins/subcommands/register.rs b/cli/src/commands/plugins/subcommands/register.rs index 357a01cc..6142831f 100644 --- a/cli/src/commands/plugins/subcommands/register.rs +++ b/cli/src/commands/plugins/subcommands/register.rs @@ -17,14 +17,10 @@ use crate::{ extensions::{WithCommandPlugin, WithFile, WithScope}, }; +#[derive(Default)] pub struct PluginsRegisterCommand; impl PluginsRegisterCommand { - #[allow(dead_code)] - pub fn new() -> Self { - Self - } - const ARG_PATH: &'static str = "path"; const ARG_DESCRIPTION: &'static str = "description"; @@ -174,7 +170,7 @@ mod tests { #[rstest] fn test_command_name_about_version() { - let cmd = PluginsRegisterCommand::new(); + let cmd = PluginsRegisterCommand; assert_eq!(cmd.name(), "register"); assert_eq!(cmd.about(), "Register a new plugin"); assert_eq!(cmd.version(), "1.0.0"); diff --git a/cli/src/commands/plugins/subcommands/unregister.rs b/cli/src/commands/plugins/subcommands/unregister.rs index 4ebaa0ab..82e05941 100644 --- a/cli/src/commands/plugins/subcommands/unregister.rs +++ b/cli/src/commands/plugins/subcommands/unregister.rs @@ -14,15 +14,9 @@ use crate::{ extensions::{WithCommandPlugin, WithScope}, }; +#[derive(Default)] pub struct PluginsUnregisterCommand; -impl PluginsUnregisterCommand { - #[allow(dead_code)] - pub fn new() -> Self { - Self - } -} - #[async_trait] impl MevaCommand for PluginsUnregisterCommand { type Container = MevaContainer; @@ -96,7 +90,7 @@ mod tests { #[rstest] fn test_command_name_about_version() { - let cmd = PluginsUnregisterCommand::new(); + let cmd = PluginsUnregisterCommand; assert_eq!(cmd.name(), "unregister"); assert_eq!(cmd.about(), "Unregister a previously registered plugin"); assert_eq!(cmd.version(), "1.0.0"); diff --git a/cli/src/commands/remote.rs b/cli/src/commands/remote.rs index f7f96990..2ed0f790 100644 --- a/cli/src/commands/remote.rs +++ b/cli/src/commands/remote.rs @@ -20,16 +20,9 @@ use crate::{ /// This command manages the set of tracked repositories ("remotes"). /// It acts as a parent command for operations like adding, removing, or renaming remotes. /// If no subcommand is provided, it defaults to listing the configured remotes. +#[derive(Default)] pub struct RemoteCommand; -impl RemoteCommand { - /// Creates a new instance of the [`RemoteCommand`]. - #[allow(dead_code)] - pub fn new() -> Self { - Self - } -} - #[async_trait] impl MevaCommand for RemoteCommand { type Container = MevaContainer; @@ -112,7 +105,7 @@ mod tests { #[rstest] fn test_command_name_about_version() { - let cmd = RemoteCommand::new(); + let cmd = RemoteCommand; assert_eq!(cmd.name(), "remote"); assert_eq!(cmd.about(), "Manage remote repositories"); assert_eq!(cmd.version(), "1.0.0"); diff --git a/cli/src/commands/remote/subcommands/add.rs b/cli/src/commands/remote/subcommands/add.rs index e98193b8..1a4d5c39 100644 --- a/cli/src/commands/remote/subcommands/add.rs +++ b/cli/src/commands/remote/subcommands/add.rs @@ -16,15 +16,10 @@ use crate::{commands::MevaCommand, extensions::WithName}; /// /// This command registers a new remote repository with a specific name and URL /// in the local repository configuration. It optionally performs an immediate fetch. +#[derive(Default)] pub struct RemoteAddCommand; impl RemoteAddCommand { - /// Creates a new instance of the [`RemoteAddCommand`]. - #[allow(dead_code)] - pub fn new() -> Self { - Self - } - /// Argument name for the fetch flag. const ARG_FETCH: &'static str = "fetch"; @@ -119,7 +114,7 @@ mod tests { #[rstest] fn test_command_name_about_version() { - let cmd = RemoteAddCommand::new(); + let cmd = RemoteAddCommand; assert_eq!(cmd.name(), "add"); assert_eq!(cmd.about(), "Add a new remote repository"); assert_eq!(cmd.version(), "1.0.0"); diff --git a/cli/src/commands/remote/subcommands/get_url.rs b/cli/src/commands/remote/subcommands/get_url.rs index e845b213..0c570ce6 100644 --- a/cli/src/commands/remote/subcommands/get_url.rs +++ b/cli/src/commands/remote/subcommands/get_url.rs @@ -15,15 +15,10 @@ use crate::{commands::MevaCommand, extensions::WithName}; /// /// This command retrieves and displays the URL associated with a tracked remote. /// It allows filtering based on whether the URL is used for fetching or pushing. +#[derive(Default)] pub struct RemoteGetUrlCommand; impl RemoteGetUrlCommand { - /// Creates a new instance of the [`RemoteGetUrlCommand`]. - #[allow(dead_code)] - pub fn new() -> Self { - Self - } - /// Argument name for the direction selection (fetch/push). const ARG_DIRECTION: &'static str = "direction"; } @@ -93,7 +88,7 @@ mod tests { #[rstest] fn test_command_name_about_version() { - let cmd = RemoteGetUrlCommand::new(); + let cmd = RemoteGetUrlCommand; assert_eq!(cmd.name(), "get-url"); assert_eq!(cmd.about(), "Show the URL of a remote"); assert_eq!(cmd.version(), "1.0.0"); diff --git a/cli/src/commands/remote/subcommands/remove.rs b/cli/src/commands/remote/subcommands/remove.rs index 1ae4950e..61c0e238 100644 --- a/cli/src/commands/remote/subcommands/remove.rs +++ b/cli/src/commands/remote/subcommands/remove.rs @@ -13,16 +13,9 @@ use crate::{commands::MevaCommand, extensions::WithName}; /// /// This command removes a remote repository configuration from the local repository. /// Once removed, the repository will no longer track changes from that specific remote. +#[derive(Default)] pub struct RemoteRemoveCommand; -impl RemoteRemoveCommand { - /// Creates a new instance of the [`RemoteRemoveCommand`]. - #[allow(dead_code)] - pub fn new() -> Self { - Self - } -} - #[async_trait] impl MevaCommand for RemoteRemoveCommand { type Container = MevaContainer; @@ -77,7 +70,7 @@ mod tests { #[rstest] fn test_command_name_about_version() { - let cmd = RemoteRemoveCommand::new(); + let cmd = RemoteRemoveCommand; assert_eq!(cmd.name(), "remove"); assert_eq!(cmd.about(), "Remove an existing remote"); assert_eq!(cmd.version(), "1.0.0"); diff --git a/cli/src/commands/remote/subcommands/rename.rs b/cli/src/commands/remote/subcommands/rename.rs index 644495df..08b5aec3 100644 --- a/cli/src/commands/remote/subcommands/rename.rs +++ b/cli/src/commands/remote/subcommands/rename.rs @@ -14,15 +14,10 @@ use crate::commands::MevaCommand; /// This command updates the identifier of an existing remote repository. /// It changes the name used to reference the remote in other commands /// (like fetch or push) without altering the remote URL itself. +#[derive(Default)] pub struct RemoteRenameCommand; impl RemoteRenameCommand { - /// Creates a new instance of the [`RemoteRenameCommand`]. - #[allow(dead_code)] - pub fn new() -> Self { - Self - } - /// Argument name for the new name of the remote. const ARG_NEW_NAME: &'static str = "new-name"; @@ -105,7 +100,7 @@ mod tests { #[rstest] fn test_command_name_about_version() { - let cmd = RemoteRenameCommand::new(); + let cmd = RemoteRenameCommand; assert_eq!(cmd.name(), "rename"); assert_eq!(cmd.about(), "Rename a remote"); assert_eq!(cmd.version(), "1.0.0"); diff --git a/cli/src/commands/remote/subcommands/set_url.rs b/cli/src/commands/remote/subcommands/set_url.rs index ae1606ac..361c958a 100644 --- a/cli/src/commands/remote/subcommands/set_url.rs +++ b/cli/src/commands/remote/subcommands/set_url.rs @@ -17,15 +17,10 @@ use crate::{commands::MevaCommand, extensions::WithName}; /// This command updates the URL for an existing remote repository. /// It supports changing either the fetch URL (default) or the push URL, /// and can selectively replace a specific URL if the remote has multiple. +#[derive(Default)] pub struct RemoteSetUrlCommand; impl RemoteSetUrlCommand { - /// Creates a new instance of the [`RemoteSetUrlCommand`]. - #[allow(dead_code)] - pub fn new() -> Self { - Self - } - /// Argument name for the direction selection (fetch/push). const ARG_DIRECTION: &'static str = "direction"; @@ -108,7 +103,7 @@ mod tests { #[rstest] fn test_command_name_about_version() { - let cmd = RemoteSetUrlCommand::new(); + let cmd = RemoteSetUrlCommand; assert_eq!(cmd.name(), "set-url"); assert_eq!(cmd.about(), "Set the URL of a remote"); assert_eq!(cmd.version(), "1.0.0"); diff --git a/cli/src/commands/remote/subcommands/show.rs b/cli/src/commands/remote/subcommands/show.rs index 67e0879b..cc7e8cc5 100644 --- a/cli/src/commands/remote/subcommands/show.rs +++ b/cli/src/commands/remote/subcommands/show.rs @@ -15,15 +15,10 @@ use crate::{commands::MevaCommand, extensions::WithName}; /// This command displays detailed information about a specific remote repository, /// such as its URLs and the status of tracked branches. It can optionally retrieve /// live data from the remote or rely solely on local configuration. +#[derive(Default)] pub struct RemoteShowCommand; impl RemoteShowCommand { - /// Creates a new instance of the [`RemoteShowCommand`]. - #[allow(dead_code)] - pub fn new() -> Self { - Self - } - /// Argument name for the no-fetch flag. const ARG_NO_FETCH: &'static str = "no-fetch"; } @@ -92,7 +87,7 @@ mod tests { #[rstest] fn test_command_name_about_version() { - let cmd = RemoteShowCommand::new(); + let cmd = RemoteShowCommand; assert_eq!(cmd.name(), "show"); assert_eq!(cmd.about(), "Show the URL of a remote"); assert_eq!(cmd.version(), "1.0.0"); diff --git a/cli/src/commands/restore.rs b/cli/src/commands/restore.rs index 0ddffd7c..bec2131a 100644 --- a/cli/src/commands/restore.rs +++ b/cli/src/commands/restore.rs @@ -19,14 +19,10 @@ use std::path::PathBuf; /// Defaults to `HEAD` if not provided. /// - **``**: Optional list of file or directory paths to restore. /// If omitted, the entire repository is restored. +#[derive(Default)] pub struct RestoreCommand; impl RestoreCommand { - /// Creates a new instance of the [RestoreCommand]. - pub fn new() -> Self { - Self - } - /// `--staged` flag key. const ARG_STAGED: &'static str = "staged"; diff --git a/cli/src/commands/show.rs b/cli/src/commands/show.rs index 8674cd29..ed9f1da1 100644 --- a/cli/src/commands/show.rs +++ b/cli/src/commands/show.rs @@ -14,14 +14,10 @@ use crate::commands::MevaCommand; /// Implements the `show` command for Meva DVCS. /// /// Displays information about a specific snapshot (commit) in the repository. -pub struct ShowCommand {} +#[derive(Default)] +pub struct ShowCommand; impl ShowCommand { - /// Creates a new instance of the `ShowCommand`. - pub fn new() -> Self { - Self {} - } - /// Argument name for specifying the snapshot identifier to display. const ARG_SNAPSHOT_ID: &'static str = "snapshot-id"; @@ -223,7 +219,7 @@ mod tests { #[rstest] fn test_command_name_about_version() { - let cmd = ShowCommand::new(); + let cmd = ShowCommand; assert_eq!(cmd.name(), "show"); assert_eq!(cmd.about(), "Show information about a snapshot"); assert_eq!(cmd.version(), "1.0.0"); diff --git a/cli/src/commands/status.rs b/cli/src/commands/status.rs index b9ebf8c0..3ee3a435 100644 --- a/cli/src/commands/status.rs +++ b/cli/src/commands/status.rs @@ -10,14 +10,10 @@ use miette::{IntoDiagnostic, Result}; /// /// Displays the current working tree status, including tracked, untracked, /// and ignored files. Supports short output format and branch information. -pub struct StatusCommand {} +#[derive(Default)] +pub struct StatusCommand; impl StatusCommand { - /// Creates a new instance of the `StatusCommand`. - pub fn new() -> Self { - Self {} - } - /// Argument name for enabling short format (`-s` / `--short`). const ARG_SHORT: &'static str = "short"; @@ -123,7 +119,7 @@ mod tests { #[rstest] fn test_command_name_about_version() { - let cmd = StatusCommand::new(); + let cmd = StatusCommand; assert_eq!(cmd.name(), "status"); assert_eq!( cmd.about(), diff --git a/cli/src/main.rs b/cli/src/main.rs index a7bfa087..528e3bb6 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -17,23 +17,23 @@ async fn main() -> Result<()> { miette::set_panic_hook(); let commands: Vec>> = vec![ - Box::new(InitCommand::new()), - Box::new(ConfigCommand::new()), - Box::new(IgnoreCommand::new()), - Box::new(PluginsCommand::new()), - Box::new(AddCommand::new()), - Box::new(LsFilesCommand::new()), - Box::new(ShowCommand::new()), - Box::new(StatusCommand::new()), - Box::new(CommitCommand::new()), - Box::new(DiffCommand::new()), - Box::new(LsTreeCommand::new()), - Box::new(LogCommand::new()), - Box::new(RestoreCommand::new()), - Box::new(CloneCommand::new()), - Box::new(RemoteCommand::new()), - Box::new(FetchCommand::new()), - Box::new(BranchCommand::new()), + Box::new(InitCommand), + Box::new(ConfigCommand), + Box::new(IgnoreCommand), + Box::new(PluginsCommand), + Box::new(AddCommand), + Box::new(LsFilesCommand), + Box::new(ShowCommand), + Box::new(StatusCommand), + Box::new(CommitCommand), + Box::new(DiffCommand), + Box::new(LsTreeCommand), + Box::new(LogCommand), + Box::new(RestoreCommand), + Box::new(CloneCommand), + Box::new(RemoteCommand), + Box::new(FetchCommand), + Box::new(BranchCommand), ]; let container = MevaContainer; diff --git a/engine/src/config/config_loader.rs b/engine/src/config/config_loader.rs index 56448a14..24f009d3 100644 --- a/engine/src/config/config_loader.rs +++ b/engine/src/config/config_loader.rs @@ -31,7 +31,7 @@ pub trait ConfigLoader: Send + Sync + Debug { /// Create a new global configuration file with default settings. /// If the file already exists, it will not overwrite it. - fn create_global_config(&self) -> EngineResult<()>; + fn create_global_config(&self, path: Option<&Path>) -> EngineResult<()>; /// Returns the default content for a local configuration file. fn get_default_local_config(&self) -> &str; @@ -138,8 +138,9 @@ impl ConfigLoader for MevaConfigLoader { Ok(()) } - fn create_global_config(&self) -> EngineResult<()> { - let path = ConfigLocation::Global.get_default_path()?; + fn create_global_config(&self, path: Option<&Path>) -> EngineResult<()> { + let global_path = ConfigLocation::Global.get_default_path()?; + let path = path.unwrap_or(&global_path); let (mut config_file, created) = create_file_with_dirs(path)?; if created { diff --git a/engine/src/engine_container.rs b/engine/src/engine_container.rs index 17d1872f..9ae31ea9 100644 --- a/engine/src/engine_container.rs +++ b/engine/src/engine_container.rs @@ -134,7 +134,8 @@ impl EngineContainer for MevaContainer { } fn config_handler(&self) -> EngineResult { - Ok(ConfigHandler) + let config_loader = Arc::new(MevaConfigLoader::default()); + Ok(ConfigHandler::new(config_loader)) } fn plugins_handler(&self) -> EngineResult { diff --git a/engine/src/handlers/config/handlers.rs b/engine/src/handlers/config/handlers.rs index 0bac8f7f..e162313b 100644 --- a/engine/src/handlers/config/handlers.rs +++ b/engine/src/handlers/config/handlers.rs @@ -1,20 +1,26 @@ +use std::sync::Arc; + use plugins::{CommandType, InvocationPostPayload, InvocationPrePayload, PluginError, models::*}; -use super::{ - ConfigOperations, GetRequest, GetResponse, ListRequest, ListResponse, SetRequest, SetResponse, - UnsetRequest, UnsetResponse, -}; +use super::*; use crate::{ - ConfigLocation, + ConfigLoader, ConfigLocation, config::{ConfigDocument, ConfigDocumentOperations}, errors::{EngineError, EngineResult}, + handlers::config::CreateRequest, plugins_interceptor::{PluginsInterceptor, PluginsInvocationMapper}, }; -pub struct ConfigHandler; +pub struct ConfigHandler { + config_loader: Arc, +} impl ConfigHandler { + pub fn new(config_loader: Arc) -> Self { + Self { config_loader } + } + pub fn handle_get( &self, request: GetRequest, @@ -54,6 +60,10 @@ impl ConfigHandler { self.list(req) }) } + + pub fn handle_create(&self, request: CreateRequest) -> EngineResult { + self.create(request) + } } impl ConfigOperations for ConfigHandler { @@ -102,6 +112,12 @@ impl ConfigOperations for ConfigHandler { Ok(response) } + + fn create(&self, request: CreateRequest) -> EngineResult { + self.config_loader + .create_global_config(request.file.as_deref())?; + Ok(CreateResponse) + } } impl PluginsInvocationMapper for ConfigHandler { diff --git a/engine/src/handlers/config/operations.rs b/engine/src/handlers/config/operations.rs index 58a830a9..4e51f1b7 100644 --- a/engine/src/handlers/config/operations.rs +++ b/engine/src/handlers/config/operations.rs @@ -1,4 +1,4 @@ -use std::collections::BTreeMap; +use std::{collections::BTreeMap, path::PathBuf}; use crate::{ConfigLocation, errors::EngineResult}; @@ -66,6 +66,13 @@ pub struct ListResponse { pub key_values: Vec<(String, String)>, } +#[derive(Debug)] +pub struct CreateRequest { + pub file: Option, +} + +pub struct CreateResponse; + impl ListResponse { /// Organizes configuration keys into groups based on their prefix. /// @@ -115,4 +122,6 @@ pub trait ConfigOperations { /// Lists all configuration values for a given location. fn list(&self, request: ListRequest) -> EngineResult; + + fn create(&self, request: CreateRequest) -> EngineResult; } diff --git a/engine/src/repositories/meva_repository.rs b/engine/src/repositories/meva_repository.rs index 9afd38da..37e28294 100644 --- a/engine/src/repositories/meva_repository.rs +++ b/engine/src/repositories/meva_repository.rs @@ -287,7 +287,7 @@ impl Repository for MevaRepository { self.check_if_exists()?; let branch_name = initial_branch.unwrap_or("master"); - self.config_loader.create_global_config()?; + self.config_loader.create_global_config(None)?; let tmp_parent = self.layout.working_dir(); let tmp_dir = TempDir::new_in(tmp_parent)?;