Skip to content

Commit c1b98e1

Browse files
authored
Merge pull request #979 from SteveL-MSFT/logical-functions
Add remaining logical functions: and, bool, false, true, not, or
2 parents 012cf3d + dc1c1b3 commit c1b98e1

File tree

9 files changed

+474
-0
lines changed

9 files changed

+474
-0
lines changed

dsc/tests/dsc_expressions.tests.ps1

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,4 +107,35 @@ resources:
107107
$LASTEXITCODE | Should -Be 0
108108
$out.results[0].result[1].result.actualState.output.family | Should -BeExactly $out.results[0].result[0].result.actualState.family
109109
}
110+
111+
It 'Logical functions work: <expression>' -TestCases @(
112+
@{ expression = "[equals('a', 'a')]"; expected = $true }
113+
@{ expression = "[equals('a', 'b')]"; expected = $false }
114+
@{ expression = "[not(equals('a', 'b'))]"; expected = $true }
115+
@{ expression = "[and(true, true)]"; expected = $true }
116+
@{ expression = "[and(true, false)]"; expected = $false }
117+
@{ expression = "[or(false, true)]"; expected = $true }
118+
@{ expression = "[or(false, false)]"; expected = $false }
119+
@{ expression = "[not(true)]"; expected = $false }
120+
@{ expression = "[not(or(true, false))]"; expected = $false }
121+
@{ expression = "[bool('TRUE')]" ; expected = $true }
122+
@{ expression = "[bool('False')]" ; expected = $false }
123+
@{ expression = "[bool(1)]" ; expected = $true }
124+
@{ expression = "[not(bool(0))]" ; expected = $true }
125+
@{ expression = "[true()]" ; expected = $true }
126+
@{ expression = "[false()]" ; expected = $false }
127+
) {
128+
param($expression, $expected)
129+
$yaml = @"
130+
`$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
131+
resources:
132+
- name: echo
133+
type: Microsoft.DSC.Debug/Echo
134+
properties:
135+
output: "$expression"
136+
"@
137+
$out = dsc config get -i $yaml 2>$TestDrive/error.log | ConvertFrom-Json
138+
$LASTEXITCODE | Should -Be 0 -Because (Get-Content $TestDrive/error.log -Raw | Out-String)
139+
$out.results[0].result.actualState.output | Should -Be $expected -Because ($out | ConvertTo-Json -Depth 10| Out-String)
140+
}
110141
}

dsc_lib/locales/en-us.toml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,9 +206,17 @@ noStringArgs = "Function '%{name}' does not accept string arguments, accepted ty
206206
description = "Adds two or more numbers together"
207207
invoked = "add function"
208208

209+
[functions.and]
210+
description = "Evaluates if all arguments are true"
211+
invoked = "and function"
212+
209213
[functions.base64]
210214
description = "Encodes a string to Base64 format"
211215

216+
[functions.bool]
217+
description = "Converts a string or number to a boolean"
218+
invoked = "bool function"
219+
212220
[functions.concat]
213221
description = "Concatenates two or more strings or arrays"
214222
invoked = "concat function"
@@ -236,6 +244,10 @@ notFound = "Environment variable not found"
236244
[functions.equals]
237245
description = "Evaluates if the two values are the same"
238246

247+
[functions.false]
248+
description = "Returns the boolean value false"
249+
invoked = "false function"
250+
239251
[functions.format]
240252
description = "Formats a string using the given arguments"
241253
experimental = "`format()` function is experimental"
@@ -276,6 +288,14 @@ divideByZero = "Cannot divide by zero"
276288
description = "Multiplies two or more numbers together"
277289
invoked = "mul function"
278290

291+
[functions.not]
292+
description = "Negates a boolean value"
293+
invoked = "not function"
294+
295+
[functions.or]
296+
description = "Evaluates if any arguments are true"
297+
invoked = "or function"
298+
279299
[functions.parameters]
280300
description = "Retrieves parameters from the configuration"
281301
invoked = "parameters function"
@@ -316,6 +336,10 @@ invoked = "sub function"
316336
description = "Returns the system root path"
317337
invoked = "systemRoot function"
318338

339+
[functions.true]
340+
description = "Returns the boolean value true"
341+
invoked = "true function"
342+
319343
[functions.variables]
320344
description = "Retrieves the value of a variable"
321345
invoked = "variables function"

dsc_lib/src/functions/and.rs

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
use crate::DscError;
5+
use crate::configure::context::Context;
6+
use crate::functions::{AcceptedArgKind, Function, FunctionCategory};
7+
use rust_i18n::t;
8+
use serde_json::Value;
9+
use tracing::debug;
10+
11+
#[derive(Debug, Default)]
12+
pub struct And {}
13+
14+
impl Function for And {
15+
fn description(&self) -> String {
16+
t!("functions.and.description").to_string()
17+
}
18+
19+
fn category(&self) -> FunctionCategory {
20+
FunctionCategory::Logical
21+
}
22+
23+
fn min_args(&self) -> usize {
24+
2
25+
}
26+
27+
fn max_args(&self) -> usize {
28+
usize::MAX
29+
}
30+
31+
fn accepted_arg_types(&self) -> Vec<AcceptedArgKind> {
32+
vec![AcceptedArgKind::Boolean]
33+
}
34+
35+
fn invoke(&self, args: &[Value], _context: &Context) -> Result<Value, DscError> {
36+
debug!("{}", t!("functions.and.invoked"));
37+
for arg in args {
38+
if let Some(value) = arg.as_bool() {
39+
if !value {
40+
return Ok(Value::Bool(false));
41+
}
42+
} else {
43+
return Err(DscError::Parser(t!("functions.invalidArguments").to_string()));
44+
}
45+
}
46+
Ok(Value::Bool(true))
47+
}
48+
}
49+
50+
#[cfg(test)]
51+
mod tests {
52+
use crate::configure::context::Context;
53+
use crate::parser::Statement;
54+
55+
#[test]
56+
fn two_values() {
57+
let mut parser = Statement::new().unwrap();
58+
let result = parser.parse_and_execute("[and(true, false)]", &Context::new()).unwrap();
59+
assert_eq!(result, false);
60+
}
61+
62+
#[test]
63+
fn multiple_values() {
64+
let mut parser = Statement::new().unwrap();
65+
let result = parser.parse_and_execute("[and(true, false, true)]", &Context::new()).unwrap();
66+
assert_eq!(result, false);
67+
}
68+
69+
#[test]
70+
fn all_false() {
71+
let mut parser = Statement::new().unwrap();
72+
let result = parser.parse_and_execute("[and(false, false)]", &Context::new()).unwrap();
73+
assert_eq!(result, false);
74+
}
75+
76+
#[test]
77+
fn all_true() {
78+
let mut parser = Statement::new().unwrap();
79+
let result = parser.parse_and_execute("[and(true, true)]", &Context::new()).unwrap();
80+
assert_eq!(result, true);
81+
}
82+
}

dsc_lib/src/functions/bool.rs

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
use crate::DscError;
5+
use crate::configure::context::Context;
6+
use crate::functions::{AcceptedArgKind, Function, FunctionCategory};
7+
use rust_i18n::t;
8+
use serde_json::Value;
9+
use tracing::debug;
10+
11+
#[derive(Debug, Default)]
12+
pub struct Bool {}
13+
14+
impl Function for Bool {
15+
fn description(&self) -> String {
16+
t!("functions.bool.description").to_string()
17+
}
18+
19+
fn category(&self) -> FunctionCategory {
20+
FunctionCategory::Logical
21+
}
22+
23+
fn min_args(&self) -> usize {
24+
1
25+
}
26+
27+
fn max_args(&self) -> usize {
28+
1
29+
}
30+
31+
fn accepted_arg_types(&self) -> Vec<AcceptedArgKind> {
32+
vec![AcceptedArgKind::String, AcceptedArgKind::Number]
33+
}
34+
35+
fn invoke(&self, args: &[Value], _context: &Context) -> Result<Value, DscError> {
36+
debug!("{}", t!("functions.bool.invoked"));
37+
if let Some(arg) = args[0].as_str() {
38+
match arg.to_lowercase().as_str() {
39+
"true" => Ok(Value::Bool(true)),
40+
"false" => Ok(Value::Bool(false)),
41+
_ => Err(DscError::Parser(t!("functions.invalidArguments").to_string())),
42+
}
43+
} else if let Some(num) = args[0].as_i64() {
44+
Ok(Value::Bool(num != 0))
45+
} else {
46+
Err(DscError::Parser(t!("functions.invalidArguments").to_string()))
47+
}
48+
}
49+
}
50+
51+
#[cfg(test)]
52+
mod tests {
53+
use crate::configure::context::Context;
54+
use crate::parser::Statement;
55+
56+
#[test]
57+
fn true_string() {
58+
let mut parser = Statement::new().unwrap();
59+
let result = parser.parse_and_execute("[bool('true')]", &Context::new()).unwrap();
60+
assert_eq!(result, true);
61+
}
62+
63+
#[test]
64+
fn false_string() {
65+
let mut parser = Statement::new().unwrap();
66+
let result = parser.parse_and_execute("[bool('false')]", &Context::new()).unwrap();
67+
assert_eq!(result, false);
68+
}
69+
70+
#[test]
71+
fn number_1() {
72+
let mut parser = Statement::new().unwrap();
73+
let result = parser.parse_and_execute("[bool(1)]", &Context::new()).unwrap();
74+
assert_eq!(result, true);
75+
}
76+
77+
#[test]
78+
fn number_0() {
79+
let mut parser = Statement::new().unwrap();
80+
let result = parser.parse_and_execute("[bool(0)]", &Context::new()).unwrap();
81+
assert_eq!(result, false);
82+
}
83+
}

dsc_lib/src/functions/false.rs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
use crate::DscError;
5+
use crate::configure::context::Context;
6+
use crate::functions::{AcceptedArgKind, Function, FunctionCategory};
7+
use rust_i18n::t;
8+
use serde_json::Value;
9+
use tracing::debug;
10+
11+
#[derive(Debug, Default)]
12+
pub struct False {}
13+
14+
impl Function for False {
15+
fn description(&self) -> String {
16+
t!("functions.false.description").to_string()
17+
}
18+
19+
fn category(&self) -> FunctionCategory {
20+
FunctionCategory::Logical
21+
}
22+
23+
fn min_args(&self) -> usize {
24+
0
25+
}
26+
27+
fn max_args(&self) -> usize {
28+
0
29+
}
30+
31+
fn accepted_arg_types(&self) -> Vec<AcceptedArgKind> {
32+
vec![]
33+
}
34+
35+
fn invoke(&self, _args: &[Value], _context: &Context) -> Result<Value, DscError> {
36+
debug!("{}", t!("functions.false.invoked"));
37+
Ok(Value::Bool(false))
38+
}
39+
}
40+
41+
#[cfg(test)]
42+
mod tests {
43+
use crate::configure::context::Context;
44+
use crate::parser::Statement;
45+
46+
#[test]
47+
fn false_function() {
48+
let mut parser = Statement::new().unwrap();
49+
let result = parser.parse_and_execute("[false()]", &Context::new()).unwrap();
50+
assert_eq!(result, false);
51+
}
52+
}

dsc_lib/src/functions/mod.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,26 +12,32 @@ use serde_json::Value;
1212
use std::fmt::Display;
1313

1414
pub mod add;
15+
pub mod and;
1516
pub mod base64;
17+
pub mod bool;
1618
pub mod concat;
1719
pub mod create_array;
1820
pub mod div;
1921
pub mod envvar;
2022
pub mod equals;
2123
pub mod r#if;
24+
pub mod r#false;
2225
pub mod format;
2326
pub mod int;
2427
pub mod max;
2528
pub mod min;
2629
pub mod mod_function;
2730
pub mod mul;
31+
pub mod not;
32+
pub mod or;
2833
pub mod parameters;
2934
pub mod path;
3035
pub mod reference;
3136
pub mod resource_id;
3237
pub mod secret;
3338
pub mod sub;
3439
pub mod system_root;
40+
pub mod r#true;
3541
pub mod variables;
3642

3743
/// The kind of argument that a function accepts.
@@ -77,26 +83,32 @@ impl FunctionDispatcher {
7783
pub fn new() -> Self {
7884
let mut functions: HashMap<String, Box<dyn Function>> = HashMap::new();
7985
functions.insert("add".to_string(), Box::new(add::Add{}));
86+
functions.insert("and".to_string(), Box::new(and::And{}));
8087
functions.insert("base64".to_string(), Box::new(base64::Base64{}));
88+
functions.insert("bool".to_string(), Box::new(bool::Bool{}));
8189
functions.insert("concat".to_string(), Box::new(concat::Concat{}));
8290
functions.insert("createArray".to_string(), Box::new(create_array::CreateArray{}));
8391
functions.insert("div".to_string(), Box::new(div::Div{}));
8492
functions.insert("envvar".to_string(), Box::new(envvar::Envvar{}));
8593
functions.insert("equals".to_string(), Box::new(equals::Equals{}));
94+
functions.insert("false".to_string(), Box::new(r#false::False{}));
8695
functions.insert("if".to_string(), Box::new(r#if::If{}));
8796
functions.insert("format".to_string(), Box::new(format::Format{}));
8897
functions.insert("int".to_string(), Box::new(int::Int{}));
8998
functions.insert("max".to_string(), Box::new(max::Max{}));
9099
functions.insert("min".to_string(), Box::new(min::Min{}));
91100
functions.insert("mod".to_string(), Box::new(mod_function::Mod{}));
92101
functions.insert("mul".to_string(), Box::new(mul::Mul{}));
102+
functions.insert("not".to_string(), Box::new(not::Not{}));
103+
functions.insert("or".to_string(), Box::new(or::Or{}));
93104
functions.insert("parameters".to_string(), Box::new(parameters::Parameters{}));
94105
functions.insert("path".to_string(), Box::new(path::Path{}));
95106
functions.insert("reference".to_string(), Box::new(reference::Reference{}));
96107
functions.insert("resourceId".to_string(), Box::new(resource_id::ResourceId{}));
97108
functions.insert("secret".to_string(), Box::new(secret::Secret{}));
98109
functions.insert("sub".to_string(), Box::new(sub::Sub{}));
99110
functions.insert("systemRoot".to_string(), Box::new(system_root::SystemRoot{}));
111+
functions.insert("true".to_string(), Box::new(r#true::True{}));
100112
functions.insert("variables".to_string(), Box::new(variables::Variables{}));
101113
Self {
102114
functions,

0 commit comments

Comments
 (0)