diff --git a/docs/reference/schemas/config/functions/trim.md b/docs/reference/schemas/config/functions/trim.md new file mode 100644 index 000000000..74f07301b --- /dev/null +++ b/docs/reference/schemas/config/functions/trim.md @@ -0,0 +1,259 @@ +--- +description: Reference for the 'trim' DSC configuration document function +ms.date: 01/10/2025 +ms.topic: reference +title: trim +--- + +# trim + +## Synopsis + +Removes all leading and trailing white-space characters from the specified string. + +## Syntax + +```Syntax +trim() +``` + +## Description + +The `trim()` function removes all leading and trailing white-space characters from +the input string. White-space characters include spaces, tabs, newlines, carriage +returns, and other Unicode whitespace characters. The function preserves internal +whitespace within the string. Use it for cleaning user input, normalizing +configuration values, or preparing strings for comparison. + +## Examples + +### Example 1 - Clean user input + +The following example removes leading and trailing spaces from a parameter value. + +```yaml +# trim.example.1.dsc.config.yaml +$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json +parameters: + userName: + type: string + defaultValue: ' admin ' +resources: +- name: Clean user input + type: Microsoft.DSC.Debug/Echo + properties: + output: + rawInput: "[parameters('userName')]" + cleanedInput: "[trim(parameters('userName'))]" +``` + +```bash +dsc config get --file trim.example.1.dsc.config.yaml +``` + +```yaml +results: +- name: Clean user input + type: Microsoft.DSC.Debug/Echo + result: + actualState: + output: + rawInput: ' admin ' + cleanedInput: admin +messages: [] +hadErrors: false +``` + +### Example 2 - Normalize file paths + +The following example demonstrates using `trim()` with [`concat()`][01] to clean +path components before building a complete file path. + +```yaml +# trim.example.2.dsc.config.yaml +$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json +parameters: + baseDir: + type: string + defaultValue: ' /var/log ' + fileName: + type: string + defaultValue: ' app.log ' +resources: +- name: Build clean file path + type: Microsoft.DSC.Debug/Echo + properties: + output: + filePath: "[concat(trim(parameters('baseDir')), '/', trim(parameters('fileName')))]" +``` + +```bash +dsc config get --file trim.example.2.dsc.config.yaml +``` + +```yaml +results: +- name: Build clean file path + type: Microsoft.DSC.Debug/Echo + result: + actualState: + output: + filePath: /var/log/app.log +messages: [] +hadErrors: false +``` + +### Example 3 - Clean configuration values for comparison + +The following example uses `trim()` to normalize strings before comparing them with +the [`equals()`][02] function. + +```yaml +# trim.example.3.dsc.config.yaml +$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json +parameters: + expectedEnv: + type: string + defaultValue: production + actualEnv: + type: string + defaultValue: ' production ' +resources: +- name: Environment comparison + type: Microsoft.DSC.Debug/Echo + properties: + output: + matches: "[equals(trim(parameters('actualEnv')), parameters('expectedEnv'))]" +``` + +```bash +dsc config get --file trim.example.3.dsc.config.yaml +``` + +```yaml +results: +- name: Environment comparison + type: Microsoft.DSC.Debug/Echo + result: + actualState: + output: + matches: true +messages: [] +hadErrors: false +``` + +### Example 4 - Process multi-line configuration + +The following example shows how `trim()` handles tabs, newlines, and various +whitespace characters. + +```yaml +# trim.example.4.dsc.config.yaml +$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json +resources: +- name: Whitespace handling + type: Microsoft.DSC.Debug/Echo + properties: + output: + spaces: "[trim(' content ')]" + mixed: "[trim(' \t\n content \n\t ')]" + internal: "[trim(' multiple spaces inside ')]" +``` + +```bash +dsc config get --file trim.example.4.dsc.config.yaml +``` + +```yaml +results: +- name: Whitespace handling + type: Microsoft.DSC.Debug/Echo + result: + actualState: + output: + spaces: content + mixed: content + internal: multiple spaces inside +messages: [] +hadErrors: false +``` + +### Example 5 - Combine with case conversion + +The following example demonstrates using `trim()` with [`toLower()`][00] to both +clean and normalize a string value. + +```yaml +# trim.example.5.dsc.config.yaml +$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json +parameters: + serviceName: + type: string + defaultValue: ' WEB-SERVER ' +resources: +- name: Clean and normalize service name + type: Microsoft.DSC.Debug/Echo + properties: + output: + original: "[parameters('serviceName')]" + normalized: "[toLower(trim(parameters('serviceName')))]" +``` + +```bash +dsc config get --file trim.example.5.dsc.config.yaml +``` + +```yaml +results: +- name: Clean and normalize service name + type: Microsoft.DSC.Debug/Echo + result: + actualState: + output: + original: ' WEB-SERVER ' + normalized: web-server +messages: [] +hadErrors: false +``` + +## Parameters + +### stringToTrim + +The string value to remove leading and trailing whitespace from. + +```yaml +Type: string +Required: true +Position: 1 +``` + +## Output + +The `trim()` function returns the input string with all leading and trailing +white-space characters removed. Internal whitespace is preserved. + +```yaml +Type: string +``` + +## Related functions + +- [`toLower()`][00] - Converts a string to lower case +- [`concat()`][01] - Concatenates strings together +- [`equals()`][02] - Compares two values for equality +- [`startsWith()`][03] - Checks if a string starts with a value +- [`endsWith()`][04] - Checks if a string ends with a value +- [`substring()`][05] - Extracts a portion of a string +- [`replace()`][06] - Replaces text in a string +- [`parameters()`][07] - Retrieves parameter values + + +[00]: ./toLower.md +[01]: ./concat.md +[02]: ./equals.md +[03]: ./startsWith.md +[04]: ./endsWith.md +[05]: ./substring.md +[06]: ./replace.md +[07]: ./parameters.md diff --git a/dsc/tests/dsc_functions.tests.ps1 b/dsc/tests/dsc_functions.tests.ps1 index ee5208cc0..876e8a44a 100644 --- a/dsc/tests/dsc_functions.tests.ps1 +++ b/dsc/tests/dsc_functions.tests.ps1 @@ -831,4 +831,31 @@ Describe 'tests for function expressions' { $out = $config_yaml | dsc config get -f - | ConvertFrom-Json $out.results[0].result.actualState.output | Should -Be $expected } + + It 'trim function works for: ' -TestCases @( + @{ expression = "[trim(' hello')]"; expected = 'hello' } + @{ expression = "[trim('hello ')]"; expected = 'hello' } + @{ expression = "[trim(' hello world ')]"; expected = 'hello world' } + @{ expression = "[trim('hello')]"; expected = 'hello' } + @{ expression = "[trim('')]"; expected = '' } + @{ expression = "[trim(' ')]"; expected = '' } + @{ expression = "[trim(' hello world ')]"; expected = 'hello world' } + @{ expression = "[trim(' café ')]"; expected = 'café' } + @{ expression = "[trim(' a ')]"; expected = 'a' } + @{ expression = "[trim(concat(' hello', ' '))]"; expected = 'hello' } + ) { + param($expression, $expected) + + $escapedExpression = $expression -replace "'", "''" + $config_yaml = @" + `$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json + resources: + - name: Echo + type: Microsoft.DSC.Debug/Echo + properties: + output: '$escapedExpression' +"@ + $out = $config_yaml | dsc config get -f - | ConvertFrom-Json + $out.results[0].result.actualState.output | Should -Be $expected + } } diff --git a/lib/dsc-lib/locales/en-us.toml b/lib/dsc-lib/locales/en-us.toml index 6b06ffcd8..2a42f3abf 100644 --- a/lib/dsc-lib/locales/en-us.toml +++ b/lib/dsc-lib/locales/en-us.toml @@ -512,6 +512,9 @@ description = "Converts the specified string to lower case" [functions.toUpper] description = "Converts the specified string to upper case" +[functions.trim] +description = "Removes all leading and trailing white-space characters from the specified string" + [functions.true] description = "Returns the boolean value true" invoked = "true function" diff --git a/lib/dsc-lib/src/functions/mod.rs b/lib/dsc-lib/src/functions/mod.rs index ce3441072..87f108b2a 100644 --- a/lib/dsc-lib/src/functions/mod.rs +++ b/lib/dsc-lib/src/functions/mod.rs @@ -65,6 +65,7 @@ pub mod substring; pub mod system_root; pub mod to_lower; pub mod to_upper; +pub mod trim; pub mod r#true; pub mod union; pub mod unique_string; @@ -186,6 +187,7 @@ impl FunctionDispatcher { Box::new(system_root::SystemRoot{}), Box::new(to_lower::ToLower{}), Box::new(to_upper::ToUpper{}), + Box::new(trim::Trim{}), Box::new(r#true::True{}), Box::new(utc_now::UtcNow{}), Box::new(union::Union{}), diff --git a/lib/dsc-lib/src/functions/trim.rs b/lib/dsc-lib/src/functions/trim.rs new file mode 100644 index 000000000..c7c93e60f --- /dev/null +++ b/lib/dsc-lib/src/functions/trim.rs @@ -0,0 +1,123 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use crate::DscError; +use crate::configure::context::Context; +use crate::functions::{FunctionArgKind, FunctionCategory, FunctionMetadata}; +use rust_i18n::t; +use serde_json::Value; +use super::Function; + +#[derive(Debug, Default)] +pub struct Trim {} + +impl Function for Trim { + fn get_metadata(&self) -> FunctionMetadata { + FunctionMetadata { + name: "trim".to_string(), + description: t!("functions.trim.description").to_string(), + category: vec![FunctionCategory::String], + min_args: 1, + max_args: 1, + accepted_arg_ordered_types: vec![vec![FunctionArgKind::String]], + remaining_arg_accepted_types: None, + return_types: vec![FunctionArgKind::String], + } + } + + fn invoke(&self, args: &[Value], _context: &Context) -> Result { + let string_to_trim = args[0].as_str().unwrap(); + let result = string_to_trim.trim(); + Ok(Value::String(result.to_string())) + } +} + +#[cfg(test)] +mod tests { + use crate::configure::context::Context; + use crate::parser::Statement; + + #[test] + fn test_trim_leading_spaces() { + let mut parser = Statement::new().unwrap(); + let result = parser.parse_and_execute("[trim(' hello')]", &Context::new()).unwrap(); + assert_eq!(result, "hello"); + } + + #[test] + fn test_trim_trailing_spaces() { + let mut parser = Statement::new().unwrap(); + let result = parser.parse_and_execute("[trim('hello ')]", &Context::new()).unwrap(); + assert_eq!(result, "hello"); + } + + #[test] + fn test_trim_both_sides() { + let mut parser = Statement::new().unwrap(); + let result = parser.parse_and_execute("[trim(' hello world ')]", &Context::new()).unwrap(); + assert_eq!(result, "hello world"); + } + + #[test] + fn test_trim_no_whitespace() { + let mut parser = Statement::new().unwrap(); + let result = parser.parse_and_execute("[trim('hello')]", &Context::new()).unwrap(); + assert_eq!(result, "hello"); + } + + #[test] + fn test_trim_empty_string() { + let mut parser = Statement::new().unwrap(); + let result = parser.parse_and_execute("[trim('')]", &Context::new()).unwrap(); + assert_eq!(result, ""); + } + + #[test] + fn test_trim_only_whitespace() { + let mut parser = Statement::new().unwrap(); + let result = parser.parse_and_execute("[trim(' ')]", &Context::new()).unwrap(); + assert_eq!(result, ""); + } + + #[test] + fn test_trim_tabs_and_newlines() { + let mut parser = Statement::new().unwrap(); + let result = parser.parse_and_execute("[trim('\t\nhello\n\t')]", &Context::new()).unwrap(); + assert_eq!(result, "hello"); + } + + #[test] + fn test_trim_internal_spaces_preserved() { + let mut parser = Statement::new().unwrap(); + let result = parser.parse_and_execute("[trim(' hello world ')]", &Context::new()).unwrap(); + assert_eq!(result, "hello world"); + } + + #[test] + fn test_trim_mixed_whitespace() { + let mut parser = Statement::new().unwrap(); + let result = parser.parse_and_execute("[trim(' \t\n hello \n\t ')]", &Context::new()).unwrap(); + assert_eq!(result, "hello"); + } + + #[test] + fn test_trim_unicode_text() { + let mut parser = Statement::new().unwrap(); + let result = parser.parse_and_execute("[trim(' café ')]", &Context::new()).unwrap(); + assert_eq!(result, "café"); + } + + #[test] + fn test_trim_with_nested_function() { + let mut parser = Statement::new().unwrap(); + let result = parser.parse_and_execute("[trim(concat(' hello', ' '))]", &Context::new()).unwrap(); + assert_eq!(result, "hello"); + } + + #[test] + fn test_trim_single_character() { + let mut parser = Statement::new().unwrap(); + let result = parser.parse_and_execute("[trim(' a ')]", &Context::new()).unwrap(); + assert_eq!(result, "a"); + } +}