diff --git a/src/lib.rs b/src/lib.rs index cdaf379..0f3fa76 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,25 +1,24 @@ mod error; mod git; -use std::process::Command; +use std::borrow::Cow; use error::{AppError, ErrorType}; -use git::{Git, GitCommand, GitOutput}; -use url::Url; +use git::{Domain, Git, GitCommand, GitOutput, Url}; #[derive(Debug)] -pub struct GitView { - remote: Option, - branch: Option, - commit: Option, +pub struct GitView<'a> { + remote: Option<&'a str>, + branch: Option<&'a str>, + commit: Option<&'a str>, is_print: bool, } -impl GitView { +impl<'a> GitView<'a> { pub fn new( - branch: Option, - remote: Option, - commit: Option, + branch: Option<&'a str>, + remote: Option<&'a str>, + commit: Option<&'a str>, is_print: bool, ) -> Self { Self { @@ -34,12 +33,9 @@ impl GitView { // Exit out if we're not inside a git repository self.is_valid_repository(&Git::IsValidRepository)?; // Retrieve the current branch - self.populate_branch(&Git::LocalBranch)?; + let branch = self.populate_branch(&Git::LocalBranch)?; // Retrieve the remote - self.remote = Some(self.populate_remote( - &Git::DefaultRemote, - &Git::TrackedRemote(self.branch.as_ref().unwrap()), - )?); + let remote = self.populate_remote(&Git::DefaultRemote, &Git::TrackedRemote(&branch))?; // TODO: Figure out how to default to 'master' or 'main' if branch doesn't exist on remote // @@ -49,19 +45,22 @@ impl GitView { // - Although, I think that this command isn't foolproof, it might be the best option though without trying to use some command line parsers // Retrieve the remote reference - let remote_ref = - self.get_remote_reference(&Git::UpstreamBranch(self.branch.as_ref().unwrap()))?; + let remote_ref = self.get_remote_reference(&branch, &Git::UpstreamBranch(&branch))?; // Retrieve the full git_url // e.g https://github.com/sgoudham/git-view.git - let git_url = self.get_git_url(&Git::IsValidRemote(self.remote.as_ref().unwrap()))?; + let git_url = self.get_git_url(&remote, &Git::IsValidRemote(&remote))?; // Extract protocol, domain and urlpath - let (protocol, domain, urlpath) = self.parse_git_url(&git_url)?; + let url = self.parse_git_url(&git_url)?; // Generate final url to open in the web browser // let final_url = self.generate_final_url(protocol, domain, urlpath); // Open the URL webbrowser::open( - format!("{}://{}/{}/tree/{}", protocol, domain, urlpath, remote_ref).as_str(), + format!( + "{}://{}/{}/tree/{}", + url.protocol, url.domain, url.path, remote_ref + ) + .as_str(), )?; Ok(()) @@ -77,17 +76,14 @@ impl GitView { } } - fn populate_branch(&mut self, command: &impl GitCommand) -> Result<(), AppError> { + fn populate_branch(&self, command: &impl GitCommand) -> Result, AppError> { if self.branch.is_none() { match command.execute()? { - GitOutput::Ok(output) => { - self.branch = Some(output); - Ok(()) - } + GitOutput::Ok(output) => Ok(Cow::Owned(output)), GitOutput::Err(err) => Err(AppError::new(ErrorType::CommandFailed, err)), } } else { - Ok(()) + Ok(Cow::Borrowed(self.branch.as_ref().unwrap())) } } @@ -97,43 +93,46 @@ impl GitView { &self, default_remote: &impl GitCommand, tracked_remote: &impl GitCommand, - ) -> Result { + ) -> Result, AppError> { // Priority goes to user given remote if self.remote.is_none() { // Priority then goes to the default remote match default_remote.execute()? { - GitOutput::Ok(def) => Ok(def), + GitOutput::Ok(def) => Ok(Cow::Owned(def)), // Priority then goes to the tracked remote GitOutput::Err(_) => match tracked_remote.execute()? { - GitOutput::Ok(tracked) => Ok(tracked), + GitOutput::Ok(tracked) => Ok(Cow::Owned(tracked)), // Default to the 'origin' remote - GitOutput::Err(_) => Ok("origin".to_string()), + GitOutput::Err(_) => Ok(Cow::Owned("origin".into())), }, } } else { - Ok(self.remote.as_ref().unwrap().to_string()) + Ok(Cow::Borrowed(self.remote.as_ref().unwrap())) } } - fn get_remote_reference(&self, command: &impl GitCommand) -> Result { + fn get_remote_reference( + &self, + branch: &'a str, + command: &impl GitCommand, + ) -> Result, AppError> { match command.execute()? { - GitOutput::Ok(output) => Ok(output.trim_start_matches("refs/heads/").to_string()), - GitOutput::Err(_) => Ok(self.branch.as_ref().unwrap().to_string()), + GitOutput::Ok(output) => Ok(Cow::Owned( + output.trim_start_matches("refs/heads/").to_string(), + )), + GitOutput::Err(_) => Ok(Cow::Borrowed(branch)), } } - fn get_git_url(&self, command: &impl GitCommand) -> Result { + fn get_git_url(&self, remote: &'a str, command: &impl GitCommand) -> Result { match command.execute()? { GitOutput::Ok(output) => { - if &output != self.remote.as_ref().unwrap() { + if output != remote { Ok(output) } else { Err(AppError::new( ErrorType::MissingGitRemote, - format!( - "Looks like your git remote isn't set for '{}'", - self.remote.as_ref().unwrap() - ), + format!("Looks like your git remote isn't set for '{}'", remote), )) } } @@ -149,22 +148,19 @@ impl GitView { * - ftp[s]://host.xz[:port]/path/to/repo.git/ * - [user@]host.xz:path/to/repo.git/ */ - fn parse_git_url(&self, git_url: &str) -> Result<(String, String, String), AppError> { + fn parse_git_url(&self, git_url: &str) -> Result { // rust-url cannot parse 'scp-like' urls -> https://github.com/servo/rust-url/issues/220 // Manually parse the url ourselves if git_url.contains("://") { - match Url::parse(git_url) { - Ok(url) => Ok(( - url.scheme().to_string(), - url.host_str() - .map_or_else(|| "github.com", |host| host) - .to_string(), + match url::Url::parse(git_url) { + Ok(url) => Ok(Url::new( + url.scheme(), + Domain::from_str(url.host_str().map_or_else(|| "github.com", |host| host)), url.path() .trim_start_matches('/') .trim_end_matches('/') - .trim_end_matches(".git") - .to_string(), + .trim_end_matches(".git"), )), Err(_) => Err(AppError::new( ErrorType::InvalidGitUrl, @@ -181,11 +177,7 @@ impl GitView { None => domain, }; - Ok(( - protocol.to_string(), - split_domain.to_string(), - path.to_string(), - )) + Ok(Url::new(protocol, Domain::from_str(split_domain), path)) } None => Err(AppError::new( ErrorType::InvalidGitUrl, @@ -201,98 +193,102 @@ impl GitView { } #[cfg(test)] -mod is_valid_repository { - use std::process::{ExitStatus, Output}; - - use crate::{git::MockGitCommand, GitView}; - - fn instantiate_handler() -> GitView { - GitView::new( - Some(String::from("main")), - Some(String::from("origin")), - Some(String::from("latest")), - false, - ) +mod lib_tests { + use crate::GitView; + + fn instantiate_handler() -> GitView<'static> { + GitView::new(Some("main"), Some("origin"), Some("latest"), false) } - // #[test] - fn yes() { - let handler = instantiate_handler(); - let mut mock = MockGitCommand::new(); - let is_valid_repository = handler.is_valid_repository(&mock); + mod is_valid_repository { - assert!(is_valid_repository.is_ok()); - } + use crate::{ + error::ErrorType, + git::{GitOutput, MockGitCommand}, + lib_tests::instantiate_handler, + }; - // #[test] - fn no() { - let handler = instantiate_handler(); - let mut mock = MockGitCommand::new(); - mock.expect_execute().never(); + #[test] + fn yes() { + let handler = instantiate_handler(); - let is_valid_repository = handler.is_valid_repository(&mock); + let mut mock = MockGitCommand::new(); + mock.expect_execute() + .returning(|| Ok(GitOutput::Ok("Valid".to_owned()))); - assert!(is_valid_repository.is_err()); - } -} + let is_valid_repository = handler.is_valid_repository(&mock); -#[cfg(test)] -mod parse_git_url { - use crate::{error::AppError, GitView}; - use test_case::test_case; - - fn instantiate_handler() -> GitView { - GitView::new( - Some(String::from("main")), - Some(String::from("origin")), - Some(String::from("latest")), - false, - ) - } + assert!(is_valid_repository.is_ok()); + } - // http[s]://host.xz[:port]/path/to/repo.git/ - #[test_case("https://github.com:8080/sgoudham/git-view.git" ; "with port")] - #[test_case("https://github.com/sgoudham/git-view.git" ; "normal")] - #[test_case("https://github.com/sgoudham/git-view.git/" ; "with trailing slash")] - fn https(git_url: &str) -> Result<(), AppError> { - let handler = instantiate_handler(); + #[test] + fn no() { + let handler = instantiate_handler(); + let mut mock = MockGitCommand::new(); + mock.expect_execute() + .returning(|| Ok(GitOutput::Err("Error".to_owned()))); - let (protocol, domain, urlpath) = handler.parse_git_url(git_url)?; + let is_valid_repository = handler.is_valid_repository(&mock); - assert_eq!(protocol, "https"); - assert_eq!(domain, "github.com"); - assert_eq!(urlpath, "sgoudham/git-view"); + assert!(is_valid_repository.is_err()); - Ok(()) + let error = is_valid_repository.as_ref().unwrap_err(); + assert_eq!(error.error_type, ErrorType::MissingGitRepository); + assert_eq!( + error.error_str, + "Looks like you're not in a valid git repository!" + ); + } } - // [user@]host.xz:path/to/repo.git/ - #[test_case("git@github.com:sgoudham/git-view.git" ; "with username")] - #[test_case("github.com:sgoudham/git-view.git" ; "normal")] - #[test_case("github.com:sgoudham/git-view.git/" ; "with trailing slash")] - fn ssh(git_url: &str) -> Result<(), AppError> { - let handler = instantiate_handler(); + mod parse_git_url { + use crate::{error::AppError, lib_tests::instantiate_handler}; + use test_case::test_case; - let (protocol, domain, urlpath) = handler.parse_git_url(git_url)?; + // http[s]://host.xz[:port]/path/to/repo.git/ + #[test_case("https://github.com:8080/sgoudham/git-view.git" ; "with port")] + #[test_case("https://github.com/sgoudham/git-view.git" ; "normal")] + #[test_case("https://github.com/sgoudham/git-view.git/" ; "with trailing slash")] + fn https(git_url: &str) -> Result<(), AppError> { + let handler = instantiate_handler(); - assert_eq!(protocol, "https"); - assert_eq!(domain, "github.com"); - assert_eq!(urlpath, "sgoudham/git-view"); + let url = handler.parse_git_url(git_url)?; - Ok(()) - } + assert_eq!(url.protocol, "https"); + assert_eq!(url.domain.to_string(), "github.com"); + assert_eq!(url.path, "sgoudham/git-view"); + + Ok(()) + } + + // [user@]host.xz:path/to/repo.git/ + #[test_case("git@github.com:sgoudham/git-view.git" ; "with username")] + #[test_case("github.com:sgoudham/git-view.git" ; "normal")] + #[test_case("github.com:sgoudham/git-view.git/" ; "with trailing slash")] + fn ssh(git_url: &str) -> Result<(), AppError> { + let handler = instantiate_handler(); - #[test] - fn invalid_git_url() { - let handler = instantiate_handler(); - let git_url_normal = "This isn't a git url"; + let url = handler.parse_git_url(git_url)?; - let error = handler.parse_git_url(git_url_normal); + assert_eq!(url.protocol, "https"); + assert_eq!(url.domain.to_string(), "github.com"); + assert_eq!(url.path, "sgoudham/git-view"); - assert!(error.is_err()); - assert_eq!( - error.unwrap_err().error_str, - "Sorry, couldn't parse git url 'This isn't a git url'" - ); + Ok(()) + } + + #[test] + fn invalid_git_url() { + let handler = instantiate_handler(); + let git_url_normal = "This isn't a git url"; + + let error = handler.parse_git_url(git_url_normal); + + assert!(error.is_err()); + assert_eq!( + error.unwrap_err().error_str, + "Sorry, couldn't parse git url 'This isn't a git url'" + ); + } } }