refactor: Use Cow<'_, str> and Url/Domain structs

pull/1/head
sgoudham 2 years ago
parent 34cb2b6ff5
commit de446012c1
Signed by: hammy
GPG Key ID: 44E818FD5457EEA4

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

Loading…
Cancel
Save