diff --git a/src/context.rs b/src/context.rs index e70ffac04..6365e3c64 100644 --- a/src/context.rs +++ b/src/context.rs @@ -9,7 +9,8 @@ use git2::{ErrorCode::UnbornBranch, Repository, RepositoryState}; use once_cell::sync::OnceCell; use std::collections::{HashMap, HashSet}; use std::env; -use std::ffi::OsString; +use std::ffi::{OsStr, OsString}; +use std::fmt::Debug; use std::fs; use std::path::{Path, PathBuf}; use std::string::String; @@ -266,18 +267,19 @@ impl<'a> Context<'a> { /// Execute a command and return the output on stdout and stderr if successful #[inline] - pub fn exec_cmd(&self, cmd: &str, args: &[&str]) -> Option { + pub fn exec_cmd + Debug, U: AsRef + Debug>( + &self, + cmd: T, + args: &[U], + ) -> Option { #[cfg(test)] { - let command = match args.len() { - 0 => cmd.to_owned(), - _ => format!("{} {}", cmd, args.join(" ")), - }; + let command = crate::utils::display_command(&cmd, args); if let Some(output) = self.cmd.get(command.as_str()) { return output.clone(); } } - exec_cmd(cmd, args, self.cmd_timeout) + exec_cmd(&cmd, args, self.cmd_timeout) } } diff --git a/src/modules/git_metrics.rs b/src/modules/git_metrics.rs index 218971d2f..6d41e0145 100644 --- a/src/modules/git_metrics.rs +++ b/src/modules/git_metrics.rs @@ -1,4 +1,5 @@ use regex::Regex; +use std::ffi::OsStr; use crate::{ config::RootModuleConfig, configs::git_metrics::GitMetricsConfig, formatter::StringFormatter, @@ -26,11 +27,11 @@ pub fn module<'a>(context: &'a Context) -> Option> { .exec_cmd( "git", &[ - "-C", - &repo_root.to_string_lossy(), - "--no-optional-locks", - "diff", - "--shortstat", + OsStr::new("-C"), + repo_root.as_os_str(), + OsStr::new("--no-optional-locks"), + OsStr::new("diff"), + OsStr::new("--shortstat"), ], )? .stdout; diff --git a/src/modules/git_status.rs b/src/modules/git_status.rs index a57ada8dc..d970eac01 100644 --- a/src/modules/git_status.rs +++ b/src/modules/git_status.rs @@ -6,6 +6,7 @@ use super::{Context, Module, RootModuleConfig}; use crate::configs::git_status::GitStatusConfig; use crate::formatter::StringFormatter; use crate::segment::Segment; +use std::ffi::OsStr; use std::sync::Arc; const ALL_STATUS_FORMAT: &str = "$conflicted$stashed$deleted$renamed$modified$staged$untracked"; @@ -184,12 +185,12 @@ fn get_repo_status(context: &Context) -> Option { let status_output = context.exec_cmd( "git", &[ - "-C", - &context.current_dir.to_string_lossy(), - "--no-optional-locks", - "status", - "--porcelain=2", - "--branch", + OsStr::new("-C"), + context.current_dir.as_os_str(), + OsStr::new("--no-optional-locks"), + OsStr::new("status"), + OsStr::new("--porcelain=2"), + OsStr::new("--branch"), ], )?; let statuses = status_output.stdout.lines(); @@ -209,11 +210,11 @@ fn get_stashed_count(context: &Context) -> Option { let stash_output = context.exec_cmd( "git", &[ - "-C", - &context.current_dir.to_string_lossy(), - "--no-optional-locks", - "stash", - "list", + OsStr::new("-C"), + context.current_dir.as_os_str(), + OsStr::new("--no-optional-locks"), + OsStr::new("stash"), + OsStr::new("list"), ], )?; @@ -320,6 +321,7 @@ fn format_symbol(format_str: &str, config_path: &str) -> Option> { #[cfg(test)] mod tests { use ansi_term::{ANSIStrings, Color}; + use std::ffi::OsStr; use std::fs::{self, File}; use std::io::{self, prelude::*}; use std::path::Path; @@ -829,9 +831,9 @@ mod tests { create_command("git")? .args(&[ - "config", - "core.worktree", - &worktree_dir.path().to_string_lossy(), + OsStr::new("config"), + OsStr::new("core.worktree"), + worktree_dir.path().as_os_str(), ]) .current_dir(repo_dir.path()) .output()?; diff --git a/src/utils.rs b/src/utils.rs index c50389946..147b68188 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -72,18 +72,35 @@ impl PartialEq for CommandOutput { } } +#[cfg(test)] +pub fn display_command + Debug, U: AsRef + Debug>( + cmd: T, + args: &[U], +) -> String { + std::iter::once(cmd.as_ref()) + .chain(args.iter().map(|i| i.as_ref())) + .map(|i| i.to_string_lossy().into_owned()) + .collect::>() + .join(" ") +} + /// Execute a command and return the output on stdout and stderr if successful #[cfg(not(test))] -pub fn exec_cmd(cmd: &str, args: &[&str], time_limit: Duration) -> Option { +pub fn exec_cmd + Debug, U: AsRef + Debug>( + cmd: T, + args: &[U], + time_limit: Duration, +) -> Option { internal_exec_cmd(cmd, args, time_limit) } #[cfg(test)] -pub fn exec_cmd(cmd: &str, args: &[&str], time_limit: Duration) -> Option { - let command = match args.len() { - 0 => String::from(cmd), - _ => format!("{} {}", cmd, args.join(" ")), - }; +pub fn exec_cmd + Debug, U: AsRef + Debug>( + cmd: T, + args: &[U], + time_limit: Duration, +) -> Option { + let command = display_command(&cmd, args); match command.as_str() { "crystal --version" => Some(CommandOutput { stdout: String::from( @@ -280,7 +297,7 @@ CMake suite maintained and supported by Kitware (kitware.com/cmake).\n", stderr: String::default(), }), // If we don't have a mocked command fall back to executing the command - _ => internal_exec_cmd(cmd, args, time_limit), + _ => internal_exec_cmd(&cmd, args, time_limit), } } @@ -345,10 +362,14 @@ pub fn wrap_seq_for_shell( final_string } -fn internal_exec_cmd(cmd: &str, args: &[&str], time_limit: Duration) -> Option { +fn internal_exec_cmd + Debug, U: AsRef + Debug>( + cmd: T, + args: &[U], + time_limit: Duration, +) -> Option { log::trace!("Executing command {:?} with args {:?}", cmd, args); - let full_path = match which::which(cmd) { + let full_path = match which::which(&cmd) { Ok(full_path) => { log::trace!("Using {:?} as {:?}", full_path, cmd); full_path @@ -483,7 +504,11 @@ mod tests { #[test] fn exec_mocked_command() { - let result = exec_cmd("dummy_command", &[], Duration::from_millis(500)); + let result = exec_cmd( + "dummy_command", + &[] as &[&OsStr], + Duration::from_millis(500), + ); let expected = Some(CommandOutput { stdout: String::from("stdout ok!\n"), stderr: String::from("stderr ok!\n"), @@ -498,7 +523,7 @@ mod tests { #[test] #[cfg(not(windows))] fn exec_no_output() { - let result = internal_exec_cmd("true", &[], Duration::from_millis(500)); + let result = internal_exec_cmd("true", &[] as &[&OsStr], Duration::from_millis(500)); let expected = Some(CommandOutput { stdout: String::from(""), stderr: String::from(""), @@ -555,7 +580,7 @@ mod tests { #[test] #[cfg(not(windows))] fn exec_with_non_zero_exit_code() { - let result = internal_exec_cmd("false", &[], Duration::from_millis(500)); + let result = internal_exec_cmd("false", &[] as &[&OsStr], Duration::from_millis(500)); let expected = None; assert_eq!(result, expected)