-
Couldn't load subscription status.
- Fork 54
Add last() function
#1211
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
SteveL-MSFT
merged 8 commits into
PowerShell:main
from
Gijsreyn:gh-57/main/add-last-function
Oct 28, 2025
+378
−0
Merged
Add last() function
#1211
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
c89f5dd
Add `last()` function
Gijsreyn b00a4dd
Fix test
Gijsreyn e40ea22
Not allowed integers
Gijsreyn 0968d54
Add `last()` function
Gijsreyn 8d14483
Fix test
Gijsreyn 03841cd
Not allowed integers
Gijsreyn f1442a0
Merge branch 'gh-57/main/add-last-function' of https://github.com/Gij…
Gijsreyn 25c8e36
Add null
Gijsreyn File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,231 @@ | ||
| --- | ||
| description: Reference for the 'last' DSC configuration document function | ||
| ms.date: 01/25/2025 | ||
| ms.topic: reference | ||
| title: last | ||
| --- | ||
|
|
||
| ## Synopsis | ||
|
|
||
| Returns the last element of an array, or the last character of a string. | ||
|
|
||
| ## Syntax | ||
|
|
||
| ```Syntax | ||
| last(arg) | ||
| ``` | ||
|
|
||
| ## Description | ||
|
|
||
| The `last()` function returns the final element from an array or the final | ||
| character from a string. This is useful when you need to access the most recent | ||
| item in a sequence, the final stage in a deployment pipeline, or the last | ||
| character in a configuration value. | ||
|
|
||
| For arrays, it returns the element at index `length - 1`. For strings, it | ||
| returns the last character as a string. | ||
|
|
||
| ## Examples | ||
|
|
||
| ### Example 1 - Extract the final deployment stage (array of strings) | ||
|
|
||
| Use `last()` to retrieve the final stage in a multi-stage deployment pipeline. | ||
| This helps you identify which environment or phase should receive special | ||
| handling, such as extended health checks or manual approval gates. This example | ||
| uses [`createArray()`][01] to build the deployment stages. | ||
|
|
||
| ```yaml | ||
| # last.example.1.dsc.config.yaml | ||
| $schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json | ||
| resources: | ||
| - name: Deployment Pipeline | ||
| type: Microsoft.DSC.Debug/Echo | ||
| properties: | ||
| output: | ||
| finalStage: "[last(createArray('dev', 'test', 'staging', 'production'))]" | ||
| requiresApproval: true | ||
| ``` | ||
|
|
||
| ```bash | ||
| dsc config get --file last.example.1.dsc.config.yaml | ||
| ``` | ||
|
|
||
| ```yaml | ||
| results: | ||
| - name: Deployment Pipeline | ||
| type: Microsoft.DSC.Debug/Echo | ||
| result: | ||
| actualState: | ||
| output: | ||
| finalStage: production | ||
| requiresApproval: true | ||
| messages: [] | ||
| hadErrors: false | ||
| ``` | ||
|
|
||
| This identifies `production` as the final stage, allowing you to apply | ||
| production-specific policies or validations. | ||
|
|
||
| ### Example 2 - Get the last character of a configuration string | ||
|
|
||
| Use `last()` to extract the final character from a string value. This is useful | ||
| for parsing identifiers, checking suffixes, or validating format conventions | ||
| like version numbers or region codes. | ||
|
|
||
| ```yaml | ||
| # last.example.2.dsc.config.yaml | ||
| $schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json | ||
| resources: | ||
| - name: Region Identifier | ||
| type: Microsoft.DSC.Debug/Echo | ||
| properties: | ||
| output: | ||
| regionCode: us-west-2 | ||
| zoneSuffix: "[last('us-west-2')]" | ||
| description: "Zone suffix extracted from region code" | ||
| ``` | ||
|
|
||
| ```bash | ||
| dsc config get --file last.example.2.dsc.config.yaml | ||
| ``` | ||
|
|
||
| ```yaml | ||
| results: | ||
| - name: Region Identifier | ||
| type: Microsoft.DSC.Debug/Echo | ||
| result: | ||
| actualState: | ||
| output: | ||
| regionCode: us-west-2 | ||
| zoneSuffix: '2' | ||
| description: Zone suffix extracted from region code | ||
| messages: [] | ||
| hadErrors: false | ||
| ``` | ||
|
|
||
| The function returns `'2'` as a single-character string, representing the zone | ||
| suffix in the region identifier. | ||
|
|
||
| ### Example 3 - Identify the most recent backup (array of numbers) | ||
|
|
||
| Use `last()` with numerical arrays to find the most recent timestamp or version | ||
| number. This example shows how to select the latest backup from a sorted list | ||
| of timestamps. This example uses [`createArray()`][01] to build the backup | ||
| timestamps. | ||
|
|
||
| ```yaml | ||
| # last.example.3.dsc.config.yaml | ||
| $schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json | ||
| resources: | ||
| - name: Backup Selection | ||
| type: Microsoft.DSC.Debug/Echo | ||
| properties: | ||
| output: | ||
| availableBackups: "[createArray(1704067200, 1704153600, 1704240000, 1704326400)]" | ||
| latestBackup: "[last(createArray(1704067200, 1704153600, 1704240000, 1704326400))]" | ||
| description: "Most recent backup timestamp (Unix epoch)" | ||
| ``` | ||
|
|
||
| ```bash | ||
| dsc config get --file last.example.3.dsc.config.yaml | ||
| ``` | ||
|
|
||
| ```yaml | ||
| results: | ||
| - name: Backup Selection | ||
| type: Microsoft.DSC.Debug/Echo | ||
| result: | ||
| actualState: | ||
| output: | ||
| availableBackups: | ||
| - 1704067200 | ||
| - 1704153600 | ||
| - 1704240000 | ||
| - 1704326400 | ||
| latestBackup: 1704326400 | ||
| description: Most recent backup timestamp (Unix epoch) | ||
| messages: [] | ||
| hadErrors: false | ||
| ``` | ||
|
|
||
| The function returns `1704326400`, which represents the most recent backup in | ||
| the chronologically sorted array. | ||
|
|
||
| ### Example 4 - Combine with other functions for complex logic | ||
|
|
||
| Use `last()` together with [`split()`][02] to extract file extensions or path | ||
| components. This example demonstrates parsing a filename to get its extension. | ||
|
|
||
| ```yaml | ||
| # last.example.4.dsc.config.yaml | ||
| $schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json | ||
| resources: | ||
| - name: File Extension Parser | ||
| type: Microsoft.DSC.Debug/Echo | ||
| properties: | ||
| output: | ||
| filename: config.production.yaml | ||
| extension: "[last(split('config.production.yaml', '.'))]" | ||
| ``` | ||
|
|
||
| ```bash | ||
| dsc config get --file last.example.4.dsc.config.yaml | ||
| ``` | ||
|
|
||
| ```yaml | ||
| results: | ||
| - name: File Extension Parser | ||
| type: Microsoft.DSC.Debug/Echo | ||
| result: | ||
| actualState: | ||
| output: | ||
| filename: config.production.yaml | ||
| extension: yaml | ||
| messages: [] | ||
| hadErrors: false | ||
| ``` | ||
|
|
||
| By combining `split()` and `last()`, you can extract the `yaml` extension from | ||
| the full filename. | ||
|
|
||
| ## Parameters | ||
|
|
||
| ### arg | ||
|
|
||
| The array or string to get the last element or character from. Required. | ||
|
|
||
| ```yaml | ||
| Type: array | string | ||
| Required: true | ||
| Position: 1 | ||
| ``` | ||
|
|
||
| ## Output | ||
|
|
||
| Returns the last element of the array (preserving its original type) or the | ||
| last character as a string. For arrays, the return type matches the element | ||
| type. For strings, returns a single-character string. | ||
|
|
||
| If the input is an empty array, the function returns `null`. If the input is an | ||
| empty string, the function returns an empty string. | ||
|
|
||
| ```yaml | ||
| Type: any | string | null | ||
| ``` | ||
|
|
||
| ## Errors | ||
|
|
||
| The function returns an error in the following cases: | ||
|
|
||
| - **Invalid type**: The argument is not an array or string | ||
|
|
||
| ## Related functions | ||
|
|
||
| - [`first()`][00] - Returns the first element of an array or character of a string | ||
| - [`split()`][02] - Splits a string into an array | ||
| - [`createArray()`][01] - Creates an array from provided values | ||
|
|
||
| <!-- Link reference definitions --> | ||
| [00]: ./first.md | ||
| [01]: ./createArray.md | ||
| [02]: ./split.md |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,116 @@ | ||
| // Copyright (c) Microsoft Corporation. | ||
| // Licensed under the MIT License. | ||
|
|
||
| use crate::DscError; | ||
| use crate::configure::context::Context; | ||
| use crate::functions::{FunctionArgKind, Function, FunctionCategory, FunctionMetadata}; | ||
| use rust_i18n::t; | ||
| use serde_json::Value; | ||
| use tracing::debug; | ||
|
|
||
| #[derive(Debug, Default)] | ||
| pub struct Last {} | ||
|
|
||
| impl Function for Last { | ||
| fn get_metadata(&self) -> FunctionMetadata { | ||
| FunctionMetadata { | ||
| name: "last".to_string(), | ||
| description: t!("functions.last.description").to_string(), | ||
| category: vec![FunctionCategory::Array, FunctionCategory::String], | ||
| min_args: 1, | ||
| max_args: 1, | ||
| accepted_arg_ordered_types: vec![vec![FunctionArgKind::Array, FunctionArgKind::String]], | ||
| remaining_arg_accepted_types: None, | ||
| return_types: vec![FunctionArgKind::String, FunctionArgKind::Number, FunctionArgKind::Array, FunctionArgKind::Object, FunctionArgKind::Null], | ||
| } | ||
| } | ||
|
|
||
| fn invoke(&self, args: &[Value], _context: &Context) -> Result<Value, DscError> { | ||
| debug!("{}", t!("functions.last.invoked")); | ||
|
|
||
| if let Some(array) = args[0].as_array() { | ||
| if array.is_empty() { | ||
| return Ok(Value::Null); | ||
| } | ||
| return Ok(array[array.len() - 1].clone()); | ||
| } | ||
|
|
||
| if let Some(string) = args[0].as_str() { | ||
| if string.is_empty() { | ||
| return Ok(Value::String(String::new())); | ||
| } | ||
| return Ok(Value::String(string.chars().last().unwrap().to_string())); | ||
| } | ||
|
|
||
| Err(DscError::Parser(t!("functions.last.invalidArgType").to_string())) | ||
| } | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| mod tests { | ||
| use crate::configure::context::Context; | ||
| use crate::parser::Statement; | ||
|
|
||
| #[test] | ||
| fn array_of_strings() { | ||
| let mut parser = Statement::new().unwrap(); | ||
| let result = parser.parse_and_execute("[last(createArray('hello', 'world'))]", &Context::new()).unwrap(); | ||
| assert_eq!(result.as_str(), Some("world")); | ||
| } | ||
|
|
||
| #[test] | ||
| fn array_of_numbers() { | ||
| let mut parser = Statement::new().unwrap(); | ||
| let result = parser.parse_and_execute("[last(createArray(1, 2, 3))]", &Context::new()).unwrap(); | ||
| assert_eq!(result.to_string(), "3"); | ||
| } | ||
|
|
||
| #[test] | ||
| fn array_of_single_element() { | ||
| let mut parser = Statement::new().unwrap(); | ||
| let result = parser.parse_and_execute("[last(array('hello'))]", &Context::new()).unwrap(); | ||
| assert_eq!(result.as_str(), Some("hello")); | ||
| } | ||
|
|
||
| #[test] | ||
| fn string_input() { | ||
| let mut parser = Statement::new().unwrap(); | ||
| let result = parser.parse_and_execute("[last('hello')]", &Context::new()).unwrap(); | ||
| assert_eq!(result.as_str(), Some("o")); | ||
| } | ||
|
|
||
| #[test] | ||
| fn single_character_string() { | ||
| let mut parser = Statement::new().unwrap(); | ||
| let result = parser.parse_and_execute("[last('a')]", &Context::new()).unwrap(); | ||
| assert_eq!(result.as_str(), Some("a")); | ||
| } | ||
|
|
||
| #[test] | ||
| fn invalid_type_object() { | ||
| let mut parser = Statement::new().unwrap(); | ||
| let result = parser.parse_and_execute("[last(createObject('key', 'value'))]", &Context::new()); | ||
| assert!(result.is_err()); | ||
| } | ||
|
|
||
| #[test] | ||
| fn invalid_type_number() { | ||
| let mut parser = Statement::new().unwrap(); | ||
| let result = parser.parse_and_execute("[last(42)]", &Context::new()); | ||
| assert!(result.is_err()); | ||
| } | ||
|
|
||
| #[test] | ||
| fn array_of_multiple_strings() { | ||
| let mut parser = Statement::new().unwrap(); | ||
| let result = parser.parse_and_execute("[last(createArray('text', 'middle', 'last'))]", &Context::new()).unwrap(); | ||
| assert_eq!(result.as_str(), Some("last")); | ||
| } | ||
|
|
||
| #[test] | ||
| fn unicode_string() { | ||
| let mut parser = Statement::new().unwrap(); | ||
| let result = parser.parse_and_execute("[last('Hello🌍')]", &Context::new()).unwrap(); | ||
| assert_eq!(result.as_str(), Some("🌍")); | ||
| } | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.