diff --git a/Cargo.lock b/Cargo.lock index 064197e..7c3ddb0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -320,9 +320,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.169" +version = "0.2.184" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" +checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" [[package]] name = "linux-raw-sys" diff --git a/Cargo.toml b/Cargo.toml index b0fdda4..155f875 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,7 @@ regex = { version = "1.7.3", default-features = false, features = [ "unicode-perl", ] } clap = { version = "4.2.1", features = ["derive", "cargo"] } -crossterm = "0.28.0" +crossterm = "0.28.1" lazy_static = "1.4.0" etcetera = "0.10.0" walkdir = "2.3.3" diff --git a/src/bin/main.rs b/src/bin/main.rs index 8036aee..d1b51c5 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -6,7 +6,7 @@ use thiserror::Error; #[derive(Error, Debug)] #[error( "\rHey, listen! navi encountered a problem. -Do you think this is a bug? File an issue at https://github.com/denisidoro/navi." +Do you think this is a bug? File an issue at https://github.com/denisidoro/navi/issues." )] pub struct FileAnIssue { #[source] diff --git a/src/common/clipboard.rs b/src/common/clipboard.rs index 47cb761..3330751 100644 --- a/src/common/clipboard.rs +++ b/src/common/clipboard.rs @@ -1,5 +1,5 @@ -use crate::common::shell::{self, ShellSpawnError, EOF}; -use crate::prelude::*; +use crate::common::shell::{out, ShellSpawnError, EOF}; +use anyhow::Result; pub fn copy(text: String) -> Result<()> { let cmd = r#" @@ -19,15 +19,16 @@ _copy() { fi }"#; - shell::out() + out() .arg( format!( - r#"{cmd} - read -r -d '' x <<'{EOF}' + r#" +{cmd} +read -r -d '' x <<'{EOF}' {text} {EOF} -echo -n "$x" | _copy"#, +echo -n "$x" | _copy"# ) .as_str(), ) diff --git a/src/common/fs.rs b/src/common/fs.rs index 9c89bd0..4800767 100644 --- a/src/common/fs.rs +++ b/src/common/fs.rs @@ -1,8 +1,9 @@ use crate::prelude::*; use remove_dir_all::remove_dir_all; +use std::env::current_exe; use std::ffi::OsStr; -use std::fs::{self, create_dir_all, File}; -use std::io; +use std::fs::{create_dir_all, read_link, File}; +use std::io::BufReader; use thiserror::Error; pub trait ToStringExt { @@ -33,30 +34,29 @@ pub struct UnreadableDir { source: anyhow::Error, } -pub fn open(filename: &Path) -> Result { - File::open(filename).with_context(|| { - let x = filename.to_string(); - format!("Failed to open file {}", &x) +pub fn open>(filename: P) -> Result { + File::open(filename.as_ref()).with_context(|| { + let x = filename.as_ref().to_string(); + format!("Failed to open file {}", x) }) } -pub fn read_lines(filename: &Path) -> Result>> { - let file = open(filename)?; - Ok(io::BufReader::new(file) - .lines() - .map(|line| line.map_err(Error::from))) +pub fn read_lines>(filename: P) -> Result>> { + let file = open(filename.as_ref())?; + Ok(BufReader::new(file).lines().map(|line| line.map_err(Error::from))) } -pub fn pathbuf_to_string(pathbuf: &Path) -> Result { +fn pathbuf_to_string>(pathbuf: P) -> Result { Ok(pathbuf + .as_ref() .as_os_str() .to_str() - .ok_or_else(|| InvalidPath(pathbuf.to_path_buf())) + .ok_or_else(|| InvalidPath(pathbuf.as_ref().to_path_buf())) .map(str::to_string)?) } fn follow_symlink(pathbuf: PathBuf) -> Result { - fs::read_link(pathbuf.clone()) + read_link(pathbuf.clone()) .map(|o| { let o_str = o .as_os_str() @@ -77,7 +77,7 @@ fn follow_symlink(pathbuf: PathBuf) -> Result { } fn exe_pathbuf() -> Result { - let pathbuf = std::env::current_exe().context("Unable to acquire executable's path")?; + let pathbuf = current_exe().context("Unable to acquire executable's path")?; #[cfg(target_family = "windows")] let pathbuf = dunce::canonicalize(pathbuf)?; @@ -108,8 +108,8 @@ pub fn exe_string() -> String { exe_abs_string().unwrap_or_else(|_| "navi".to_string()) } -pub fn create_dir(path: &Path) -> Result<()> { - create_dir_all(path).with_context(|| { +pub fn create_dir>(path: P) -> Result<()> { + create_dir_all(path.as_ref()).with_context(|| { format!( "Failed to create directory `{}`", pathbuf_to_string(path).expect("Unable to parse {path}") @@ -117,8 +117,8 @@ pub fn create_dir(path: &Path) -> Result<()> { }) } -pub fn remove_dir(path: &Path) -> Result<()> { - remove_dir_all(path).with_context(|| { +pub fn remove_dir>(path: P) -> Result<()> { + remove_dir_all(path.as_ref()).with_context(|| { format!( "Failed to remove directory `{}`", pathbuf_to_string(path).expect("Unable to parse {path}") diff --git a/src/common/git.rs b/src/common/git.rs index 2823fe6..41dd4f5 100644 --- a/src/common/git.rs +++ b/src/common/git.rs @@ -15,8 +15,21 @@ pub fn shallow_clone(uri: &str, target: &str) -> Result<()> { pub fn meta(uri: &str) -> (String, String, String) { let actual_uri = if uri.contains("://") || uri.contains('@') { uri.to_string() + } else if let Some((domain, route)) = uri.split_once('/') { + if domain.contains(".") { + format!("https://{domain}/{route}") + } else { + // Users can pass name starting wirh a slash + let first_char = uri.chars().next(); + + if first_char == Some('/') { + format!("https://github.com{uri}") + } else { + format!("https://github.com/{uri}") + } + } } else { - format!("https://github.com/{uri}") + panic!("Invalid link") }; let uri_to_split = actual_uri.replace(':', "/"); @@ -54,4 +67,36 @@ mod tests { assert_eq!(user, "user".to_string()); assert_eq!(repo, "repo".to_string()); } + + #[test] + fn test_meta_github_repo_name() { + let (actual_uri, user, repo) = meta("user/repo"); + assert_eq!(actual_uri, "https://github.com/user/repo".to_string()); + assert_eq!(user, "user".to_string()); + assert_eq!(repo, "repo".to_string()); + } + + #[test] + fn test_meta_github_repo_name_with_slash() { + let (actual_uri, user, repo) = meta("/user/repo"); + assert_eq!(actual_uri, "https://github.com/user/repo".to_string()); + assert_eq!(user, "user".to_string()); + assert_eq!(repo, "repo".to_string()); + } + + #[test] + fn test_meta_github_clean_link() { + let (actual_uri, user, repo) = meta("github.com/user/repo"); + assert_eq!(actual_uri, "https://github.com/user/repo".to_string()); + assert_eq!(user, "user".to_string()); + assert_eq!(repo, "repo".to_string()); + } + + #[test] + fn test_meta_random_git_provider_http() { + let (actual_uri, user, repo) = meta("https://sr.ht/user/repo"); + assert_eq!(actual_uri, "https://sr.ht/user/repo".to_string()); + assert_eq!(user, "user".to_string()); + assert_eq!(repo, "repo".to_string()); + } } diff --git a/src/common/hash.rs b/src/common/hash.rs index eba695f..7bc2483 100644 --- a/src/common/hash.rs +++ b/src/common/hash.rs @@ -1,30 +1,7 @@ -use std::hash::{Hash, Hasher}; - -const MAGIC_INIT: u64 = 0x811C_9DC5; +use std::hash::{DefaultHasher, Hash, Hasher}; pub fn fnv(x: &T) -> u64 { - let mut hasher = FnvHasher::new(); + let mut hasher = DefaultHasher::new(); x.hash(&mut hasher); hasher.finish() } - -struct FnvHasher(u64); - -impl FnvHasher { - fn new() -> Self { - FnvHasher(MAGIC_INIT) - } -} - -impl Hasher for FnvHasher { - fn finish(&self) -> u64 { - self.0 - } - - fn write(&mut self, bytes: &[u8]) { - for byte in bytes.iter() { - self.0 ^= u64::from(*byte); - self.0 = self.0.wrapping_mul(0x0100_0000_01b3); - } - } -} diff --git a/src/common/shell.rs b/src/common/shell.rs index c9d86a6..601d32b 100644 --- a/src/common/shell.rs +++ b/src/common/shell.rs @@ -1,4 +1,4 @@ -use crate::prelude::*; +use crate::config::CONFIG; use clap::ValueEnum; use std::process::Command; use thiserror::Error; @@ -24,9 +24,10 @@ pub struct ShellSpawnError { } impl ShellSpawnError { - pub fn new(command: impl Into, source: SourceError) -> Self + pub fn new(command: T, source: SourceError) -> Self where SourceError: std::error::Error + Sync + Send + 'static, + T: Into, { ShellSpawnError { command: command.into(), diff --git a/src/common/terminal.rs b/src/common/terminal.rs index b88bf41..1f3c2d4 100644 --- a/src/common/terminal.rs +++ b/src/common/terminal.rs @@ -1,5 +1,4 @@ use crate::prelude::*; -use crossterm::style; use crossterm::terminal; use std::process::Command; @@ -45,21 +44,15 @@ pub fn width() -> u16 { } } -pub fn parse_ansi(ansi: &str) -> Option { - style::Color::parse_ansi(&format!("5;{ansi}")) -} +#[cfg(test)] +mod tests { + use super::*; -#[derive(Debug, Clone)] -pub struct Color(#[allow(unused)] pub style::Color); // suppress warning: field `0` is never read. + #[test] + fn test_width() { + let result = width(); + let is_ok = if result == 0 { false } else { true }; -impl FromStr for Color { - type Err = &'static str; - - fn from_str(ansi: &str) -> Result { - if let Some(c) = parse_ansi(ansi) { - Ok(Color(c)) - } else { - Err("Invalid color") - } + assert!(is_ok); } } diff --git a/src/filesystem.rs b/src/filesystem.rs index 4d0b4e5..62b0b3a 100644 --- a/src/filesystem.rs +++ b/src/filesystem.rs @@ -98,12 +98,12 @@ pub fn cheat_paths(path: Option) -> Result { /// /// We are currently handling two cases: When the platform is `macOS` and when the platform isn't (including `Windows` and `Linux/Unix` platforms) fn get_data_dir_by_platform() -> Result { - if cfg!(target_os = "macos") { - let base_dirs = etcetera::base_strategy::Apple::new()?; + if cfg!(target_os = "windows") { + let base_dirs = etcetera::choose_base_strategy()?; Ok(base_dirs.data_dir()) } else { - let base_dirs = etcetera::choose_base_strategy()?; + let base_dirs = etcetera::base_strategy::Xdg::new()?; Ok(base_dirs.data_dir()) } @@ -113,12 +113,12 @@ fn get_data_dir_by_platform() -> Result { /// /// We are currently handling two cases: When the platform is `macOS` and when the platform isn't (including `Windows` and `Linux/Unix` platforms) fn get_config_dir_by_platform() -> Result { - if cfg!(target_os = "macos") { - let base_dirs = etcetera::base_strategy::Apple::new()?; + if cfg!(target_os = "windows") { + let base_dirs = etcetera::choose_base_strategy()?; Ok(base_dirs.config_dir()) } else { - let base_dirs = etcetera::choose_base_strategy()?; + let base_dirs = etcetera::base_strategy::Xdg::new()?; Ok(base_dirs.config_dir()) }