diff options
author | Michael Aaron Murphy <mmstickman@gmail.com> | 2016-09-01 23:28:07 -0400 |
---|---|---|
committer | Michael Aaron Murphy <mmstickman@gmail.com> | 2016-09-01 23:28:07 -0400 |
commit | 1468e2f935a39b042f5eceb42597a84e4eb915c0 (patch) | |
tree | d2927d913bc27c3ead9b7c206c57a832e85bd778 | |
parent | 7309739c41cfbc80c42eb037efdad82143867860 (diff) |
0.2.1: Parse Syntax with --no-shell0.2.1
This change will allow the --no-shell mode to correctly parse arguments that contain
spaces, so long as the argument is inbetween double quotes or spaces follow a backslash.
-rw-r--r-- | README.md | 15 | ||||
-rw-r--r-- | src/command.rs | 113 |
2 files changed, 111 insertions, 17 deletions
@@ -92,6 +92,7 @@ In addition to the command syntax, there are also some options that you can use - **-j**, **--jobs**: Defines the number of jobs/threads to run in parallel. - **-u**, **--ungroup**: By default, stdout/stderr buffers are grouped in the order that they are received. - **-n**, **--no-shell**: Disables executing commands within the platform's shell for a performance boost. + - Double quotes and backslashes are used to allow spaces in inputs, similar to standard shells. - **-v**, **--verbose**: Prints information about running processes. - **--num-cpu-cores**: Prints the number of CPU cores in the system and exits. @@ -161,18 +162,20 @@ parallel 'echo {}' ::: * There are a number of methods that you can use to install the application. I provide binary packages for AMD64 systems that are available for download: +### Gentoo + +I have a [personal Gentoo layman overlay](https://github.com/mmstick/mmstick-overlay) that provides this application for installation. + ### Ubuntu -```sh -wget https://github.com/mmstick/parallel/releases/download/0.2.0/parallel_0.2.0_amd64.deb -sudo dpkg -i parallel_0.2.0_amd64.deb -``` +Debian packages are provided on the [releases page](https://github.com/mmstick/parallel/releases). +If a release is not available, it's because I haven't built it yet with cargo deb. ### Everyone Else ```sh -wget https://github.com/mmstick/parallel/releases/download/0.2.0/parallel_0.2.0_amd64.tar.xz -tar xf parallel.tar.xz +wget https://github.com/mmstick/parallel/releases/download/0.2.1/parallel_0.2.1_amd64.tar.xz +tar xf parallel_0.2.1.tar.xz sudo install parallel /usr/local/bin ``` diff --git a/src/command.rs b/src/command.rs index 926e11e..b713bc6 100644 --- a/src/command.rs +++ b/src/command.rs @@ -69,10 +69,8 @@ pub fn get_command_output(command: &str, uses_shell: bool, child: &mut Child) if uses_shell { shell_output(command, child) } else { - let mut iter = command.split_whitespace(); - let command = iter.next().unwrap(); - let args = iter.collect::<Vec<&str>>(); - Command::new(command).args(&args) + let arguments = split_into_args(command); + Command::new(&arguments[0]).args(&arguments[1..]) .stdout(Stdio::piped()) .stderr(Stdio::piped()) .spawn().map(|process| { @@ -82,14 +80,95 @@ pub fn get_command_output(command: &str, uses_shell: bool, child: &mut Child) } } +/// Handles quoting of arguments to prevent arguments with spaces as being read as +/// multiple separate arguments. This is only executed when `--no-shell` is in use. +fn split_into_args(command: &str) -> Vec<String> { + let mut output = Vec::new(); + let mut buffer = String::new(); + let mut quoted = false; + let mut prev_char_was_a_backslash = false; + for character in command.chars() { + if quoted { + match character { + '\\' => { + if prev_char_was_a_backslash { + buffer.push('\\'); + prev_char_was_a_backslash = false; + } else { + prev_char_was_a_backslash = true; + } + }, + '"' => { + if prev_char_was_a_backslash { + buffer.push('\\'); + prev_char_was_a_backslash = false; + } else { + if !buffer.is_empty() { + output.push(buffer.clone()); + buffer.clear(); + } + quoted = false; + } + }, + _ => { + if prev_char_was_a_backslash { + buffer.push('\\'); + prev_char_was_a_backslash = false; + } + buffer.push(character); + } + } + } else { + match character { + ' ' => { + if prev_char_was_a_backslash { + buffer.push(' '); + prev_char_was_a_backslash = false; + } else { + if !buffer.is_empty() { + output.push(buffer.clone()); + buffer.clear(); + } + } + }, + '\\' => { + if prev_char_was_a_backslash { + buffer.push('\\'); + prev_char_was_a_backslash = false; + } else { + prev_char_was_a_backslash = true; + } + }, + '"' => { + if prev_char_was_a_backslash { + buffer.push('"'); + prev_char_was_a_backslash = false; + } else { + quoted = true; + } + }, + _ => { + if prev_char_was_a_backslash { + buffer.push('\\'); + prev_char_was_a_backslash = false; + } else { + buffer.push(character); + } + } + } + } + } + output +} + pub fn get_command_status(command: &str, uses_shell: bool) -> io::Result<ExitStatus> { if uses_shell { shell_status(command) } else { let mut iter = command.split_whitespace(); let command = iter.next().unwrap(); - let args = iter.collect::<Vec<&str>>(); - Command::new(command).args(&args).status() + let arguments = split_into_args(command); + Command::new(&arguments[0]).args(&arguments[1..]).status() } } @@ -202,14 +281,26 @@ fn path_dirname_empty() { #[test] fn build_arguments_test() { - let input = "applesauce.mp4"; - let job = "1"; - let slot = "1"; - let total = "1"; let tokens = vec![Token::Argument("-i ".to_owned()), Token::Placeholder, Token::Argument(" ".to_owned()), Token::RemoveExtension, Token::Argument(".mkv".to_owned())]; + + let command = ParallelCommand { + slot_no: "1", + job_no: "1", + job_total: "1", + input: "applesauce.mp4", + command_template: &tokens, + }; + let mut arguments = String::new(); - build_arguments(&mut arguments, &tokens, input, slot, job, total); + command.build_arguments(&mut arguments); assert_eq!(arguments, String::from("-i applesauce.mp4 applesauce.mkv")) } + +#[test] +fn test_split_args() { + let argument = "ffmpeg -i \"file with spaces\" \"output with spaces\""; + let expected = vec!["ffmpeg", "-i", "file with spaces", "output with spaces"]; + assert_eq!(split_into_args(argument), expected) +} |