lib.rs
1 use std::io; 2 use std::path::PathBuf; 3 use std::process::Command; 4 5 use thiserror::Error; 6 use tracing::trace; 7 8 const LOG_TARGET: &str = "bin-intercept"; 9 10 #[derive(Debug, Error)] 11 pub enum InterceptError { 12 #[error("Base name missing in the current executed binary")] 13 MissingBaseName, 14 #[error("Next bin to call not found: {0}")] 15 FindNextBin(FindNextBinError), 16 #[error("Could not call `which`: {0}")] 17 Which(which::Error), 18 #[error("IO Error: {0}")] 19 IO(io::Error), 20 } 21 22 #[derive(Error, Debug)] 23 pub enum FindNextBinError { 24 #[error("Current bin not found in PATH: {}", current_exe.display())] 25 NoMatch { current_exe: PathBuf }, 26 27 #[error("No more bin found after current_bin in the PATH: {}", current_exe.display() )] 28 NextBinMissing { current_exe: PathBuf }, 29 } 30 31 pub type InterceptResult<T> = std::result::Result<T, InterceptError>; 32 pub type FindNextBinResult<T> = std::result::Result<T, FindNextBinError>; 33 34 pub struct Intercept { 35 next_bin: PathBuf, 36 } 37 38 impl Intercept { 39 pub fn new() -> InterceptResult<Self> { 40 let current_exe = std::env::current_exe().map_err(InterceptError::IO)?; 41 42 trace!(target: LOG_TARGET, current_exe=%current_exe.display(), "Current exe"); 43 44 let bin_basename = current_exe 45 .file_name() 46 .ok_or(InterceptError::MissingBaseName)?; 47 let all_bins = which::which_all(bin_basename).map_err(InterceptError::Which)?; 48 49 let next_bin = 50 find_next_bin(¤t_exe, all_bins).map_err(InterceptError::FindNextBin)?; 51 trace!(target: LOG_TARGET, current_exe=%next_bin.display(), "Next bin"); 52 53 Ok(Self { next_bin }) 54 } 55 56 pub fn intercept<F, FE>(self, f: F) -> std::result::Result<Command, FE> 57 where 58 F: FnOnce(&mut Command) -> std::result::Result<(), FE>, 59 { 60 let mut command = Command::new(self.next_bin); 61 62 f(&mut command)?; 63 64 Ok(command) 65 } 66 } 67 68 fn find_next_bin( 69 our_bin_path: &PathBuf, 70 all_bin_paths: impl Iterator<Item = PathBuf>, 71 ) -> FindNextBinResult<PathBuf> { 72 let mut found = false; 73 74 for path in all_bin_paths { 75 if found { 76 return Ok(path); 77 } 78 if &path == our_bin_path { 79 found = true; 80 } 81 } 82 83 if found { 84 Err(FindNextBinError::NextBinMissing { 85 current_exe: our_bin_path.clone(), 86 }) 87 } else { 88 Err(FindNextBinError::NoMatch { 89 current_exe: our_bin_path.clone(), 90 }) 91 } 92 }