diff --git a/docs/reference/schemas/config/functions/toLower.md b/docs/reference/schemas/config/functions/toLower.md new file mode 100644 index 000000000..2227a129c --- /dev/null +++ b/docs/reference/schemas/config/functions/toLower.md @@ -0,0 +1,219 @@ +--- +description: Reference for the 'toLower' DSC configuration document function +ms.date: 10/03/2025 +ms.topic: reference +title: toLower +--- + +# toLower + +## Synopsis + +Converts the specified string to lower case. + +## Syntax + +```Syntax +toLower() +``` + +## Description + +The `toLower()` function converts all uppercase letters in the input string to +lowercase letters. Numbers, symbols, punctuation, and whitespace are unchanged. +The function supports Unicode characters and preserves the original string +structure. Use it for normalizing string comparisons, creating consistent +naming conventions, or formatting output for case-sensitive operations. + +## Examples + +### Example 1 - Normalize resource names + +The following example converts a parameter value to lowercase for consistent +resource naming. + +```yaml +# tolower.example.1.dsc.config.yaml +$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json +parameters: + resourceName: + type: string + defaultValue: WEB-API-SERVICE +resources: +- name: Resource name normalization + type: Microsoft.DSC.Debug/Echo + properties: + output: + originalName: "[parameters('resourceName')]" + normalizedName: "[toLower(parameters('resourceName'))]" +``` + +```bash +dsc config get --file tolower.example.1.dsc.config.yaml +``` + +```yaml +results: +- name: Resource name normalization + type: Microsoft.DSC.Debug/Echo + result: + actualState: + output: + originalName: WEB-API-SERVICE + normalizedName: web-api-service +messages: [] +hadErrors: false +``` + +### Example 2 - Create consistent file paths + +The following example demonstrates using `toLower()` with the [`concat()`][01] +function to create lowercase file paths. + +```yaml +# tolower.example.2.dsc.config.yaml +$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json +parameters: + fileName: + type: string + defaultValue: CONFIG-FILE + extension: + type: string + defaultValue: JSON +resources: +- name: File path creation + type: Microsoft.DSC.Debug/Echo + properties: + output: + filePath: "[concat('/etc/', toLower(parameters('fileName')), '.', toLower(parameters('extension')))]" +``` + +```bash +dsc config get --file tolower.example.2.dsc.config.yaml +``` + +```yaml +results: +- name: File path creation + type: Microsoft.DSC.Debug/Echo + result: + actualState: + output: + filePath: /etc/config-file.json +messages: [] +hadErrors: false +``` + +### Example 3 - Case-insensitive comparison preparation + +The following example uses `toLower()` to normalize strings for comparison +using the `equals()` function. + +```yaml +# tolower.example.3.dsc.config.yaml +$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json +parameters: + userInput: + type: string + defaultValue: Production + expectedValue: + type: string + defaultValue: PRODUCTION +resources: +- name: Case-insensitive comparison + type: Microsoft.DSC.Debug/Echo + properties: + output: + matches: "[equals(toLower(parameters('userInput')), toLower(parameters('expectedValue')))]" +``` + +```bash +dsc config get --file tolower.example.3.dsc.config.yaml +``` + +```yaml +results: +- name: Case-insensitive comparison + type: Microsoft.DSC.Debug/Echo + result: + actualState: + output: + matches: true +messages: [] +hadErrors: false +``` + +### Example 4 - Unicode and special character handling + +The following example shows how `toLower()` handles Unicode characters and +preserves special characters. + +```yaml +# tolower.example.4.dsc.config.yaml +$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json +resources: +- name: Unicode and special character conversion + type: Microsoft.DSC.Debug/Echo + properties: + output: + ascii: "[toLower('HELLO WORLD!')]" + unicode: "[toLower('CAFÉ RÉSUMÉ')]" + mixed: "[toLower('SERVER-01 (PRIMARY)')]" +``` + +```bash +dsc config get --file tolower.example.4.dsc.config.yaml +``` + +```yaml +results: +- name: Unicode and special character conversion + type: Microsoft.DSC.Debug/Echo + result: + actualState: + output: + ascii: hello world! + unicode: café résumé + mixed: server-01 (primary) +messages: [] +hadErrors: false +``` + +## Parameters + +### stringToChange + +The string value to convert to lower case. + +```yaml +Type: string +Required: true +Position: 1 +``` + +## Output + +The `toLower()` function returns the input string with all uppercase letters +converted to lowercase. Numbers, symbols, punctuation, and whitespace remain +unchanged. + +```yaml +Type: string +``` + +## Related functions + +- [`toUpper()`][00] - Converts a string to upper case +- [`concat()`][01] - Concatenates strings together +- [`equals()`][02] - Compares two values for equality +- [`if()`][03] - Returns values based on a condition +- [`string()`][04] - Converts values to strings +- [`parameters()`][05] - Retrieves parameter values + + +[00]: ./toUpper.md +[01]: ./concat.md +[02]: ./equals.md +[03]: ./if.md +[04]: ./string.md +[05]: ./parameters.md diff --git a/docs/reference/schemas/config/functions/toUpper.md b/docs/reference/schemas/config/functions/toUpper.md new file mode 100644 index 000000000..48f367171 --- /dev/null +++ b/docs/reference/schemas/config/functions/toUpper.md @@ -0,0 +1,217 @@ +--- +description: Reference for the 'toUpper' DSC configuration document function +ms.date: 10/03/2025 +ms.topic: reference +title: toUpper +--- + +# toUpper + +## Synopsis + +Converts the specified string to upper case. + +## Syntax + +```Syntax +toUpper() +``` + +## Description + +The `toUpper()` function converts all lowercase letters in the input string to +uppercase letters. Numbers, symbols, punctuation, and whitespace are unchanged. +The function supports Unicode characters and preserves the original string +structure. Use it for normalizing string comparisons, formatting output, or +ensuring consistent casing in configuration values. + +## Examples + +### Example 1 - Convert user input to uppercase + +The following example converts a parameter value to uppercase for consistent +processing. + +```yaml +# toupper.example.1.dsc.config.yaml +$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json +parameters: + serviceName: + type: string + defaultValue: web-api-service +resources: +- name: Service name conversion + type: Microsoft.DSC.Debug/Echo + properties: + output: + originalName: "[parameters('serviceName')]" + upperName: "[toUpper(parameters('serviceName'))]" +``` + +```bash +dsc config get --file toupper.example.1.dsc.config.yaml +``` + +```yaml +results: +- name: Service name conversion + type: Microsoft.DSC.Debug/Echo + result: + actualState: + output: + originalName: web-api-service + upperName: WEB-API-SERVICE +messages: [] +hadErrors: false +``` + +### Example 2 - Format configuration values + +The following example demonstrates using `toUpper()` with the [`concat()`][01] +function to create formatted configuration keys. + +```yaml +# toupper.example.2.dsc.config.yaml +$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json +parameters: + environment: + type: string + defaultValue: production + component: + type: string + defaultValue: database +resources: +- name: Configuration key formatting + type: Microsoft.DSC.Debug/Echo + properties: + output: + configKey: "[concat(toUpper(parameters('environment')), '_', toUpper(parameters('component')), '_CONFIG')]" +``` + +```bash +dsc config get --file toupper.example.2.dsc.config.yaml +``` + +```yaml +results: +- name: Configuration key formatting + type: Microsoft.DSC.Debug/Echo + result: + actualState: + output: + configKey: PRODUCTION_DATABASE_CONFIG +messages: [] +hadErrors: false +``` + +### Example 3 - Conditional uppercase conversion + +The following example uses `toUpper()` conditionally with the [`if()`][02] +function based on a parameter value. + +```yaml +# toupper.example.3.dsc.config.yaml +$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json +parameters: + text: + type: string + defaultValue: Hello World + shouldCapitalize: + type: bool + defaultValue: true +resources: +- name: Conditional uppercase + type: Microsoft.DSC.Debug/Echo + properties: + output: + result: "[if(parameters('shouldCapitalize'), toUpper(parameters('text')), parameters('text'))]" +``` + +```bash +dsc config get --file toupper.example.3.dsc.config.yaml +``` + +```yaml +results: +- name: Conditional uppercase + type: Microsoft.DSC.Debug/Echo + result: + actualState: + output: + result: HELLO WORLD +messages: [] +hadErrors: false +``` + +### Example 4 - Unicode and special character handling + +The following example shows how `toUpper()` handles Unicode characters and +preserves special characters. + +```yaml +# toupper.example.4.dsc.config.yaml +$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json +resources: +- name: Unicode and special character conversion + type: Microsoft.DSC.Debug/Echo + properties: + output: + ascii: "[toUpper('hello world!')]" + unicode: "[toUpper('café résumé')]" + mixed: "[toUpper('Server-01 (primary)')]" +``` + +```bash +dsc config get --file toupper.example.4.dsc.config.yaml +``` + +```yaml +results: +- name: Unicode and special character conversion + type: Microsoft.DSC.Debug/Echo + result: + actualState: + output: + ascii: HELLO WORLD! + unicode: CAFÉ RÉSUMÉ + mixed: SERVER-01 (PRIMARY) +messages: [] +hadErrors: false +``` + +## Parameters + +### stringToChange + +The string value to convert to upper case. + +```yaml +Type: string +Required: true +Position: 1 +``` + +## Output + +The `toUpper()` function returns the input string with all lowercase letters +converted to uppercase. Numbers, symbols, punctuation, and whitespace remain +unchanged. + +```yaml +Type: string +``` + +## Related functions + +- [`toLower()`][00] - Converts a string to lower case +- [`concat()`][01] - Concatenates strings together +- [`if()`][02] - Returns values based on a condition +- [`string()`][03] - Converts values to strings +- [`parameters()`][04] - Retrieves parameter values + + +[00]: ./toLower.md +[01]: ./concat.md +[02]: ./if.md +[03]: ./string.md +[04]: ./parameters.md diff --git a/dsc/tests/dsc_functions.tests.ps1 b/dsc/tests/dsc_functions.tests.ps1 index 21da3062e..ee5208cc0 100644 --- a/dsc/tests/dsc_functions.tests.ps1 +++ b/dsc/tests/dsc_functions.tests.ps1 @@ -779,4 +779,56 @@ Describe 'tests for function expressions' { $errorContent = Get-Content $TestDrive/error.log -Raw $errorContent | Should -Match $expectedError } + + It 'toUpper function works for: ' -TestCases @( + @{ expression = "[toUpper('hello world')]"; expected = 'HELLO WORLD' } + @{ expression = "[toUpper('Hello World')]"; expected = 'HELLO WORLD' } + @{ expression = "[toUpper('HELLO WORLD')]"; expected = 'HELLO WORLD' } + @{ expression = "[toUpper('')]"; expected = '' } + @{ expression = "[toUpper('Hello123!@#')]"; expected = 'HELLO123!@#' } + @{ expression = "[toUpper('café')]"; expected = 'CAFÉ' } + @{ expression = "[toUpper(' hello world ')]"; expected = ' HELLO WORLD ' } + @{ expression = "[toUpper('a')]"; expected = 'A' } + @{ expression = "[toUpper(concat('hello', ' world'))]"; expected = 'HELLO WORLD' } + ) { + 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 + } + + It 'toLower function works for: ' -TestCases @( + @{ expression = "[toLower('HELLO WORLD')]"; expected = 'hello world' } + @{ expression = "[toLower('Hello World')]"; expected = 'hello world' } + @{ expression = "[toLower('hello world')]"; expected = 'hello world' } + @{ expression = "[toLower('')]"; expected = '' } + @{ expression = "[toLower('HELLO123!@#')]"; expected = 'hello123!@#' } + @{ expression = "[toLower('CAFÉ')]"; expected = 'café' } + @{ expression = "[toLower(' HELLO WORLD ')]"; expected = ' hello world ' } + @{ expression = "[toLower('A')]"; expected = 'a' } + @{ expression = "[toLower(concat('HELLO', ' WORLD'))]"; expected = 'hello world' } + ) { + 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/dsc_lib/locales/en-us.toml b/dsc_lib/locales/en-us.toml index 39547ddc6..959878bf3 100644 --- a/dsc_lib/locales/en-us.toml +++ b/dsc_lib/locales/en-us.toml @@ -507,6 +507,12 @@ invoked = "sub function" description = "Returns the system root path" invoked = "systemRoot function" +[functions.toLower] +description = "Converts the specified string to lower case" + +[functions.toUpper] +description = "Converts the specified string to upper case" + [functions.true] description = "Returns the boolean value true" invoked = "true function" diff --git a/dsc_lib/src/functions/mod.rs b/dsc_lib/src/functions/mod.rs index 5b76938ca..ce3441072 100644 --- a/dsc_lib/src/functions/mod.rs +++ b/dsc_lib/src/functions/mod.rs @@ -63,6 +63,8 @@ pub mod string; pub mod sub; pub mod substring; pub mod system_root; +pub mod to_lower; +pub mod to_upper; pub mod r#true; pub mod union; pub mod unique_string; @@ -182,6 +184,8 @@ impl FunctionDispatcher { Box::new(sub::Sub{}), Box::new(substring::Substring{}), Box::new(system_root::SystemRoot{}), + Box::new(to_lower::ToLower{}), + Box::new(to_upper::ToUpper{}), Box::new(r#true::True{}), Box::new(utc_now::UtcNow{}), Box::new(union::Union{}), diff --git a/dsc_lib/src/functions/to_lower.rs b/dsc_lib/src/functions/to_lower.rs new file mode 100644 index 000000000..683761e48 --- /dev/null +++ b/dsc_lib/src/functions/to_lower.rs @@ -0,0 +1,102 @@ +// 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 ToLower {} + +impl Function for ToLower { + fn get_metadata(&self) -> FunctionMetadata { + FunctionMetadata { + name: "toLower".to_string(), + description: t!("functions.toLower.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_change = args[0].as_str().unwrap(); + let result = string_to_change.to_lowercase(); + Ok(Value::String(result)) + } +} + +#[cfg(test)] +mod tests { + use crate::configure::context::Context; + use crate::parser::Statement; + + #[test] + fn test_to_lower_uppercase() { + let mut parser = Statement::new().unwrap(); + let result = parser.parse_and_execute("[toLower('HELLO WORLD')]", &Context::new()).unwrap(); + assert_eq!(result, "hello world"); + } + + #[test] + fn test_to_lower_mixed_case() { + let mut parser = Statement::new().unwrap(); + let result = parser.parse_and_execute("[toLower('Hello World')]", &Context::new()).unwrap(); + assert_eq!(result, "hello world"); + } + + #[test] + fn test_to_lower_already_lowercase() { + let mut parser = Statement::new().unwrap(); + let result = parser.parse_and_execute("[toLower('hello world')]", &Context::new()).unwrap(); + assert_eq!(result, "hello world"); + } + + #[test] + fn test_to_lower_empty_string() { + let mut parser = Statement::new().unwrap(); + let result = parser.parse_and_execute("[toLower('')]", &Context::new()).unwrap(); + assert_eq!(result, ""); + } + + #[test] + fn test_to_lower_with_numbers_and_symbols() { + let mut parser = Statement::new().unwrap(); + let result = parser.parse_and_execute("[toLower('HELLO123!@#')]", &Context::new()).unwrap(); + assert_eq!(result, "hello123!@#"); + } + + #[test] + fn test_to_lower_unicode() { + let mut parser = Statement::new().unwrap(); + let result = parser.parse_and_execute("[toLower('CAFÉ')]", &Context::new()).unwrap(); + assert_eq!(result, "café"); + } + + #[test] + fn test_to_lower_with_spaces() { + let mut parser = Statement::new().unwrap(); + let result = parser.parse_and_execute("[toLower(' HELLO WORLD ')]", &Context::new()).unwrap(); + assert_eq!(result, " hello world "); + } + + #[test] + fn test_to_lower_single_character() { + let mut parser = Statement::new().unwrap(); + let result = parser.parse_and_execute("[toLower('A')]", &Context::new()).unwrap(); + assert_eq!(result, "a"); + } + + #[test] + fn test_to_lower_nested_function() { + let mut parser = Statement::new().unwrap(); + let result = parser.parse_and_execute("[toLower(concat('HELLO', ' WORLD'))]", &Context::new()).unwrap(); + assert_eq!(result, "hello world"); + } +} \ No newline at end of file diff --git a/dsc_lib/src/functions/to_upper.rs b/dsc_lib/src/functions/to_upper.rs new file mode 100644 index 000000000..2319708ad --- /dev/null +++ b/dsc_lib/src/functions/to_upper.rs @@ -0,0 +1,102 @@ +// 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 ToUpper {} + +impl Function for ToUpper { + fn get_metadata(&self) -> FunctionMetadata { + FunctionMetadata { + name: "toUpper".to_string(), + description: t!("functions.toUpper.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_change = args[0].as_str().unwrap(); + let result = string_to_change.to_uppercase(); + Ok(Value::String(result)) + } +} + +#[cfg(test)] +mod tests { + use crate::configure::context::Context; + use crate::parser::Statement; + + #[test] + fn test_to_upper_lowercase() { + let mut parser = Statement::new().unwrap(); + let result = parser.parse_and_execute("[toUpper('hello world')]", &Context::new()).unwrap(); + assert_eq!(result, "HELLO WORLD"); + } + + #[test] + fn test_to_upper_mixed_case() { + let mut parser = Statement::new().unwrap(); + let result = parser.parse_and_execute("[toUpper('Hello World')]", &Context::new()).unwrap(); + assert_eq!(result, "HELLO WORLD"); + } + + #[test] + fn test_to_upper_already_uppercase() { + let mut parser = Statement::new().unwrap(); + let result = parser.parse_and_execute("[toUpper('HELLO WORLD')]", &Context::new()).unwrap(); + assert_eq!(result, "HELLO WORLD"); + } + + #[test] + fn test_to_upper_empty_string() { + let mut parser = Statement::new().unwrap(); + let result = parser.parse_and_execute("[toUpper('')]", &Context::new()).unwrap(); + assert_eq!(result, ""); + } + + #[test] + fn test_to_upper_with_numbers_and_symbols() { + let mut parser = Statement::new().unwrap(); + let result = parser.parse_and_execute("[toUpper('Hello123!@#')]", &Context::new()).unwrap(); + assert_eq!(result, "HELLO123!@#"); + } + + #[test] + fn test_to_upper_unicode() { + let mut parser = Statement::new().unwrap(); + let result = parser.parse_and_execute("[toUpper('café')]", &Context::new()).unwrap(); + assert_eq!(result, "CAFÉ"); + } + + #[test] + fn test_to_upper_with_spaces() { + let mut parser = Statement::new().unwrap(); + let result = parser.parse_and_execute("[toUpper(' hello world ')]", &Context::new()).unwrap(); + assert_eq!(result, " HELLO WORLD "); + } + + #[test] + fn test_to_upper_single_character() { + let mut parser = Statement::new().unwrap(); + let result = parser.parse_and_execute("[toUpper('a')]", &Context::new()).unwrap(); + assert_eq!(result, "A"); + } + + #[test] + fn test_to_upper_nested_function() { + let mut parser = Statement::new().unwrap(); + let result = parser.parse_and_execute("[toUpper(concat('hello', ' world'))]", &Context::new()).unwrap(); + assert_eq!(result, "HELLO WORLD"); + } +} \ No newline at end of file