Skip to content

Commit b1c6e97

Browse files
committed
Add skip function
1 parent 728b529 commit b1c6e97

File tree

5 files changed

+263
-0
lines changed

5 files changed

+263
-0
lines changed
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
---
2+
description: Reference for the 'skip' DSC configuration document function
3+
ms.date: 08/29/2025
4+
ms.topic: reference
5+
title: skip
6+
---
7+
8+
## Synopsis
9+
10+
Returns an array with all the elements after the specified number in the array,
11+
or returns a string with all the characters after the specified number in the
12+
string.
13+
14+
## Syntax
15+
16+
```Syntax
17+
skip(<originalValue>, <numberToSkip>)
18+
```
19+
20+
## Description
21+
22+
The `skip()` function returns the tail of an array or string by skipping a
23+
specified number of items from the start.
24+
25+
- For arrays: returns a new array containing elements after the specified index
26+
- For strings: returns a new string containing characters after the specified index
27+
28+
Both parameters are required. `originalValue` must be an array or a string.
29+
`numberToSkip` must be an integer greater than or equal to zero. If the number
30+
is greater than the length of the array or string, the function returns an
31+
empty array or an empty string respectively.
32+
33+
## Examples
34+
35+
### Example 1 - Skip elements in an array
36+
37+
The following example returns the tail of an array by skipping the first two
38+
elements.
39+
40+
```yaml
41+
# skip.example.1.dsc.config.yaml
42+
$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
43+
resources:
44+
- name: Tail of array
45+
type: Microsoft.DSC.Debug/Echo
46+
properties:
47+
output:
48+
tail: "[skip(createArray('a','b','c','d'), 2)]"
49+
```
50+
51+
```bash
52+
dsc config get --file skip.example.1.dsc.config.yaml
53+
```
54+
55+
```yaml
56+
results:
57+
- name: Tail of array
58+
type: Microsoft.DSC.Debug/Echo
59+
result:
60+
actualState:
61+
output:
62+
tail:
63+
- c
64+
- d
65+
messages: []
66+
hadErrors: false
67+
```
68+
69+
### Example 2 - Skip characters in a string
70+
71+
The following example returns the substring of a string by skipping the first
72+
two characters.
73+
74+
```yaml
75+
# skip.example.2.dsc.config.yaml
76+
$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
77+
resources:
78+
- name: Tail of string
79+
type: Microsoft.DSC.Debug/Echo
80+
properties:
81+
output:
82+
tail: "[skip('hello', 2)]"
83+
```
84+
85+
```bash
86+
dsc config get --file skip.example.2.dsc.config.yaml
87+
```
88+
89+
```yaml
90+
results:
91+
- name: Tail of string
92+
type: Microsoft.DSC.Debug/Echo
93+
result:
94+
actualState:
95+
output:
96+
tail: llo
97+
messages: []
98+
hadErrors: false
99+
```
100+
101+
## Parameters
102+
103+
### originalValue
104+
105+
The value to skip items from. Can be an array or a string.
106+
107+
```yaml
108+
Type: array | string
109+
Required: true
110+
Position: 1
111+
```
112+
113+
### numberToSkip
114+
115+
The number of items to skip from the start. Must be a non-negative integer.
116+
117+
```yaml
118+
Type: int
119+
Required: true
120+
Position: 2
121+
```
122+
123+
## Output
124+
125+
Returns the same type as `originalValue`:
126+
127+
- If `originalValue` is an array, returns an array
128+
- If `originalValue` is a string, returns a string
129+
130+
```yaml
131+
Type: array | string
132+
```
133+
134+
## Error conditions
135+
136+
- `originalValue` is not an array or string
137+
- `numberToSkip` is not an integer
138+
- `numberToSkip` is negative
139+
140+
## Related functions
141+
142+
- [`first()`][00] - Returns the first element or character
143+
- [`length()`][01] - Returns the number of elements or characters
144+
145+
<!-- Link reference definitions -->
146+
[00]: ./first.md
147+
[01]: ./length.md

dsc/tests/dsc_functions.tests.ps1

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,4 +408,27 @@ Describe 'tests for function expressions' {
408408
$LASTEXITCODE | Should -Be 0 -Because (Get-Content $TestDrive/error.log -Raw)
409409
($out.results[0].result.actualState.output | Out-String) | Should -BeExactly ($expected | Out-String)
410410
}
411+
412+
It 'skip function works for: <expression>' -TestCases @(
413+
@{ expression = "[skip(createArray('a','b','c','d'), 2)]"; expected = @('c','d') }
414+
@{ expression = "[skip('hello', 2)]"; expected = 'llo' }
415+
@{ expression = "[skip(createArray('a'), 0)]"; expected = @('a') }
416+
@{ expression = "[skip('a', 0)]"; expected = 'a' }
417+
@{ expression = "[skip(createArray('a','b'), 5)]"; expected = @() }
418+
@{ expression = "[skip('', 1)]"; expected = '' }
419+
) {
420+
param($expression, $expected)
421+
422+
$config_yaml = @"
423+
`$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
424+
resources:
425+
- name: Echo
426+
type: Microsoft.DSC.Debug/Echo
427+
properties:
428+
output: "$expression"
429+
"@
430+
$out = dsc -l trace config get -i $config_yaml 2>$TestDrive/error.log | ConvertFrom-Json
431+
$LASTEXITCODE | Should -Be 0 -Because (Get-Content $TestDrive/error.log -Raw)
432+
($out.results[0].result.actualState.output | Out-String) | Should -BeExactly ($expected | Out-String)
433+
}
411434
}

dsc_lib/locales/en-us.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -405,6 +405,13 @@ secretNotFound = "Secret '%{name}' not found"
405405
description = "Checks if a string starts with a specific prefix"
406406
invoked = "startsWith function"
407407

408+
[functions.skip]
409+
description = "Returns an array with all elements after the specified number, or a string with all characters after the specified number"
410+
invoked = "skip function"
411+
invalidNumberToSkip = "Second argument must be an integer"
412+
negativeNotAllowed = "Second argument cannot be negative"
413+
invalidOriginalValue = "First argument must be an array or string"
414+
408415
[functions.string]
409416
description = "Converts a value to a string"
410417

dsc_lib/src/functions/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ pub mod path;
4949
pub mod reference;
5050
pub mod resource_id;
5151
pub mod secret;
52+
pub mod skip;
5253
pub mod starts_with;
5354
pub mod string;
5455
pub mod sub;
@@ -158,6 +159,7 @@ impl FunctionDispatcher {
158159
Box::new(reference::Reference{}),
159160
Box::new(resource_id::ResourceId{}),
160161
Box::new(secret::Secret{}),
162+
Box::new(skip::Skip{}),
161163
Box::new(starts_with::StartsWith{}),
162164
Box::new(string::StringFn{}),
163165
Box::new(sub::Sub{}),

dsc_lib/src/functions/skip.rs

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
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::{FunctionArgKind, Function, FunctionCategory, FunctionMetadata};
7+
use rust_i18n::t;
8+
use serde_json::Value;
9+
use tracing::debug;
10+
11+
#[derive(Debug, Default)]
12+
pub struct Skip {}
13+
14+
impl Function for Skip {
15+
fn get_metadata(&self) -> FunctionMetadata {
16+
FunctionMetadata {
17+
name: "skip".to_string(),
18+
description: t!("functions.skip.description").to_string(),
19+
category: FunctionCategory::Array,
20+
min_args: 2,
21+
max_args: 2,
22+
accepted_arg_ordered_types: vec![
23+
vec![FunctionArgKind::Array, FunctionArgKind::String],
24+
vec![FunctionArgKind::Number],
25+
],
26+
remaining_arg_accepted_types: None,
27+
return_types: vec![FunctionArgKind::Array, FunctionArgKind::String],
28+
}
29+
}
30+
31+
fn invoke(&self, args: &[Value], _context: &Context) -> Result<Value, DscError> {
32+
debug!("{}", t!("functions.skip.invoked"));
33+
34+
let Some(count_i64) = args[1].as_i64() else {
35+
return Err(DscError::Parser(t!("functions.skip.invalidNumberToSkip").to_string()));
36+
};
37+
if count_i64 < 0 {
38+
return Err(DscError::Parser(t!("functions.skip.negativeNotAllowed").to_string()));
39+
}
40+
let count: usize = count_i64.try_into().unwrap_or(usize::MAX);
41+
42+
if let Some(array) = args[0].as_array() {
43+
if count >= array.len() { return Ok(Value::Array(vec![])); }
44+
let skipped = array.iter().skip(count).cloned().collect::<Vec<Value>>();
45+
return Ok(Value::Array(skipped));
46+
}
47+
48+
if let Some(s) = args[0].as_str() {
49+
50+
let result: String = s.chars().skip(count).collect();
51+
return Ok(Value::String(result));
52+
}
53+
54+
Err(DscError::Parser(t!("functions.skip.invalidOriginalValue").to_string()))
55+
}
56+
}
57+
58+
#[cfg(test)]
59+
mod tests {
60+
use crate::configure::context::Context;
61+
use crate::parser::Statement;
62+
use serde_json::Value;
63+
64+
#[test]
65+
fn skip_array_basic() {
66+
let mut parser = Statement::new().unwrap();
67+
let result = parser.parse_and_execute("[skip(createArray('a','b','c','d'), 2)]", &Context::new()).unwrap();
68+
assert_eq!(result, Value::Array(vec![Value::String("c".into()), Value::String("d".into())]));
69+
}
70+
71+
#[test]
72+
fn skip_string_basic() {
73+
let mut parser = Statement::new().unwrap();
74+
let result = parser.parse_and_execute("[skip('hello', 2)]", &Context::new()).unwrap();
75+
assert_eq!(result, Value::String("llo".into()));
76+
}
77+
78+
#[test]
79+
fn skip_more_than_length() {
80+
let mut parser = Statement::new().unwrap();
81+
let result = parser.parse_and_execute("[skip(createArray('a','b'), 5)]", &Context::new()).unwrap();
82+
assert_eq!(result, Value::Array(vec![]));
83+
}
84+
}

0 commit comments

Comments
 (0)