"Fossies" - the Fresh Open Source Software Archive

Member "fd-8.1.1/src/exec/mod.rs" (25 May 2020, 10252 Bytes) of package /linux/privat/fd-8.1.1.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Rust source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. See also the last Fossies "Diffs" side-by-side code changes report for "mod.rs": 8.0.0_vs_8.1.0.

    1 mod command;
    2 mod input;
    3 mod job;
    4 mod token;
    5 
    6 use std::ffi::OsString;
    7 use std::path::{Path, PathBuf};
    8 use std::process::{Command, Stdio};
    9 use std::sync::{Arc, Mutex};
   10 
   11 use anyhow::{anyhow, Result};
   12 use lazy_static::lazy_static;
   13 use regex::Regex;
   14 
   15 use crate::exit_codes::ExitCode;
   16 use crate::filesystem::strip_current_dir;
   17 
   18 use self::command::execute_command;
   19 use self::input::{basename, dirname, remove_extension};
   20 pub use self::job::{batch, job};
   21 use self::token::Token;
   22 
   23 /// Execution mode of the command
   24 #[derive(Debug, Clone, Copy, PartialEq)]
   25 pub enum ExecutionMode {
   26     /// Command is executed for each search result
   27     OneByOne,
   28     /// Command is run for a batch of results at once
   29     Batch,
   30 }
   31 
   32 /// Represents a template that is utilized to generate command strings.
   33 ///
   34 /// The template is meant to be coupled with an input in order to generate a command. The
   35 /// `generate_and_execute()` method will be used to generate a command and execute it.
   36 #[derive(Debug, Clone, PartialEq)]
   37 pub struct CommandTemplate {
   38     args: Vec<ArgumentTemplate>,
   39     mode: ExecutionMode,
   40 }
   41 
   42 impl CommandTemplate {
   43     pub fn new<I, S>(input: I) -> CommandTemplate
   44     where
   45         I: IntoIterator<Item = S>,
   46         S: AsRef<str>,
   47     {
   48         Self::build(input, ExecutionMode::OneByOne)
   49     }
   50 
   51     pub fn new_batch<I, S>(input: I) -> Result<CommandTemplate>
   52     where
   53         I: IntoIterator<Item = S>,
   54         S: AsRef<str>,
   55     {
   56         let cmd = Self::build(input, ExecutionMode::Batch);
   57         if cmd.number_of_tokens() > 1 {
   58             return Err(anyhow!("Only one placeholder allowed for batch commands"));
   59         }
   60         if cmd.args[0].has_tokens() {
   61             return Err(anyhow!(
   62                 "First argument of exec-batch is expected to be a fixed executable"
   63             ));
   64         }
   65         Ok(cmd)
   66     }
   67 
   68     fn build<I, S>(input: I, mode: ExecutionMode) -> CommandTemplate
   69     where
   70         I: IntoIterator<Item = S>,
   71         S: AsRef<str>,
   72     {
   73         lazy_static! {
   74             static ref PLACEHOLDER_PATTERN: Regex = Regex::new(r"\{(/?\.?|//)\}").unwrap();
   75         }
   76 
   77         let mut args = Vec::new();
   78         let mut has_placeholder = false;
   79 
   80         for arg in input {
   81             let arg = arg.as_ref();
   82 
   83             let mut tokens = Vec::new();
   84             let mut start = 0;
   85 
   86             for placeholder in PLACEHOLDER_PATTERN.find_iter(arg) {
   87                 // Leading text before the placeholder.
   88                 if placeholder.start() > start {
   89                     tokens.push(Token::Text(arg[start..placeholder.start()].to_owned()));
   90                 }
   91 
   92                 start = placeholder.end();
   93 
   94                 match placeholder.as_str() {
   95                     "{}" => tokens.push(Token::Placeholder),
   96                     "{.}" => tokens.push(Token::NoExt),
   97                     "{/}" => tokens.push(Token::Basename),
   98                     "{//}" => tokens.push(Token::Parent),
   99                     "{/.}" => tokens.push(Token::BasenameNoExt),
  100                     _ => unreachable!("Unhandled placeholder"),
  101                 }
  102 
  103                 has_placeholder = true;
  104             }
  105 
  106             // Without a placeholder, the argument is just fixed text.
  107             if tokens.is_empty() {
  108                 args.push(ArgumentTemplate::Text(arg.to_owned()));
  109                 continue;
  110             }
  111 
  112             if start < arg.len() {
  113                 // Trailing text after last placeholder.
  114                 tokens.push(Token::Text(arg[start..].to_owned()));
  115             }
  116 
  117             args.push(ArgumentTemplate::Tokens(tokens));
  118         }
  119 
  120         // If a placeholder token was not supplied, append one at the end of the command.
  121         if !has_placeholder {
  122             args.push(ArgumentTemplate::Tokens(vec![Token::Placeholder]));
  123         }
  124 
  125         CommandTemplate { args, mode }
  126     }
  127 
  128     fn number_of_tokens(&self) -> usize {
  129         self.args.iter().filter(|arg| arg.has_tokens()).count()
  130     }
  131 
  132     /// Generates and executes a command.
  133     ///
  134     /// Using the internal `args` field, and a supplied `input` variable, a `Command` will be
  135     /// build. Once all arguments have been processed, the command is executed.
  136     pub fn generate_and_execute(&self, input: &Path, out_perm: Arc<Mutex<()>>) -> ExitCode {
  137         let input = strip_current_dir(input);
  138 
  139         let mut cmd = Command::new(self.args[0].generate(&input));
  140         for arg in &self.args[1..] {
  141             cmd.arg(arg.generate(&input));
  142         }
  143 
  144         execute_command(cmd, &out_perm)
  145     }
  146 
  147     pub fn in_batch_mode(&self) -> bool {
  148         self.mode == ExecutionMode::Batch
  149     }
  150 
  151     pub fn generate_and_execute_batch<I>(&self, paths: I) -> ExitCode
  152     where
  153         I: Iterator<Item = PathBuf>,
  154     {
  155         let mut cmd = Command::new(self.args[0].generate(""));
  156         cmd.stdin(Stdio::inherit());
  157         cmd.stdout(Stdio::inherit());
  158         cmd.stderr(Stdio::inherit());
  159 
  160         let mut paths: Vec<_> = paths.collect();
  161         let mut has_path = false;
  162 
  163         for arg in &self.args[1..] {
  164             if arg.has_tokens() {
  165                 paths.sort();
  166 
  167                 // A single `Tokens` is expected
  168                 // So we can directly consume the iterator once and for all
  169                 for path in &mut paths {
  170                     cmd.arg(arg.generate(strip_current_dir(path)));
  171                     has_path = true;
  172                 }
  173             } else {
  174                 cmd.arg(arg.generate(""));
  175             }
  176         }
  177 
  178         if has_path {
  179             execute_command(cmd, &Mutex::new(()))
  180         } else {
  181             ExitCode::Success
  182         }
  183     }
  184 }
  185 
  186 /// Represents a template for a single command argument.
  187 ///
  188 /// The argument is either a collection of `Token`s including at least one placeholder variant, or
  189 /// a fixed text.
  190 #[derive(Clone, Debug, PartialEq)]
  191 enum ArgumentTemplate {
  192     Tokens(Vec<Token>),
  193     Text(String),
  194 }
  195 
  196 impl ArgumentTemplate {
  197     pub fn has_tokens(&self) -> bool {
  198         match self {
  199             ArgumentTemplate::Tokens(_) => true,
  200             _ => false,
  201         }
  202     }
  203 
  204     pub fn generate(&self, path: impl AsRef<Path>) -> OsString {
  205         use self::Token::*;
  206 
  207         match *self {
  208             ArgumentTemplate::Tokens(ref tokens) => {
  209                 let mut s = OsString::new();
  210                 for token in tokens {
  211                     match *token {
  212                         Basename => s.push(basename(path.as_ref())),
  213                         BasenameNoExt => {
  214                             s.push(remove_extension(&PathBuf::from(basename(path.as_ref()))))
  215                         }
  216                         NoExt => s.push(remove_extension(path.as_ref())),
  217                         Parent => s.push(dirname(path.as_ref())),
  218                         Placeholder => s.push(path.as_ref()),
  219                         Text(ref string) => s.push(string),
  220                     }
  221                 }
  222                 s
  223             }
  224             ArgumentTemplate::Text(ref text) => OsString::from(text),
  225         }
  226     }
  227 }
  228 
  229 #[cfg(test)]
  230 mod tests {
  231     use super::*;
  232 
  233     #[test]
  234     fn tokens_with_placeholder() {
  235         assert_eq!(
  236             CommandTemplate::new(&[&"echo", &"${SHELL}:"]),
  237             CommandTemplate {
  238                 args: vec![
  239                     ArgumentTemplate::Text("echo".into()),
  240                     ArgumentTemplate::Text("${SHELL}:".into()),
  241                     ArgumentTemplate::Tokens(vec![Token::Placeholder]),
  242                 ],
  243                 mode: ExecutionMode::OneByOne,
  244             }
  245         );
  246     }
  247 
  248     #[test]
  249     fn tokens_with_no_extension() {
  250         assert_eq!(
  251             CommandTemplate::new(&["echo", "{.}"]),
  252             CommandTemplate {
  253                 args: vec![
  254                     ArgumentTemplate::Text("echo".into()),
  255                     ArgumentTemplate::Tokens(vec![Token::NoExt]),
  256                 ],
  257                 mode: ExecutionMode::OneByOne,
  258             }
  259         );
  260     }
  261 
  262     #[test]
  263     fn tokens_with_basename() {
  264         assert_eq!(
  265             CommandTemplate::new(&["echo", "{/}"]),
  266             CommandTemplate {
  267                 args: vec![
  268                     ArgumentTemplate::Text("echo".into()),
  269                     ArgumentTemplate::Tokens(vec![Token::Basename]),
  270                 ],
  271                 mode: ExecutionMode::OneByOne,
  272             }
  273         );
  274     }
  275 
  276     #[test]
  277     fn tokens_with_parent() {
  278         assert_eq!(
  279             CommandTemplate::new(&["echo", "{//}"]),
  280             CommandTemplate {
  281                 args: vec![
  282                     ArgumentTemplate::Text("echo".into()),
  283                     ArgumentTemplate::Tokens(vec![Token::Parent]),
  284                 ],
  285                 mode: ExecutionMode::OneByOne,
  286             }
  287         );
  288     }
  289 
  290     #[test]
  291     fn tokens_with_basename_no_extension() {
  292         assert_eq!(
  293             CommandTemplate::new(&["echo", "{/.}"]),
  294             CommandTemplate {
  295                 args: vec![
  296                     ArgumentTemplate::Text("echo".into()),
  297                     ArgumentTemplate::Tokens(vec![Token::BasenameNoExt]),
  298                 ],
  299                 mode: ExecutionMode::OneByOne,
  300             }
  301         );
  302     }
  303 
  304     #[test]
  305     fn tokens_multiple() {
  306         assert_eq!(
  307             CommandTemplate::new(&["cp", "{}", "{/.}.ext"]),
  308             CommandTemplate {
  309                 args: vec![
  310                     ArgumentTemplate::Text("cp".into()),
  311                     ArgumentTemplate::Tokens(vec![Token::Placeholder]),
  312                     ArgumentTemplate::Tokens(vec![
  313                         Token::BasenameNoExt,
  314                         Token::Text(".ext".into())
  315                     ]),
  316                 ],
  317                 mode: ExecutionMode::OneByOne,
  318             }
  319         );
  320     }
  321 
  322     #[test]
  323     fn tokens_single_batch() {
  324         assert_eq!(
  325             CommandTemplate::new_batch(&["echo", "{.}"]).unwrap(),
  326             CommandTemplate {
  327                 args: vec![
  328                     ArgumentTemplate::Text("echo".into()),
  329                     ArgumentTemplate::Tokens(vec![Token::NoExt]),
  330                 ],
  331                 mode: ExecutionMode::Batch,
  332             }
  333         );
  334     }
  335 
  336     #[test]
  337     fn tokens_multiple_batch() {
  338         assert!(CommandTemplate::new_batch(&["echo", "{.}", "{}"]).is_err());
  339     }
  340 }