mirror of
https://github.com/sagiegurari/duckscript
synced 2024-10-04 15:09:08 +00:00
commit
aa243daa67
|
@ -1,5 +1,10 @@
|
|||
## CHANGELOG
|
||||
|
||||
### v0.8.12
|
||||
|
||||
* Enhancement: Add support for stdin input passing to child process in exec, watchdog and spawn commands #247
|
||||
* Update dependencies
|
||||
|
||||
### v0.8.11 (2022-04-20)
|
||||
|
||||
* Fix: Runtime - fix control characters '\' parsing and expansion #237
|
||||
|
|
15
Cargo.lock
generated
15
Cargo.lock
generated
|
@ -377,13 +377,12 @@ checksum = "2022715d62ab30faffd124d40b76f4134a550a87792276512b18d63272333394"
|
|||
|
||||
[[package]]
|
||||
name = "fsio"
|
||||
version = "0.3.0"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09e87827efaf94c7a44b562ff57de06930712fe21b530c3797cdede26e6377eb"
|
||||
checksum = "de6fce87c901c64837f745e7fffddeca1de8e054b544ba82c419905d40a0e1be"
|
||||
dependencies = [
|
||||
"dunce",
|
||||
"rand",
|
||||
"users",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1055,16 +1054,6 @@ dependencies = [
|
|||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "users"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24cc0f6d6f267b73e5a2cadf007ba8f9bc39c6a6f9666f8cf25ea809a153b032"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "utf8-ranges"
|
||||
version = "0.1.3"
|
||||
|
|
11
docs/sdk.md
11
docs/sdk.md
|
@ -5308,7 +5308,7 @@ ftp_put_in_memory
|
|||
<a name="std__process__Execute"></a>
|
||||
## `std::process::Execute`
|
||||
```sh
|
||||
exec [--fail-on-error] command [args]*
|
||||
exec [--fail-on-error|--get-exit-code] [--input value] command [args]*
|
||||
|
||||
output = exec command [args]*
|
||||
stdout = set ${output.stdout}
|
||||
|
@ -5334,6 +5334,7 @@ If an output variable is set and the --get-exit-code flag is provided, the outpu
|
|||
|
||||
* --fail-on-error - If no output variable is provided, it will cause an error in case the executed process exits with an error exit code.
|
||||
* --get-exit-code - If an output variable is provided, it will contain the exit code.
|
||||
* --input - Optional content to be sent to the child process input stream.
|
||||
* The command to execute and its arguments.
|
||||
|
||||
### Return Value
|
||||
|
@ -5421,7 +5422,7 @@ pid, process_id
|
|||
<a name="std__process__Spawn"></a>
|
||||
## `std::process::Spawn`
|
||||
```sh
|
||||
pid = spawn [--silent] command [args]*
|
||||
pid = spawn [--silent] [--input value] command [args]*
|
||||
```
|
||||
|
||||
Executes the provided native command and arguments.<br>
|
||||
|
@ -5429,7 +5430,8 @@ It will not wait for the process to finish and will return the process pid.
|
|||
|
||||
### Parameters
|
||||
|
||||
* Option --silent flag to suppress any output.
|
||||
* Optional --silent flag to suppress any output.
|
||||
* --input - Optional content to be sent to the child process input stream.
|
||||
* The command to execute and its arguments.
|
||||
|
||||
### Return Value
|
||||
|
@ -5451,7 +5453,7 @@ spawn
|
|||
<a name="std__process__Watchdog"></a>
|
||||
## `std::process::Watchdog`
|
||||
```sh
|
||||
count = watchdog [--max-retries value] [--interval value] -- command [arguments]*
|
||||
count = watchdog [--max-retries value] [--interval value] [--input value] -- command [arguments]*
|
||||
```
|
||||
|
||||
Executes the provided native command and arguments.<br>
|
||||
|
@ -5463,6 +5465,7 @@ In case of an invalid command, the watchdog will not reattempt the invocation an
|
|||
|
||||
* --max-retries - Positive value of max retries (excluding the first invocation). value <= 0 for unlimited retries. Default is unlimited.
|
||||
* --interval - The amount in milliseconds between retries. 0 for no waiting between invocations. Default is no wait.
|
||||
* --input - Optional content to be sent to the child process input stream.
|
||||
* The command to execute (preceded by a **--** separator).
|
||||
* The command arguments.
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@ cfg-if = "^1"
|
|||
colored = "^2"
|
||||
duckscript = { version = "^0.7.2", path = "../duckscript" }
|
||||
fs_extra = "^1"
|
||||
fsio = { version = "^0.3", features = ["temp-path"] }
|
||||
fsio = { version = "^0.3.1", features = ["temp-path"] }
|
||||
ftp = "^3"
|
||||
glob = "^0.3"
|
||||
heck = "^0.4"
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
```sh
|
||||
exec [--fail-on-error] command [args]*
|
||||
exec [--fail-on-error|--get-exit-code] [--input value] command [args]*
|
||||
|
||||
output = exec command [args]*
|
||||
stdout = set ${output.stdout}
|
||||
|
@ -25,6 +25,7 @@ If an output variable is set and the --get-exit-code flag is provided, the outpu
|
|||
|
||||
* --fail-on-error - If no output variable is provided, it will cause an error in case the executed process exits with an error exit code.
|
||||
* --get-exit-code - If an output variable is provided, it will contain the exit code.
|
||||
* --input - Optional content to be sent to the child process input stream.
|
||||
* The command to execute and its arguments.
|
||||
|
||||
### Return Value
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use crate::utils::exec::ExecInput;
|
||||
use crate::utils::{exec, pckg};
|
||||
use duckscript::types::command::{Command, CommandResult, Commands};
|
||||
use duckscript::types::instruction::Instruction;
|
||||
|
@ -8,6 +9,11 @@ use std::collections::HashMap;
|
|||
#[path = "./mod_test.rs"]
|
||||
mod mod_test;
|
||||
|
||||
enum LookingFor {
|
||||
Flag,
|
||||
Input,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct CommandImpl {
|
||||
package: String,
|
||||
|
@ -44,22 +50,48 @@ impl Command for CommandImpl {
|
|||
_commands: &mut Commands,
|
||||
_line: usize,
|
||||
) -> CommandResult {
|
||||
let allow_input = output_variable.is_some();
|
||||
let (print_output, start_index, fail_on_error, exit_code_output) =
|
||||
if !arguments.is_empty() && arguments[0] == "--fail-on-error" {
|
||||
(
|
||||
output_variable.is_none(),
|
||||
1,
|
||||
output_variable.is_none(),
|
||||
false,
|
||||
)
|
||||
} else if !arguments.is_empty() && arguments[0] == "--get-exit-code" {
|
||||
(true, 1, false, true)
|
||||
let mut input = if output_variable.is_some() {
|
||||
ExecInput::External
|
||||
} else {
|
||||
(output_variable.is_none(), 0, false, false)
|
||||
ExecInput::None
|
||||
};
|
||||
let mut command_start_index = 0;
|
||||
let mut print_output = output_variable.is_none();
|
||||
let mut fail_on_error = false;
|
||||
let mut exit_code_output = false;
|
||||
|
||||
match exec::exec(&arguments, print_output, allow_input, start_index) {
|
||||
let mut index = 0;
|
||||
let mut looking_for = LookingFor::Flag;
|
||||
for argument in &arguments {
|
||||
index = index + 1;
|
||||
|
||||
match looking_for {
|
||||
LookingFor::Flag => match argument.as_str() {
|
||||
"--fail-on-error" => {
|
||||
fail_on_error = output_variable.is_none();
|
||||
command_start_index = command_start_index + 1;
|
||||
}
|
||||
"--get-exit-code" => {
|
||||
exit_code_output = true;
|
||||
print_output = true;
|
||||
command_start_index = command_start_index + 1;
|
||||
}
|
||||
"--input" => {
|
||||
looking_for = LookingFor::Input;
|
||||
command_start_index = command_start_index + 1;
|
||||
}
|
||||
_ => break,
|
||||
},
|
||||
LookingFor::Input => {
|
||||
input = ExecInput::Text(argument.to_string());
|
||||
command_start_index = command_start_index + 1;
|
||||
|
||||
looking_for = LookingFor::Flag;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match exec::exec(&arguments, print_output, input, command_start_index) {
|
||||
Ok((stdout, stderr, exit_code)) => match output_variable {
|
||||
Some(name) => {
|
||||
if exit_code_output {
|
||||
|
|
|
@ -48,6 +48,24 @@ fn run_with_output() {
|
|||
assert_eq!(exit_code, "0");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "linux")]
|
||||
fn run_with_input() {
|
||||
let context = test::run_script_and_validate(
|
||||
vec![create("")],
|
||||
"out = exec --input test cat",
|
||||
CommandValidation::Match("out.code".to_string(), "0".to_string()),
|
||||
);
|
||||
|
||||
let stdout = context.variables.get("out.stdout").unwrap();
|
||||
let stderr = context.variables.get("out.stderr").unwrap();
|
||||
let exit_code = context.variables.get("out.code").unwrap();
|
||||
|
||||
assert!(stdout.contains("test"));
|
||||
assert!(stderr.is_empty());
|
||||
assert_eq!(exit_code, "0");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn run_error_code_with_output() {
|
||||
test::run_script_and_error(vec![create("")], "out = exec badcommand", "out");
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
```sh
|
||||
pid = spawn [--silent] command [args]*
|
||||
pid = spawn [--silent] [--input value] command [args]*
|
||||
```
|
||||
|
||||
Executes the provided native command and arguments.<br>
|
||||
|
@ -7,7 +7,8 @@ It will not wait for the process to finish and will return the process pid.
|
|||
|
||||
### Parameters
|
||||
|
||||
* Option --silent flag to suppress any output.
|
||||
* Optional --silent flag to suppress any output.
|
||||
* --input - Optional content to be sent to the child process input stream.
|
||||
* The command to execute and its arguments.
|
||||
|
||||
### Return Value
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use crate::utils::exec::ExecInput;
|
||||
use crate::utils::{exec, pckg};
|
||||
use duckscript::types::command::{Command, CommandResult};
|
||||
|
||||
|
@ -5,6 +6,11 @@ use duckscript::types::command::{Command, CommandResult};
|
|||
#[path = "./mod_test.rs"]
|
||||
mod mod_test;
|
||||
|
||||
enum LookingFor {
|
||||
Flag,
|
||||
Input,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct CommandImpl {
|
||||
package: String,
|
||||
|
@ -28,13 +34,37 @@ impl Command for CommandImpl {
|
|||
}
|
||||
|
||||
fn run(&self, arguments: Vec<String>) -> CommandResult {
|
||||
let (print_output, start_index) = if !arguments.is_empty() && arguments[0] == "--silent" {
|
||||
(false, 1)
|
||||
} else {
|
||||
(true, 0)
|
||||
};
|
||||
let mut print_output = true;
|
||||
let mut input = ExecInput::None;
|
||||
let mut command_start_index = 0;
|
||||
|
||||
match exec::spawn(&arguments, print_output, false, start_index) {
|
||||
let mut index = 0;
|
||||
let mut looking_for = LookingFor::Flag;
|
||||
for argument in &arguments {
|
||||
index = index + 1;
|
||||
|
||||
match looking_for {
|
||||
LookingFor::Flag => match argument.as_str() {
|
||||
"--silent" => {
|
||||
print_output = false;
|
||||
command_start_index = command_start_index + 1;
|
||||
}
|
||||
"--input" => {
|
||||
looking_for = LookingFor::Input;
|
||||
command_start_index = command_start_index + 1;
|
||||
}
|
||||
_ => break,
|
||||
},
|
||||
LookingFor::Input => {
|
||||
input = ExecInput::Text(argument.to_string());
|
||||
command_start_index = command_start_index + 1;
|
||||
|
||||
looking_for = LookingFor::Flag;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match exec::spawn(&arguments, print_output, true, input, command_start_index) {
|
||||
Ok(child) => {
|
||||
let pid = child.id();
|
||||
|
||||
|
|
|
@ -29,3 +29,13 @@ fn run_valid_silent() {
|
|||
CommandValidation::PositiveNumber("out".to_string()),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "linux")]
|
||||
fn run_valid_with_input() {
|
||||
test::run_script_and_validate(
|
||||
vec![create("")],
|
||||
"out = spawn --input test cat",
|
||||
CommandValidation::PositiveNumber("out".to_string()),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
```sh
|
||||
count = watchdog [--max-retries value] [--interval value] -- command [arguments]*
|
||||
count = watchdog [--max-retries value] [--interval value] [--input value] -- command [arguments]*
|
||||
```
|
||||
|
||||
Executes the provided native command and arguments.<br>
|
||||
|
@ -11,6 +11,7 @@ In case of an invalid command, the watchdog will not reattempt the invocation an
|
|||
|
||||
* --max-retries - Positive value of max retries (excluding the first invocation). value <= 0 for unlimited retries. Default is unlimited.
|
||||
* --interval - The amount in milliseconds between retries. 0 for no waiting between invocations. Default is no wait.
|
||||
* --input - Optional content to be sent to the child process input stream.
|
||||
* The command to execute (preceded by a **--** separator).
|
||||
* The command arguments.
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use crate::utils::exec::ExecInput;
|
||||
use crate::utils::{exec, pckg};
|
||||
use duckscript::types::command::{Command, CommandResult};
|
||||
use std::thread;
|
||||
|
@ -11,6 +12,7 @@ enum LookingFor {
|
|||
Flag,
|
||||
MaxRetries,
|
||||
Interval,
|
||||
Input,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
|
@ -41,6 +43,7 @@ impl Command for CommandImpl {
|
|||
} else {
|
||||
let mut max_retries: isize = -1;
|
||||
let mut interval: u64 = 0;
|
||||
let mut input = ExecInput::None;
|
||||
let mut command_start_index = 0;
|
||||
|
||||
let mut index = 0;
|
||||
|
@ -56,6 +59,7 @@ impl Command for CommandImpl {
|
|||
}
|
||||
"--max-retries" => looking_for = LookingFor::MaxRetries,
|
||||
"--interval" => looking_for = LookingFor::Interval,
|
||||
"--input" => looking_for = LookingFor::Input,
|
||||
_ => {
|
||||
return CommandResult::Error(
|
||||
format!("Unexpected argument: {} found", argument).to_string(),
|
||||
|
@ -94,6 +98,10 @@ impl Command for CommandImpl {
|
|||
|
||||
looking_for = LookingFor::Flag;
|
||||
}
|
||||
LookingFor::Input => {
|
||||
input = ExecInput::Text(argument.to_string());
|
||||
looking_for = LookingFor::Flag;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -106,7 +114,7 @@ impl Command for CommandImpl {
|
|||
loop {
|
||||
attempt = attempt + 1;
|
||||
|
||||
match exec::exec(&arguments, false, false, command_start_index) {
|
||||
match exec::exec(&arguments, false, input.clone(), command_start_index) {
|
||||
Ok(_) => (),
|
||||
Err(error) => return CommandResult::Error(error),
|
||||
}
|
||||
|
|
|
@ -39,6 +39,16 @@ fn run_with_retries() {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "linux")]
|
||||
fn run_with_input() {
|
||||
test::run_script_and_validate(
|
||||
vec![create("")],
|
||||
"out = watchdog --max-retries 0 --interval 0 --input test -- cat",
|
||||
CommandValidation::Match("out".to_string(), "1".to_string()),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn run_error_code_with_output() {
|
||||
test::run_script_and_error(vec![create("")], "out = watchdog badcommand", "out");
|
||||
|
|
|
@ -1,18 +1,26 @@
|
|||
use std::io::Write;
|
||||
use std::process::{Child, Command, Stdio};
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "./exec_test.rs"]
|
||||
mod exec_test;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) enum ExecInput {
|
||||
None,
|
||||
External,
|
||||
Text(String),
|
||||
}
|
||||
|
||||
pub(crate) fn exec(
|
||||
arguments: &Vec<String>,
|
||||
print_output: bool,
|
||||
allow_input: bool,
|
||||
input: ExecInput,
|
||||
start_index: usize,
|
||||
) -> Result<(String, String, i32), String> {
|
||||
let mut command = create_command(arguments, print_output, false, allow_input, start_index)?;
|
||||
let child = spawn(arguments, print_output, false, input, start_index)?;
|
||||
|
||||
match command.output() {
|
||||
match child.wait_with_output() {
|
||||
Ok(ref output) => {
|
||||
let stdout = String::from_utf8_lossy(&output.stdout).into_owned();
|
||||
let stderr = String::from_utf8_lossy(&output.stderr).into_owned();
|
||||
|
@ -36,13 +44,29 @@ pub(crate) fn exec(
|
|||
pub(crate) fn spawn(
|
||||
arguments: &Vec<String>,
|
||||
print_output: bool,
|
||||
allow_input: bool,
|
||||
output_blocking: bool,
|
||||
input: ExecInput,
|
||||
start_index: usize,
|
||||
) -> Result<Child, String> {
|
||||
let mut command = create_command(arguments, print_output, true, allow_input, start_index)?;
|
||||
let mut command = create_command(
|
||||
arguments,
|
||||
print_output,
|
||||
output_blocking,
|
||||
&input,
|
||||
start_index,
|
||||
)?;
|
||||
|
||||
match command.spawn() {
|
||||
Ok(child) => Ok(child),
|
||||
Ok(mut child) => match input {
|
||||
ExecInput::Text(value) => match child.stdin.as_mut() {
|
||||
Some(stdin) => match stdin.write_all(value.as_bytes()) {
|
||||
Ok(_) => Ok(child),
|
||||
Err(error) => Err(error.to_string()),
|
||||
},
|
||||
None => Err("Unable to write input to process".to_string()),
|
||||
},
|
||||
_ => Ok(child),
|
||||
},
|
||||
Err(error) => Err(error.to_string()),
|
||||
}
|
||||
}
|
||||
|
@ -51,7 +75,7 @@ fn create_command(
|
|||
arguments: &Vec<String>,
|
||||
print_output: bool,
|
||||
output_blocking: bool,
|
||||
allow_input: bool,
|
||||
input: &ExecInput,
|
||||
start_index: usize,
|
||||
) -> Result<Command, String> {
|
||||
if arguments.len() <= start_index {
|
||||
|
@ -64,16 +88,18 @@ fn create_command(
|
|||
command.arg(argument);
|
||||
}
|
||||
|
||||
if allow_input {
|
||||
command.stdin(Stdio::inherit());
|
||||
} else {
|
||||
command.stdin(Stdio::null());
|
||||
}
|
||||
match input {
|
||||
ExecInput::None => command.stdin(Stdio::null()),
|
||||
ExecInput::External => command.stdin(Stdio::inherit()),
|
||||
ExecInput::Text(_) => command.stdin(Stdio::piped()),
|
||||
};
|
||||
|
||||
if print_output {
|
||||
command.stdout(Stdio::inherit()).stderr(Stdio::inherit());
|
||||
} else if output_blocking {
|
||||
command.stdout(Stdio::null()).stderr(Stdio::null());
|
||||
} else {
|
||||
command.stdout(Stdio::piped()).stderr(Stdio::piped());
|
||||
}
|
||||
|
||||
Ok(command)
|
||||
|
|
|
@ -10,7 +10,7 @@ fn exec_valid() {
|
|||
"world".to_string(),
|
||||
],
|
||||
false,
|
||||
false,
|
||||
ExecInput::None,
|
||||
1,
|
||||
)
|
||||
.unwrap();
|
||||
|
@ -30,9 +30,25 @@ fn exec_error() {
|
|||
"world".to_string(),
|
||||
],
|
||||
false,
|
||||
false,
|
||||
ExecInput::None,
|
||||
0,
|
||||
);
|
||||
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "linux")]
|
||||
fn exec_with_input() {
|
||||
let (stdout, stderr, code) = exec(
|
||||
&vec!["cat".to_string()],
|
||||
false,
|
||||
ExecInput::Text("1 2 3".to_string()),
|
||||
0,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(code, 0);
|
||||
assert_eq!(stdout.trim(), "1 2 3");
|
||||
assert!(stderr.trim().is_empty());
|
||||
}
|
||||
|
|
|
@ -11,6 +11,20 @@ fn test_echo_with_output
|
|||
assert_eq ${exit_code} 0
|
||||
end
|
||||
|
||||
fn test_echo_with_input
|
||||
if not is_windows
|
||||
output = exec --input "1 2 3" cat
|
||||
|
||||
stdout = trim ${output.stdout}
|
||||
stderr = trim ${output.stderr}
|
||||
exit_code = set ${output.code}
|
||||
|
||||
assert_eq ${stdout} "1 2 3"
|
||||
assert_eq ${stderr} ""
|
||||
assert_eq ${exit_code} 0
|
||||
end
|
||||
end
|
||||
|
||||
fn test_echo_without_output
|
||||
exec echo hello world
|
||||
end
|
||||
|
|
|
@ -17,13 +17,21 @@ fn test_with_retries_and_interval
|
|||
assert_eq ${count} 4
|
||||
end
|
||||
|
||||
fn test_with_input
|
||||
if not is_windows
|
||||
count = watchdog --max-retries 0 --input 1 -- cat
|
||||
|
||||
assert_eq ${count} 1
|
||||
end
|
||||
end
|
||||
|
||||
fn test_bad_command
|
||||
count = watchdog --max-retries 3 --interval 10 -- badcommand
|
||||
|
||||
assert_eq ${count} false
|
||||
end
|
||||
|
||||
fn test_negatived_max_retries
|
||||
fn test_negative_max_retries
|
||||
count = watchdog --max-retries -3 --interval 10 -- echo test
|
||||
|
||||
assert_eq ${count} 1
|
||||
|
|
Loading…
Reference in a new issue