The if/else and not commands now support complex conditions

This commit is contained in:
sagie gur ari 2020-02-05 19:09:27 +00:00
parent c54af0f297
commit 4cc8f820b9
15 changed files with 581 additions and 47 deletions

View File

@ -2,6 +2,7 @@
### v0.1.9 ### v0.1.9
* The if/else and not commands now support complex conditions.
* Release command now support recursive option. * Release command now support recursive option.
* New map_clear command. * New map_clear command.
* New map_to_properties command. * New map_to_properties command.

View File

@ -348,12 +348,12 @@ end
value = set false value = set false
if ${value} if ${value}
echo should not be here echo should not be here
elseif true elseif true or false
echo in else if but not done yet echo in else if but not done yet
value = set true value = set true
if not false if not true and false
echo nested if echo nested if
value = set "some text" value = set "some text"

View File

@ -304,12 +304,12 @@ end
value = set false value = set false
if ${value} if ${value}
echo should not be here echo should not be here
elseif true elseif true or false
echo in else if but not done yet echo in else if but not done yet
value = set true value = set true
if not false if not true and false
echo nested if echo nested if
value = set "some text" value = set "some text"

View File

@ -343,7 +343,7 @@ goto
<a name="std__If"></a> <a name="std__If"></a>
## std::If ## std::If
```sh ```sh
if command|value if [command|value|condition]
# commands # commands
elseif command|value elseif command|value
# commands # commands
@ -363,8 +363,9 @@ if and elseif commands accept either:
* A command with optional arguments and invokes it * A command with optional arguments and invokes it
* A single value which doesn't match any known command * A single value which doesn't match any known command
* A condition statement
If the value or the result of the command is one of the following: If the result is one of the following:
* No output * No output
* false (case insensitive) * false (case insensitive)
@ -378,6 +379,9 @@ If a truthy (non falsy) output is found, it will invoke the commands of that cod
if blocks can be nested in other if blocks (see examples). if blocks can be nested in other if blocks (see examples).
A condition statement is made up of values, or/and keywords and '('/')' groups.<br>
Each must be separated with a space character.
#### Parameters #### Parameters
* if/elseif - A command and its arguments to invoke and evaluate its output, if a single value is provided an no such command exists, it is evaluated as a value. * if/elseif - A command and its arguments to invoke and evaluate its output, if a single value is provided an no such command exists, it is evaluated as a value.
@ -433,6 +437,16 @@ elseif set true
else else
echo should not be here echo should not be here
end end
valid = set false
if true and false or true and false or ( true and true or false )
valid = set true
end
assert ${valid}
if true and false or true and false or ( true and true or false ) and false
assert_fail
end
``` ```
@ -469,7 +483,7 @@ is_defined
<a name="std__Not"></a> <a name="std__Not"></a>
## std::Not ## std::Not
```sh ```sh
output = not command|value output = not [command|value|condition]
``` ```
Enables to switch falsy to true and truthy to false.<br> Enables to switch falsy to true and truthy to false.<br>
@ -477,8 +491,9 @@ The **not** commands accept either:
* A command with optional arguments and invokes it * A command with optional arguments and invokes it
* A single value which doesn't match any known command * A single value which doesn't match any known command
* A condition statement
If the value or the result of the command is one of the following: If the result is one of the following:
* No output * No output
* false (case insensitive) * false (case insensitive)
@ -488,6 +503,9 @@ If the value or the result of the command is one of the following:
It will return true, otherwise it will return false. It will return true, otherwise it will return false.
A condition statement is made up of values, or/and keywords and '('/')' groups.<br>
Each must be separated with a space character.
#### Parameters #### Parameters
A command and its arguments to invoke and evaluate its output, if a single value is provided an no such command exists, it is evaluated as a value. A command and its arguments to invoke and evaluate its output, if a single value is provided an no such command exists, it is evaluated as a value.
@ -499,19 +517,41 @@ The switched value of the input.
#### Examples #### Examples
```sh ```sh
# Simple example of converting true/false values fn test_not_true
is_false = not true value = not true
echo is false: ${is_false}
is_true = not false assert_false ${value}
echo is true: ${is_true} end
# Example of converting command output value fn test_not_false
is_false = not set true value = not false
echo is false: ${is_false}
is_true = not set false assert ${value}
echo is true: ${is_true} end
fn test_not_command_true
value = not set true
assert_false ${value}
end
fn test_not_command_false
value = not set false
assert ${value}
end
fn test_not_condition_true
value = not true and false or true and false or ( true and true or false )
assert_false ${value}
end
fn test_not_condition_false
value = not true and false or true and false or ( true and true or false ) and false
assert ${value}
end
``` ```

View File

@ -14,11 +14,18 @@ fn load_valid() {
let mut context = Context::new(); let mut context = Context::new();
let result = load(&mut context.commands); let result = load(&mut context.commands);
match result { assert!(result.is_ok());
Ok(_) => (),
Err(e) => panic!("{:#?}", e), assert!(!context.commands.get_all_command_names().is_empty());
}; }
// assert!(result.is_ok());
#[test]
fn test_scripts() {
let mut context = Context::new();
let result = load(&mut context.commands);
assert!(result.is_ok());
assert!(!context.commands.get_all_command_names().is_empty()); assert!(!context.commands.get_all_command_names().is_empty());
let result = runner::run_script("test_directory ../test", context); let result = runner::run_script("test_directory ../test", context);

View File

@ -1,5 +1,5 @@
```sh ```sh
if command|value if [command|value|condition]
# commands # commands
elseif command|value elseif command|value
# commands # commands
@ -19,8 +19,9 @@ if and elseif commands accept either:
* A command with optional arguments and invokes it * A command with optional arguments and invokes it
* A single value which doesn't match any known command * A single value which doesn't match any known command
* A condition statement
If the value or the result of the command is one of the following: If the result is one of the following:
* No output * No output
* false (case insensitive) * false (case insensitive)
@ -34,6 +35,9 @@ If a truthy (non falsy) output is found, it will invoke the commands of that cod
if blocks can be nested in other if blocks (see examples). if blocks can be nested in other if blocks (see examples).
A condition statement is made up of values, or/and keywords and '('/')' groups.<br>
Each must be separated with a space character.
#### Parameters #### Parameters
* if/elseif - A command and its arguments to invoke and evaluate its output, if a single value is provided an no such command exists, it is evaluated as a value. * if/elseif - A command and its arguments to invoke and evaluate its output, if a single value is provided an no such command exists, it is evaluated as a value.
@ -89,4 +93,14 @@ elseif set true
else else
echo should not be here echo should not be here
end end
valid = set false
if true and false or true and false or ( true and true or false )
valid = set true
end
assert ${valid}
if true and false or true and false or ( true and true or false ) and false
assert_fail
end
``` ```

View File

@ -1,5 +1,5 @@
```sh ```sh
output = not command|value output = not [command|value|condition]
``` ```
Enables to switch falsy to true and truthy to false.<br> Enables to switch falsy to true and truthy to false.<br>
@ -7,8 +7,9 @@ The **not** commands accept either:
* A command with optional arguments and invokes it * A command with optional arguments and invokes it
* A single value which doesn't match any known command * A single value which doesn't match any known command
* A condition statement
If the value or the result of the command is one of the following: If the result is one of the following:
* No output * No output
* false (case insensitive) * false (case insensitive)
@ -18,6 +19,9 @@ If the value or the result of the command is one of the following:
It will return true, otherwise it will return false. It will return true, otherwise it will return false.
A condition statement is made up of values, or/and keywords and '('/')' groups.<br>
Each must be separated with a space character.
#### Parameters #### Parameters
A command and its arguments to invoke and evaluate its output, if a single value is provided an no such command exists, it is evaluated as a value. A command and its arguments to invoke and evaluate its output, if a single value is provided an no such command exists, it is evaluated as a value.
@ -29,17 +33,39 @@ The switched value of the input.
#### Examples #### Examples
```sh ```sh
# Simple example of converting true/false values fn test_not_true
is_false = not true value = not true
echo is false: ${is_false}
is_true = not false assert_false ${value}
echo is true: ${is_true} end
# Example of converting command output value fn test_not_false
is_false = not set true value = not false
echo is false: ${is_false}
is_true = not set false assert ${value}
echo is true: ${is_true} end
fn test_not_command_true
value = not set true
assert_false ${value}
end
fn test_not_command_false
value = not set false
assert ${value}
end
fn test_not_condition_true
value = not true and false or true and false or ( true and true or false )
assert_false ${value}
end
fn test_not_condition_false
value = not true and false or true and false or ( true and true or false ) and false
assert ${value}
end
``` ```

View File

@ -7,6 +7,13 @@ use std::collections::HashMap;
#[path = "./condition_test.rs"] #[path = "./condition_test.rs"]
mod condition_test; mod condition_test;
enum FoundToken {
None,
And,
Or,
Value,
}
pub(crate) fn is_true(value: Option<String>) -> bool { pub(crate) fn is_true(value: Option<String>) -> bool {
let failed = match value { let failed = match value {
Some(value_str) => { Some(value_str) => {
@ -28,11 +35,7 @@ pub(crate) fn eval_condition(
if arguments.is_empty() { if arguments.is_empty() {
Ok(is_true(None)) Ok(is_true(None))
} else { } else {
let eval_statement = if arguments.len() == 1 { let eval_statement = commands.exists(&arguments[0]);
commands.exists(&arguments[0])
} else {
true
};
if eval_statement { if eval_statement {
match eval::eval_with_error(&arguments, state, variables, commands) { match eval::eval_with_error(&arguments, state, variables, commands) {
@ -45,9 +48,125 @@ pub(crate) fn eval_condition(
_ => Err("Invalid condition evaluation result.".to_string()), _ => Err("Invalid condition evaluation result.".to_string()),
} }
} else { } else {
let passed = is_true(Some(arguments[0].to_string())); eval_condition_for_slice(&arguments[..])
}
Ok(passed) }
}
pub(crate) fn eval_condition_for_slice(arguments: &[String]) -> Result<bool, String> {
if arguments.is_empty() {
Ok(is_true(None))
} else {
let mut searching_block_end = false;
let mut start_block = 0;
let mut counter = 0;
let mut index = 0;
let mut total_evaluated = None;
let mut partial_evaluated = None;
let mut found_token = FoundToken::None;
for argument in arguments {
if argument == "(" {
searching_block_end = true;
if counter == 0 {
start_block = index + 1
}
counter = counter + 1;
} else if argument == ")" {
counter = counter - 1;
if counter == 0 {
searching_block_end = false;
match eval_condition_for_slice(&arguments[start_block..index]) {
Ok(evaluated) => {
start_block = 0;
match found_token {
FoundToken::None => {
total_evaluated = Some(evaluated);
found_token = FoundToken::Value;
}
FoundToken::And => {
partial_evaluated = Some(evaluated);
found_token = FoundToken::Value;
}
FoundToken::Or => {
partial_evaluated =
Some(evaluated || partial_evaluated.unwrap_or(false));
found_token = FoundToken::Value;
}
FoundToken::Value => {
return Err(
format!("Unexpected value: {}", argument).to_string()
);
}
}
}
Err(error) => return Err(error),
};
} else if counter < 0 {
return Err("Unexpected ')'".to_string());
}
} else if !searching_block_end {
if argument == "and" {
match found_token {
FoundToken::Value => {
found_token = FoundToken::And;
total_evaluated = Some(
total_evaluated.unwrap_or(true)
&& partial_evaluated.unwrap_or(true),
);
partial_evaluated = None;
if !total_evaluated.unwrap() {
return Ok(false);
}
}
_ => return Err("Unexpected 'and'".to_string()),
}
} else if argument == "or" {
match found_token {
FoundToken::Value => found_token = FoundToken::Or,
_ => return Err("Unexpected 'or'".to_string()),
}
} else {
let evaluated = is_true(Some(argument.to_string()));
match found_token {
FoundToken::None => {
partial_evaluated = Some(evaluated);
found_token = FoundToken::Value;
}
FoundToken::And => {
partial_evaluated = Some(evaluated);
found_token = FoundToken::Value;
}
FoundToken::Or => {
partial_evaluated =
Some(evaluated || partial_evaluated.unwrap_or(false));
found_token = FoundToken::Value;
}
FoundToken::Value => {
return Err(format!("Unexpected value: {}", argument).to_string());
}
}
}
}
index = index + 1;
}
if searching_block_end {
Err("Missing ')'".to_string())
} else {
let total_bool = if total_evaluated.is_none() && partial_evaluated.is_none() {
is_true(None)
} else {
partial_evaluated.unwrap_or(true) && total_evaluated.unwrap_or(true)
};
Ok(total_bool)
} }
} }
} }

View File

@ -166,3 +166,290 @@ fn eval_condition_command_error() {
assert!(result.is_err()); assert!(result.is_err());
} }
#[test]
fn eval_condition_for_slice_empty() {
let result = eval_condition_for_slice(&vec![]);
let output = result.unwrap();
assert!(!output);
}
#[test]
fn eval_condition_for_slice_true() {
let result = eval_condition_for_slice(&vec!["true".to_string()]);
let output = result.unwrap();
assert!(output);
}
#[test]
fn eval_condition_for_slice_false() {
let result = eval_condition_for_slice(&vec!["false".to_string()]);
let output = result.unwrap();
assert!(!output);
}
#[test]
fn eval_condition_for_slice_true_and_false() {
let result = eval_condition_for_slice(&vec![
"true".to_string(),
"and".to_string(),
"false".to_string(),
]);
let output = result.unwrap();
assert!(!output);
}
#[test]
fn eval_condition_for_slice_false_and_true() {
let result = eval_condition_for_slice(&vec![
"false".to_string(),
"and".to_string(),
"true".to_string(),
]);
let output = result.unwrap();
assert!(!output);
}
#[test]
fn eval_condition_for_slice_true_or_false() {
let result = eval_condition_for_slice(&vec![
"true".to_string(),
"or".to_string(),
"false".to_string(),
]);
let output = result.unwrap();
assert!(output);
}
#[test]
fn eval_condition_for_slice_false_or_true() {
let result = eval_condition_for_slice(&vec![
"false".to_string(),
"or".to_string(),
"true".to_string(),
]);
let output = result.unwrap();
assert!(output);
}
#[test]
fn eval_condition_for_slice_complex_no_parts() {
let mut result = eval_condition_for_slice(&vec![
"false".to_string(),
"or".to_string(),
"true".to_string(),
"or".to_string(),
"false".to_string(),
]);
assert!(result.unwrap());
result = eval_condition_for_slice(&vec![
"true".to_string(),
"and".to_string(),
"false".to_string(),
"or".to_string(),
"true".to_string(),
"and".to_string(),
"false".to_string(),
"or".to_string(),
"true".to_string(),
]);
assert!(result.unwrap());
result = eval_condition_for_slice(&vec![
"false".to_string(),
"and".to_string(),
"false".to_string(),
"or".to_string(),
"true".to_string(),
"and".to_string(),
"false".to_string(),
"or".to_string(),
"true".to_string(),
]);
assert!(!result.unwrap());
result = eval_condition_for_slice(&vec![
"true".to_string(),
"and".to_string(),
"false".to_string(),
"or".to_string(),
"true".to_string(),
"and".to_string(),
"false".to_string(),
"or".to_string(),
"false".to_string(),
]);
assert!(!result.unwrap());
}
#[test]
fn eval_condition_for_slice_complex_with_parts() {
let mut result = eval_condition_for_slice(&vec![
"(".to_string(),
"false".to_string(),
"or".to_string(),
"true".to_string(),
")".to_string(),
]);
assert!(result.unwrap());
result = eval_condition_for_slice(&vec![
"(".to_string(),
"false".to_string(),
"or".to_string(),
"(".to_string(),
"true".to_string(),
")".to_string(),
")".to_string(),
]);
assert!(result.unwrap());
result = eval_condition_for_slice(&vec![
"true".to_string(),
"and".to_string(),
"(".to_string(),
"false".to_string(),
"or".to_string(),
"true".to_string(),
")".to_string(),
"and".to_string(),
"false".to_string(),
"or".to_string(),
"false".to_string(),
]);
assert!(!result.unwrap());
result = eval_condition_for_slice(&vec![
"true".to_string(),
"and".to_string(),
"(".to_string(),
"false".to_string(),
"or".to_string(),
"true".to_string(),
"and".to_string(),
"false".to_string(),
")".to_string(),
"or".to_string(),
"true".to_string(),
]);
assert!(result.unwrap());
result = eval_condition_for_slice(&vec![
"(".to_string(),
"(".to_string(),
"(".to_string(),
")".to_string(),
")".to_string(),
")".to_string(),
]);
assert!(!result.unwrap());
result = eval_condition_for_slice(&vec![
"true".to_string(),
"and".to_string(),
"false".to_string(),
"or".to_string(),
"true".to_string(),
"and".to_string(),
"false".to_string(),
"or".to_string(),
"(".to_string(),
"true".to_string(),
"and".to_string(),
"true".to_string(),
"or".to_string(),
"false".to_string(),
")".to_string(),
"and".to_string(),
"false".to_string(),
]);
assert!(!result.unwrap());
result = eval_condition_for_slice(&vec![
"true".to_string(),
"and".to_string(),
"false".to_string(),
"or".to_string(),
"true".to_string(),
"and".to_string(),
"false".to_string(),
"or".to_string(),
"(".to_string(),
"true".to_string(),
"and".to_string(),
"true".to_string(),
"or".to_string(),
"false".to_string(),
")".to_string(),
]);
assert!(result.unwrap());
}
#[test]
fn eval_condition_for_slice_parse_errors() {
let mut result = eval_condition_for_slice(&vec!["or".to_string()]);
assert!(result.is_err());
result = eval_condition_for_slice(&vec!["and".to_string()]);
assert!(result.is_err());
result = eval_condition_for_slice(&vec!["(".to_string()]);
assert!(result.is_err());
result = eval_condition_for_slice(&vec![")".to_string()]);
assert!(result.is_err());
result = eval_condition_for_slice(&vec!["(".to_string(), "true".to_string()]);
assert!(result.is_err());
result = eval_condition_for_slice(&vec![
"false".to_string(),
"or".to_string(),
"true".to_string(),
")".to_string(),
]);
assert!(result.is_err());
result = eval_condition_for_slice(&vec!["false".to_string(), "true".to_string()]);
assert!(result.is_err());
result = eval_condition_for_slice(&vec![
"true".to_string(),
"or".to_string(),
"or".to_string(),
"true".to_string(),
]);
assert!(result.is_err());
result = eval_condition_for_slice(&vec![
"true".to_string(),
"and".to_string(),
"and".to_string(),
"true".to_string(),
]);
assert!(result.is_err());
result = eval_condition_for_slice(&vec![
"true".to_string(),
"and".to_string(),
"or".to_string(),
"true".to_string(),
]);
assert!(result.is_err());
result = eval_condition_for_slice(&vec![
"true".to_string(),
"or".to_string(),
"and".to_string(),
"true".to_string(),
]);
assert!(result.is_err());
}

View File

@ -31,7 +31,7 @@ end
if set false if set false
echo should not be here echo should not be here
elseif set true elseif true or false
echo in else if but not done yet echo in else if but not done yet
if set true if set true
@ -49,7 +49,7 @@ elseif true
value = set true value = set true
if not false if not true and false
echo nested if echo nested if
value = set "some text" value = set "some text"

View File

@ -35,6 +35,22 @@ fn test_if_command_returns_true
assert ${valid} assert ${valid}
end end
fn test_if_condition_true
valid = set false
if true and false or true and false or ( true and true or false )
valid = set true
end
assert ${valid}
end
fn test_if_condition_false
if true and false or true and false or ( true and true or false ) and false
assert_fail
end
end
fn test_simple_else fn test_simple_else
valid = set false valid = set false

View File

@ -10,3 +10,27 @@ fn test_not_false
assert ${value} assert ${value}
end end
fn test_not_command_true
value = not set true
assert_false ${value}
end
fn test_not_command_false
value = not set false
assert ${value}
end
fn test_not_condition_true
value = not true and false or true and false or ( true and true or false )
assert_false ${value}
end
fn test_not_condition_false
value = not true and false or true and false or ( true and true or false ) and false
assert ${value}
end