diff --git a/appveyor.yml b/appveyor.yml index bd610e04..f6e7e8a7 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -27,7 +27,8 @@ test_script: - if [%APPVEYOR_REPO_TAG%]==[false] ( cargo fmt --all -- --check && cargo clippy --all-targets --all-features -- -D warnings && - cargo clippy --all-targets -- -D warnings + cargo clippy --all-targets -- -D warnings && + cargo test ) before_deploy: diff --git a/ci/script.sh b/ci/script.sh index c3062300..d25e366f 100644 --- a/ci/script.sh +++ b/ci/script.sh @@ -9,6 +9,7 @@ main() { cross clippy --all-targets --all-features -- -D warnings if [ ! -z $DISABLE_TESTS ]; then + cross test return fi diff --git a/src/steps/git.rs b/src/steps/git.rs index ab9090c7..4cc5e3c8 100644 --- a/src/steps/git.rs +++ b/src/steps/git.rs @@ -1,7 +1,7 @@ use crate::error::Error; use crate::executor::{CommandExt, RunType}; use crate::terminal::print_separator; -use crate::utils::which; +use crate::utils::{which, HumanizedPath}; use log::{debug, error}; use std::collections::HashSet; use std::io; @@ -58,7 +58,7 @@ impl Git { pub fn pull>(&self, path: P, run_type: RunType) -> Option<(String, bool)> { let path = path.as_ref(); - print_separator(format!("Pulling {}", path.display())); + print_separator(format!("Pulling {}", HumanizedPath::from(path))); let git = self.git.as_ref().unwrap(); @@ -73,7 +73,7 @@ impl Git { }() .is_ok(); - Some((format!("git: {}", path.display()), success)) + Some((format!("git: {}", HumanizedPath::from(path)), success)) } } diff --git a/src/utils.rs b/src/utils.rs index 805bc4d9..83b77a59 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,8 +1,8 @@ use super::error::{Error, ErrorKind}; use log::{debug, error}; use std::ffi::OsStr; -use std::fmt::Debug; -use std::path::{Path, PathBuf}; +use std::fmt::{self, Debug}; +use std::path::{Component, Path, PathBuf}; use std::process::{ExitStatus, Output}; use which_crate; @@ -68,3 +68,86 @@ pub fn which + Debug>(binary_name: T) -> Option { } } } + +/// `std::fmt::Display` implementation for `std::path::Path`. +/// +/// This struct differs from `std::path::Display` in that in Windows it takes care of printing backslashes +/// instead of slashes and don't print the `\\?` prefix in long paths. +pub struct HumanizedPath<'a> { + path: &'a Path, +} + +impl<'a> From<&'a Path> for HumanizedPath<'a> { + fn from(path: &'a Path) -> Self { + Self { path } + } +} + +impl<'a> fmt::Display for HumanizedPath<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if cfg!(windows) { + let mut iterator = self.path.components().peekable(); + + while let Some(component) = iterator.next() { + let mut print_seperator = iterator.peek().is_some(); + + match &component { + Component::Normal(c) if *c == "?" => { + print_seperator = false; + } + Component::RootDir | Component::CurDir => { + print_seperator = false; + } + Component::ParentDir => { + write!(f, "..")?; + } + Component::Prefix(p) => { + write!(f, "{}", p.as_os_str().to_string_lossy())?; + print_seperator = true; + } + Component::Normal(c) => { + write!(f, "{}", c.to_string_lossy())?; + } + }; + + if print_seperator { + write!(f, "{}", std::path::MAIN_SEPARATOR)?; + } + } + } else { + write!(f, "{}", self.path.display())?; + } + + Ok(()) + } +} + +#[cfg(test)] +#[cfg(windows)] +mod tests { + use super::*; + + fn humanize>(path: P) -> String { + format!("{}", HumanizedPath::from(path.as_ref())) + } + + #[test] + fn test_just_drive() { + assert_eq!("C:\\", humanize("C:\\")); + } + + #[test] + fn test_path() { + assert_eq!("C:\\hi", humanize("C:\\hi")); + } + + #[test] + fn test_unc() { + assert_eq!("\\\\server\\share\\", humanize("\\\\server\\share")); + } + + #[test] + fn test_long_path() { + assert_eq!("C:\\hi", humanize("//?/C:/hi")); + } +}