/ bin-intercept / src / lib.rs
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(&current_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  }