From cdbfa38327191aeaa52601b40c5880228a61082e Mon Sep 17 00:00:00 2001 From: Tyler Cook <10459406+cilki@users.noreply.github.com> Date: Sun, 5 May 2024 16:15:52 -0500 Subject: [PATCH] wip: add enum dispatch --- Cargo.lock | 13 +++++++ Cargo.toml | 1 + src/api/mod.rs | 10 ------ src/cmd/mod.rs | 11 ++++-- src/lib.rs | 68 +++++++++++++++++++++++++---------- src/main.rs | 22 ++---------- src/{api => remote}/github.rs | 13 ++++--- src/{api => remote}/gitlab.rs | 0 src/remote/mod.rs | 21 +++++++++++ 9 files changed, 102 insertions(+), 57 deletions(-) delete mode 100644 src/api/mod.rs rename src/{api => remote}/github.rs (89%) rename src/{api => remote}/gitlab.rs (100%) create mode 100644 src/remote/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 115e0e8..dfd8811 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -391,6 +391,18 @@ version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2" +[[package]] +name = "enum_dispatch" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa18ce2bc66555b3218614519ac839ddb759a7d6720732f979ef8d13be147ecd" +dependencies = [ + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.60", +] + [[package]] name = "env_logger" version = "0.10.2" @@ -2309,6 +2321,7 @@ dependencies = [ "anyhow", "built", "cmd_lib", + "enum_dispatch", "git-repository", "home", "log", diff --git a/Cargo.toml b/Cargo.toml index 5075973..3b47669 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ repository = "https://github.com/cilki/wsx/" [dependencies] anyhow = "1.0.82" cmd_lib = "1.3.0" +enum_dispatch = "0.3.13" git-repository = { version = "0", optional = true } home = "0.5.9" log = "0.4.17" diff --git a/src/api/mod.rs b/src/api/mod.rs deleted file mode 100644 index a0aff1f..0000000 --- a/src/api/mod.rs +++ /dev/null @@ -1,10 +0,0 @@ -use anyhow::Result; -use std::fmt::Display; - -//pub mod github; -//pub mod gitlab; - -pub trait Provider: Display { - /// List all repository paths available to the provider. - fn list_repo_paths(&self) -> Result>; -} diff --git a/src/cmd/mod.rs b/src/cmd/mod.rs index 5ba49fe..bbe70eb 100644 --- a/src/cmd/mod.rs +++ b/src/cmd/mod.rs @@ -6,25 +6,30 @@ pub mod drop; pub mod open; /// Represents a pattern that matches one or more repositories. It has the -/// format: [workspace]:[provider]/[path]. +/// format: [workspace]:[remote]/[path]. #[derive(Debug, Eq, PartialEq)] pub struct RepoPattern { /// The workspace name pub workspace: Option, + /// The remote name + pub remote: Option, + /// The repo path pub path: String, } impl RepoPattern { pub fn parse(path: &str) -> Result { - match Regex::new(r"^([^/]+:)?(.*)$")?.captures(path) { - Some(captures) => Ok(Self { + match Regex::new(r"^([^/]+:)?([^/]+)?(.*)$")?.captures(path) { + Some(captures) => {captures.len(); + Ok(Self { workspace: captures .get(1) .map(|m| m.as_str().to_string()) .map(|s| s[..s.len() - 1].to_string()), path: captures.get(2).unwrap().as_str().to_string(), + remote: todo!(), }, }), None => bail!("Invalid repository path pattern"), } diff --git a/src/lib.rs b/src/lib.rs index 509667b..a5ca3f1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,14 +1,16 @@ use crate::cmd::RepoPattern; +use anyhow::bail; use anyhow::Result; use cmd_lib::run_fun; +use remote::Remote; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha512}; use std::path::Path; use std::path::PathBuf; use tracing::debug; -pub mod api; pub mod cmd; +pub mod remote; /// Represents the user's config file #[derive(Debug, Serialize, Deserialize)] @@ -17,23 +19,58 @@ pub struct Config { /// The cache directory for all workspaces #[serde(flatten)] - pub cache: Option, + pub cache: Option, } impl Default for Config { // Place the cache according to platform fn default() -> Self { + let home = home::home_dir().expect("the home directory exists"); + Self { - workspace: vec![], - cache: None, + workspace: vec![Workspace { + name: Some("default".into()), + path: home.join("workspace").display().to_string(), + remotes: vec![], + }], + cache: Some(RepoCache { + path: home.join(".cache/wsx").display().to_string(), + }), } } } impl Config { + /// Load the application config from the filesystem or provide a default if + /// none exists. + pub fn load() -> Result { + let config_path = match home::home_dir() { + Some(home) => format!("{}/.config/wsx.toml", home.display()), + None => bail!("Home directory not found"), + }; + debug!(config_path = %config_path, "Searching for configuration file"); + + let config: Config = match std::fs::metadata(&config_path) { + Ok(_) => toml::from_str(&std::fs::read_to_string(config_path)?)?, + Err(_) => Config::default(), + }; + debug!(config = ?config, "Loaded configuration"); + + // Make sure all necessary directories exist + if let Some(cache) = config.cache.as_ref() { + std::fs::create_dir_all(&cache.path)?; + } + for workspace in config.workspace.iter() { + std::fs::create_dir_all(&workspace.path)?; + } + + Ok(config) + } + /// Resolve a repository pattern against local repositories. pub fn resolve_local(&self, pattern: &RepoPattern) -> Vec { + // Either find the specified workspace or choose the first available let workspace: &Workspace = match &pattern.workspace { Some(workspace_name) => self .workspace @@ -46,26 +83,26 @@ impl Config { None => self.workspace.first().unwrap(), }; - let (provider, path) = match pattern.maybe_provider() { + let (remote, path) = match pattern.maybe_provider() { Some((provider, path)) => { debug!("{} {}", provider, path); ( workspace - .provider + .remotes .iter() .find(|&p| p.name == provider) .unwrap(), path, ) } - None => (workspace.provider.first().unwrap(), pattern.path.clone()), + None => (workspace.remotes.first().unwrap(), pattern.path.clone()), }; - find_git_dir(&format!("{}/{}/{}", workspace.path, provider.name, path)).unwrap() + find_git_dir(&format!("{}/{}/{}", workspace.path, remote.name, path)).unwrap() } } -/// Recursively find git repositories. +/// Recursively find "top-level" git repositories. fn find_git_dir(path: &str) -> Result> { debug!("Searching for git repositories in: {}", path); let mut found: Vec = Vec::new(); @@ -100,7 +137,7 @@ pub struct Workspace { pub path: String, /// A list of providers for the workspace - pub provider: Vec, + pub remotes: Vec, } impl Workspace { @@ -118,20 +155,15 @@ impl Workspace { } } -#[derive(Debug, Serialize, Deserialize)] -pub struct Provider { - /// The provider's name for use in repo paths - pub name: String, -} - /// Caches repositories that are dropped from a `Workspace` in a separate directory. /// Entries in this cache are bare repositories for space efficiency. #[derive(Debug, Serialize, Deserialize)] -pub struct Cache { +pub struct RepoCache { pub path: String, + // TODO cache parameters? } -impl Cache { +impl RepoCache { /// Move the given repository into the cache. pub fn cache(&self, repo_path: String) -> Result<()> { // Make sure the cache directory exists first diff --git a/src/main.rs b/src/main.rs index 01b7294..891e7e4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,25 +13,7 @@ fn main() -> Result<()> { .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) .init(); - // Locate configuration file - let config_path = match home::home_dir() { - Some(home) => format!("{}/.config/wsx.toml", home.display()), - None => todo!(), - }; - - // Load configuration file - let config: Config = match std::fs::metadata(&config_path) { - Ok(_) => { - debug!(config_path = %config_path, "Loading configuration file"); - toml::from_str(&std::fs::read_to_string(config_path)?)? - } - Err(_) => { - debug!("Using default config"); - Config::default() - } - }; - - debug!(config = ?config, "Using configuration"); + let config = Config::load()?; match std::env::var("_ARGCOMPLETE_") { Ok(shell_type) => { @@ -51,6 +33,7 @@ fn main() -> Result<()> { match args.subcommand()? { Some(command) => match command.as_str() { + "clone" => wsx::cmd::open::run_open(&config, args.opt_free_from_str()?), "drop" => wsx::cmd::drop::run_drop(&config, args.opt_free_from_str()?), "help" => print_help(), _ => wsx::cmd::open::run_open(&config, Some(command)), @@ -68,6 +51,7 @@ fn print_help() -> Result<()> { ); println!(""); println!("wsx - Clone one or more repositories"); + println!("wsx clone - Clone one or more repositories"); println!("wsx drop [repo pattern] - Drop one or more repositories"); Ok(()) } diff --git a/src/api/github.rs b/src/remote/github.rs similarity index 89% rename from src/api/github.rs rename to src/remote/github.rs index d1b01c2..75fddb3 100644 --- a/src/api/github.rs +++ b/src/remote/github.rs @@ -1,19 +1,18 @@ -use crate::api::Provider; -use crate::Provider; +use super::ListRepos; use anyhow::Result; +use serde::{Deserialize, Serialize}; use std::fmt; -pub struct GithubProvider { - pub config: Provider, -} +#[derive(Debug, Serialize, Deserialize)] +pub struct GithubRemote {} -impl fmt::Display for GithubProvider { +impl fmt::Display for GithubRemote { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "Github",) } } -impl Provider for GithubProvider { +impl ListRepos for GithubRemote { fn list_repo_paths(&self) -> Result> { let mut paths: Vec = Vec::new(); diff --git a/src/api/gitlab.rs b/src/remote/gitlab.rs similarity index 100% rename from src/api/gitlab.rs rename to src/remote/gitlab.rs diff --git a/src/remote/mod.rs b/src/remote/mod.rs new file mode 100644 index 0000000..821fe5e --- /dev/null +++ b/src/remote/mod.rs @@ -0,0 +1,21 @@ +use self::github::GithubRemote; +use anyhow::Result; +use enum_dispatch::enum_dispatch; +use serde::{Deserialize, Serialize}; +use std::fmt::Display; + +pub mod github; +pub mod gitlab; + +#[enum_dispatch] +pub trait ListRepos { + /// List all repository paths available to the provider. + fn list_repo_paths(&self) -> Result>; +} + +#[derive(Debug, Serialize, Deserialize)] +#[enum_dispatch(ListRepos)] +pub enum Remote { + Github(GithubRemote), + // Gitlab(GitlabRemote), +}