diff --git a/src/cmd/mod.rs b/src/cmd/mod.rs index bbe70eb..0e271f0 100644 --- a/src/cmd/mod.rs +++ b/src/cmd/mod.rs @@ -1,3 +1,6 @@ +use std::error::Error; +use std::str::FromStr; + use anyhow::bail; use anyhow::Result; use regex::Regex; @@ -10,32 +13,34 @@ pub mod open; #[derive(Debug, Eq, PartialEq)] pub struct RepoPattern { /// The workspace name - pub workspace: Option, - - /// The remote name - pub remote: Option, + pub workspace_name: Option, /// The repo path pub path: String, } -impl RepoPattern { - pub fn parse(path: &str) -> Result { - match Regex::new(r"^([^/]+:)?([^/]+)?(.*)$")?.captures(path) { - Some(captures) => {captures.len(); - Ok(Self { - workspace: captures +impl FromStr for RepoPattern { + type Err = Box; + + fn from_str(path: &str) -> std::prelude::v1::Result { + match Regex::new(r"^([^/]+:)?(.*)$")?.captures(path) { + Some(captures) => Ok(Self { + workspace_name: 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"), + None => Err("Invalid repository path pattern".into()), } } +} - pub fn maybe_provider(&self) -> Option<(String, String)> { +impl RepoPattern { + /// Try to parse the remote from the repo path. A `RepoPattern` doesn't + /// have enough information to know whether this is actually the remote, so + /// leave that decision up to the caller. + pub fn maybe_remote(&self) -> Option<(String, String)> { let parts: Vec<&str> = self.path.splitn(2, "/").collect(); if parts.len() == 2 && parts[0] != "" { Some((parts[0].to_string(), parts[1].to_string())) @@ -48,21 +53,21 @@ impl RepoPattern { #[cfg(test)] mod test_repo_pattern { use super::RepoPattern; - use anyhow::Result; + use std::error::Error; #[test] - fn test_parse() -> Result<()> { + fn test_parse() -> Result<(), Box> { assert_eq!( - RepoPattern::parse("workspace12:remote1/abc/123")?, + str::parse::("workspace12:remote1/abc/123")?, RepoPattern { - workspace: Some("workspace12".to_string()), + workspace_name: Some("workspace12".to_string()), path: "remote1/abc/123".to_string() } ); assert_eq!( - RepoPattern::parse("123")?, + str::parse::("123")?, RepoPattern { - workspace: None, + workspace_name: None, path: "123".to_string() } ); diff --git a/src/cmd/open.rs b/src/cmd/open.rs index ecf329d..3792ce6 100644 --- a/src/cmd/open.rs +++ b/src/cmd/open.rs @@ -1,18 +1,30 @@ use crate::cmd::RepoPattern; use crate::Config; -use anyhow::Result; +use anyhow::{bail, Result}; use cmd_lib::*; use tracing::debug; /// Open one or more repositories in the workspace pub fn run_open(config: &Config, path: Option) -> Result<()> { - debug!("Open requested for: {:?}", &path); + let path = match path { + Some(path) => path, + None => bail!("No pattern given"), + }; + + let pattern = match str::parse::(&path) { + Ok(pattern) => pattern, + Err(_) => bail!("Failed to parse pattern"), + }; - // Check the cache first - if let Some(cache) = &config.cache {} + debug!(pattern = ?pattern, "Opening repos"); + + // Check the cache + if let Some(cache) = &config.cache { + // TODO cache.search + } let repos = match path { - Some(p) => config.resolve_local(&RepoPattern::parse(&p)?), // TODO remote + Some(p) => config.search_local(&RepoPattern::parse(&p)?), // TODO remote None => Vec::new(), }; @@ -20,5 +32,6 @@ pub fn run_open(config: &Config, path: Option) -> Result<()> { let out = run_fun!(git -C $repo status --porcelain)?; println!("{}", out); } + Ok(()) } diff --git a/src/lib.rs b/src/lib.rs index a5ca3f1..733267b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,6 +8,7 @@ use sha2::{Digest, Sha512}; use std::path::Path; use std::path::PathBuf; use tracing::debug; +use tracing::warn; pub mod cmd; pub mod remote; @@ -23,8 +24,6 @@ pub struct Config { } impl Default for Config { - // Place the cache according to platform - fn default() -> Self { let home = home::home_dir().expect("the home directory exists"); @@ -68,43 +67,34 @@ impl Config { 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 - .iter() - .find(|&w| match &w.name { - Some(name) => name == workspace_name, - None => false, - }) - .unwrap(), - None => self.workspace.first().unwrap(), - }; + /// Find a configured workspace by name. + pub fn workspace_by_name(&self, workspace_name: &str) -> Option<&Workspace> { + self.workspace.iter().find(|&w| match &w.name { + Some(name) => name == workspace_name, + None => false, + }) + } - let (remote, path) = match pattern.maybe_provider() { - Some((provider, path)) => { - debug!("{} {}", provider, path); - ( - workspace - .remotes - .iter() - .find(|&p| p.name == provider) - .unwrap(), - path, - ) + /// Resolve a repository pattern against local repositories. + pub fn search_local(&self, pattern: &RepoPattern) -> Result> { + match &pattern.workspace_name { + Some(workspace_name) => { + if let Some(workspace) = self.workspace_by_name(&workspace_name) { + workspace.search(pattern) + } else { + bail!("Workspace not found") + } } - None => (workspace.remotes.first().unwrap(), pattern.path.clone()), - }; - - find_git_dir(&format!("{}/{}/{}", workspace.path, remote.name, path)).unwrap() + None => { + todo!() + } + } } } /// Recursively find "top-level" git repositories. fn find_git_dir(path: &str) -> Result> { - debug!("Searching for git repositories in: {}", path); + debug!(path = %path, "Recursively searching for git repositories"); let mut found: Vec = Vec::new(); match std::fs::metadata(format!("{}/.git", &path)) { @@ -153,6 +143,21 @@ impl Workspace { .unwrap(), } } + + /// Search the workspace for local repos matching the given pattern. + pub fn search(&self, pattern: &RepoPattern) -> Result> { + let repos = find_git_dir(&format!("{}/{}", self.path, pattern.path))?; + + // Try each remote if there were no matches immediately + // if repos.len() == 0 { + // for remote in self.remotes.iter() { + // let repos = find_git_dir(&format!("{}/{}/{}", self.path, remote.name(), pattern.path))?; + // if repos.len() == 0 {} + // } + // } + + Ok(repos) + } } /// Caches repositories that are dropped from a `Workspace` in a separate directory. diff --git a/src/remote/mod.rs b/src/remote/mod.rs index 821fe5e..f31edfb 100644 --- a/src/remote/mod.rs +++ b/src/remote/mod.rs @@ -13,6 +13,11 @@ pub trait ListRepos { fn list_repo_paths(&self) -> Result>; } +#[enum_dispatch] +pub trait Metadata { + fn name(&self) -> String; +} + #[derive(Debug, Serialize, Deserialize)] #[enum_dispatch(ListRepos)] pub enum Remote {