Skip to content

Commit

Permalink
refactor: Use Cow<'_, str> and Url/Domain structs
Browse files Browse the repository at this point in the history
  • Loading branch information
sgoudham committed Jun 12, 2022
1 parent 34cb2b6 commit de44601
Showing 1 changed file with 125 additions and 129 deletions.
254 changes: 125 additions & 129 deletions 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<String>,
branch: Option<String>,
commit: Option<String>,
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<String>,
remote: Option<String>,
commit: Option<String>,
branch: Option<&'a str>,
remote: Option<&'a str>,
commit: Option<&'a str>,
is_print: bool,
) -> Self {
Self {
Expand All @@ -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
//
Expand All @@ -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(())
Expand All @@ -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() {
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()))
}
}

Expand All @@ -97,43 +93,46 @@ impl GitView {
&self,
default_remote: &impl GitCommand,
tracked_remote: &impl GitCommand,
) -> Result<String, AppError> {
) -> Result<Cow<'_, str>, 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<String, AppError> {
fn get_remote_reference(
&self,
branch: &'a str,
command: &impl GitCommand,
) -> Result<Cow<'a, str>, 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<String, AppError> {
fn get_git_url(&self, remote: &'a str, command: &impl GitCommand) -> Result<String, AppError> {
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),
))
}
}
Expand All @@ -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<Url, AppError> {
// 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,
Expand All @@ -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,
Expand All @@ -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'"
);
}
}
}

0 comments on commit de44601

Please sign in to comment.