From 87ce15a00dde62e03b80f96e4f200cec6c91fbd1 Mon Sep 17 00:00:00 2001 From: Patrick D'appollonio <930925+patrickdappollonio@users.noreply.github.com> Date: Sat, 25 Jan 2025 17:51:09 -0500 Subject: [PATCH] Use emojis first, then fallback to Nerd Fonts. Fixes #7 --- Cargo.lock | 80 +++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 3 ++ src/main.rs | 57 +++++++++++++++++++++++++++++------- src/parser.rs | 55 +++++++++++++++++++++++++---------- 4 files changed, 169 insertions(+), 26 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2c22e08..832d050 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,13 +11,35 @@ dependencies = [ "memchr", ] +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "emojis" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99e1f1df1f181f2539bac8bf027d31ca5ffbf9e559e3f2d09413b9107b5c02f4" +dependencies = [ + "phf", +] + [[package]] name = "gc-rust" version = "0.1.0" dependencies = [ + "emojis", "getopts", "regex", "subprocess", + "terminal-emoji", ] [[package]] @@ -29,6 +51,21 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + [[package]] name = "libc" version = "0.2.169" @@ -41,6 +78,24 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[package]] +name = "phf" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +dependencies = [ + "phf_shared", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", +] + [[package]] name = "regex" version = "1.11.1" @@ -70,6 +125,12 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + [[package]] name = "subprocess" version = "0.2.9" @@ -80,6 +141,25 @@ dependencies = [ "winapi", ] +[[package]] +name = "terminal-emoji" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8143568e8d5270b3b8f573ab8bb11a556a5c9e4becdc12241a7eef4c54a55170" +dependencies = [ + "terminal-supports-emoji", +] + +[[package]] +name = "terminal-supports-emoji" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8873a7a1f2d286cfedc10663a722309b1c74092852cf149aee738cbe901c6eb" +dependencies = [ + "atty", + "lazy_static", +] + [[package]] name = "unicode-width" version = "0.1.14" diff --git a/Cargo.toml b/Cargo.toml index f95131e..eb6437b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,11 +4,14 @@ version = "0.1.0" edition = "2021" [dependencies] +emojis = "0.6.4" getopts = "0.2.21" regex = "1.11" subprocess = "0.2.9" +terminal-emoji = "0.4.1" [profile.release] opt-level = "z" # Optimize for size. lto = true # Enable link time optimization. codegen-units = 1 # Reduce parallel code generation units. +debug = 0 # No debug information diff --git a/src/main.rs b/src/main.rs index a936fec..5e8c47f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,6 +4,7 @@ use std::path::Path; use std::{env, fmt}; use std::{fs, io}; use subprocess::{Exec, Redirection}; +use terminal_emoji::Emoji; mod parser; @@ -67,12 +68,21 @@ fn main() { match run() { Ok(_) => {} Err(err) => { - eprintln!("\u{f071} Error: {}", err); + eprintln!("{} Error: {}", get_emoji("x", "\u{f071}"), err); std::process::exit(1); } } } +/// Get an emoji by shortcode or fallback to a unicode code point or string +fn get_emoji(emoji_code: &str, fallback: &str) -> String { + Emoji::new( + emojis::get_by_shortcode(emoji_code).unwrap().as_str(), + fallback, + ) + .to_string() +} + fn run() -> Result<(), ApplicationError> { // Get the base directory let base_dir = env::var("GC_DOWNLOAD_PATH") @@ -80,6 +90,11 @@ fn run() -> Result<(), ApplicationError> { .map_err(|_| ApplicationError::BaseDirNotFound)?; let base_dir = format!("{}/src", base_dir); + // Get the default user if one is set + let default_user = env::var("GC_GITHUB_USERNAME") + .or_else(|_| env::var("GITHUB_USERNAME")) + .unwrap_or("".to_string()); + // Try opening the base directory fs::read_dir(&base_dir).map_err(ApplicationError::BaseDirCannotBeOpened)?; @@ -108,21 +123,25 @@ fn run() -> Result<(), ApplicationError> { let branch = matches.opt_str("b"); // Parse the repository URL - let (host, team, project) = parser::repository(repo_url.to_string())?; + let (host, team, project) = parser::repository(default_user, repo_url.to_string())?; let project_path = format!("{}/{}/{}/{}", base_dir, host, team, project); let clone_url = format!("git@{}:{}/{}.git", host, team, project); // Create the directory if it does not exist if !Path::new(&project_path).exists() { eprintln!( - "\u{ea83} Destination directory for {}/{} does not exist. Creating...", - team, project + "{} Destination directory for {}/{} does not exist. Creating...", + get_emoji("white_check_mark", "\u{ea83}"), + team, + project ); fs::create_dir_all(&project_path).map_err(ApplicationError::CantCreateTargetDir)?; } else { eprintln!( - "\u{eb32} Destination directory for {}/{} already exists.", - team, project + "{} Destination directory for {}/{} already exists.", + get_emoji("warning", "\u{eb32}"), + team, + project ); eprintln!("Press to confirm deletion or to cancel..."); let mut input = String::new(); @@ -134,7 +153,12 @@ fn run() -> Result<(), ApplicationError> { } // Run the git clone command - eprintln!("\u{ebcc} Cloning {}/{}...", team, project); + eprintln!( + "{} Cloning {}/{}...", + get_emoji("inbox_tray", "\u{ebcc}"), + team, + project, + ); let exec = Exec::cmd("git") .args(&["clone", &clone_url, &project_path]) @@ -149,12 +173,19 @@ fn run() -> Result<(), ApplicationError> { } eprintln!( - "\u{f058} Successfully cloned {}/{} into {}", - team, project, project_path + "{} Successfully cloned {}/{} into {}", + get_emoji("white_check_mark", "\u{f058}"), + team, + project, + project_path ); if let Some(branch) = branch { - eprintln!("\u{f5c4} Checking out branch {}...", branch); + eprintln!( + "{} Checking out branch {}...", + get_emoji("zap", "\u{f5c4}"), + branch + ); let exec = Exec::cmd("git") .args(&["checkout", &branch]) @@ -168,7 +199,11 @@ fn run() -> Result<(), ApplicationError> { return Err(ApplicationError::FailedGitOperation()); } - eprintln!("\u{f5c4} Successfully checked out branch {}", branch); + eprintln!( + "{} Successfully checked out branch {}", + get_emoji("white_check_mark", "\u{f5c4}"), + branch + ); } println!("{}", project_path); diff --git a/src/parser.rs b/src/parser.rs index 8c76dea..9331dfd 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -66,12 +66,15 @@ impl From for ParseRepoError { } } -pub fn repository(repo_url: String) -> Result<(String, String, String), ParseRepoError> { +pub fn repository( + default_user: String, + repo_url: String, +) -> Result<(String, String, String), ParseRepoError> { if repo_url.contains('@') && repo_url.contains(':') { return parse_ssh_url(&repo_url).map_err(ParseRepoError::from); } - parse_http_url(&repo_url).map_err(ParseRepoError::from) + parse_http_url(&default_user, &repo_url).map_err(ParseRepoError::from) } #[derive(Debug)] @@ -114,23 +117,28 @@ fn parse_ssh_url(url: &str) -> Result<(String, String, String), CantConvertSSHEr Ok((host.to_string(), team.to_string(), project.to_string())) } -fn parse_http_url(url: &str) -> Result<(String, String, String), CantConvertError> { - let re = Regex::new(r"^(https://)?(github\.com/)?(?[a-zA-Z0-9-]+)/(?[\w\.-]+).*$") - .map_err(CantConvertError::InvalidRegexp)?; +fn parse_http_url( + default_user: &str, + url: &str, +) -> Result<(String, String, String), CantConvertError> { + let re = + Regex::new(r"^(https://)?(github\.com/)?((?[a-zA-Z0-9-]+)/)?(?[\w\.-]+).*$") + .map_err(CantConvertError::InvalidRegexp)?; let caps = re .captures(url) .ok_or(CantConvertError::InvalidURL(url.to_owned()))?; - let team = caps - .name("org") - .ok_or(CantConvertError::MissingOrganization(url.to_owned()))? - .as_str(); + let team = caps.name("org").map_or(default_user, |m| m.as_str()); let project = caps .name("repo") .ok_or(CantConvertError::MissingProject(url.to_owned()))? .as_str() .trim_end_matches(".git"); + if team.is_empty() { + return Err(CantConvertError::MissingOrganization(url.to_owned())); + } + Ok(( "github.com".to_string(), team.to_string(), @@ -147,36 +155,49 @@ mod tests { let cases = vec![ ( "git@github.com:example/application.git", + "", ("github.com", "example", "application"), ), ( "github.com/example/application", + "", ("github.com", "example", "application"), ), ( "example/application", + "", ("github.com", "example", "application"), ), ( "https://github.com/example/application", + "", ("github.com", "example", "application"), ), ( "https://github.com/example/application/issues", + "", ("github.com", "example", "application"), ), ( "https://github.com/example/application/security/dependabot", + "", ("github.com", "example", "application"), ), ( "https://github.com/example/application/this/is/a/made/up/path", + "", ("github.com", "example", "application"), ), + ( + "example", + "patrickdappollonio", + ("github.com", "patrickdappollonio", "example"), + ), ]; - for (input, expected) in cases { - let (host, team, project) = repository(input.to_string()).unwrap(); + for (input, default_user, expected) in cases { + let (host, team, project) = + repository(default_user.to_string(), input.to_string()).unwrap(); let (expected_host, expected_team, expected_project) = expected; assert_eq!(host, expected_host.to_string()); assert_eq!(team, expected_team.to_string()); @@ -189,7 +210,7 @@ mod tests { let cases = vec![""]; for input in cases { - let result = repository(input.to_string()); + let result = repository(input.to_string(), input.to_string()); assert!(result.is_err()); } } @@ -200,28 +221,32 @@ mod tests { ( "https://github.com/patrickdappollonio/gc-rust", false, + "", ("github.com", "patrickdappollonio", "gc-rust"), ), ( "https://github.com/patrickdappollonio/gc-rust.git", false, + "", ("github.com", "patrickdappollonio", "gc-rust"), ), - ("http://patrickdap.com", true, ("", "", "")), + ("http://patrickdap.com", true, "", ("", "", "")), ( "https://github.com/patrickdappollonio/gc-rust/foo/bar", false, + "", ("github.com", "patrickdappollonio", "gc-rust"), ), ( "patrickdappollonio/gc-rust", false, + "", ("github.com", "patrickdappollonio", "gc-rust"), ), ]; - for (input, should_fail, expected) in cases { - let result = parse_http_url(input); + for (input, should_fail, default_user, expected) in cases { + let result = parse_http_url(default_user, input); if should_fail { assert!(result.is_err());