Skip to content

Commit 74a652f

Browse files
authored
Merge pull request #1085 from Gijsreyn/add-skip-function
Add skip() function
2 parents 2075fb9 + 1dd3e45 commit 74a652f

File tree

5 files changed

+284
-0
lines changed

5 files changed

+284
-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; negative values are treated as zero. If the
30+
number is greater than the length of the array or string, the function returns
31+
an 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 an integer. Negative values
116+
are treated as zero.
117+
118+
```yaml
119+
Type: int
120+
Required: true
121+
Position: 2
122+
```
123+
124+
## Output
125+
126+
Returns the same type as `originalValue`:
127+
128+
- If `originalValue` is an array, returns an array
129+
- If `originalValue` is a string, returns a string
130+
131+
```yaml
132+
Type: array | string
133+
```
134+
135+
## Error conditions
136+
137+
- `originalValue` is not an array or string
138+
- `numberToSkip` is not an integer
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: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,32 @@ Describe 'tests for function expressions' {
409409
($out.results[0].result.actualState.output | Out-String) | Should -BeExactly ($expected | Out-String)
410410
}
411411

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','b'), 0)]"; expected = @('a','b') }
416+
@{ expression = "[skip('abc', 0)]"; expected = 'abc' }
417+
@{ expression = "[skip(createArray('a','b'), 5)]"; expected = @() }
418+
@{ expression = "[skip('', 1)]"; expected = '' }
419+
# Negative counts are treated as zero
420+
@{ expression = "[skip(createArray('x','y'), -3)]"; expected = @('x','y') }
421+
@{ expression = "[skip('xy', -1)]"; expected = 'xy' }
422+
) {
423+
param($expression, $expected)
424+
425+
$config_yaml = @"
426+
`$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
427+
resources:
428+
- name: Echo
429+
type: Microsoft.DSC.Debug/Echo
430+
properties:
431+
output: "$expression"
432+
"@
433+
$out = dsc -l trace config get -i $config_yaml 2>$TestDrive/error.log | ConvertFrom-Json
434+
$LASTEXITCODE | Should -Be 0 -Because (Get-Content $TestDrive/error.log -Raw)
435+
($out.results[0].result.actualState.output | Out-String) | Should -BeExactly ($expected | Out-String)
436+
}
437+
412438
It 'lastIndexOf function works for: <expression>' -TestCases @(
413439
@{ expression = "[lastIndexOf(createArray('a', 'b', 'a', 'c'), 'a')]"; expected = 2 }
414440
@{ expression = "[lastIndexOf(createArray(10, 20, 30, 20), 20)]"; expected = 3 }

dsc_lib/locales/en-us.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,12 @@ secretNotFound = "Secret '%{name}' not found"
410410
description = "Checks if a string starts with a specific prefix"
411411
invoked = "startsWith function"
412412

413+
[functions.skip]
414+
description = "Returns an array with all elements after the specified number, or a string with all characters after the specified number"
415+
invoked = "skip function"
416+
invalidNumberToSkip = "Second argument must be an integer"
417+
invalidOriginalValue = "First argument must be an array or string"
418+
413419
[functions.string]
414420
description = "Converts a value to a string"
415421

dsc_lib/src/functions/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ pub mod path;
5050
pub mod reference;
5151
pub mod resource_id;
5252
pub mod secret;
53+
pub mod skip;
5354
pub mod starts_with;
5455
pub mod string;
5556
pub mod sub;
@@ -160,6 +161,7 @@ impl FunctionDispatcher {
160161
Box::new(reference::Reference{}),
161162
Box::new(resource_id::ResourceId{}),
162163
Box::new(secret::Secret{}),
164+
Box::new(skip::Skip{}),
163165
Box::new(starts_with::StartsWith{}),
164166
Box::new(string::StringFn{}),
165167
Box::new(sub::Sub{}),

dsc_lib/src/functions/skip.rs

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
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+
if let Some(count_i64) = args[1].as_i64() {
35+
let count: usize = if count_i64 < 0 {
36+
0
37+
} else {
38+
count_i64.try_into().unwrap_or(usize::MAX)
39+
};
40+
41+
if let Some(array) = args[0].as_array() {
42+
if count >= array.len() { return Ok(Value::Array(vec![])); }
43+
let skipped = array.iter().skip(count).cloned().collect::<Vec<Value>>();
44+
return Ok(Value::Array(skipped));
45+
}
46+
47+
if let Some(s) = args[0].as_str() {
48+
let result: String = s.chars().skip(count).collect();
49+
return Ok(Value::String(result));
50+
}
51+
52+
return Err(DscError::Parser(t!("functions.skip.invalidOriginalValue").to_string()));
53+
}
54+
55+
Err(DscError::Parser(t!("functions.skip.invalidNumberToSkip").to_string()))
56+
}
57+
}
58+
59+
#[cfg(test)]
60+
mod tests {
61+
use crate::configure::context::Context;
62+
use crate::parser::Statement;
63+
use serde_json::Value;
64+
65+
#[test]
66+
fn skip_array_basic() {
67+
let mut parser = Statement::new().unwrap();
68+
let result = parser.parse_and_execute("[skip(createArray('a','b','c','d'), 2)]", &Context::new()).unwrap();
69+
assert_eq!(result, Value::Array(vec![Value::String("c".into()), Value::String("d".into())]));
70+
}
71+
72+
#[test]
73+
fn skip_string_basic() {
74+
let mut parser = Statement::new().unwrap();
75+
let result = parser.parse_and_execute("[skip('hello', 2)]", &Context::new()).unwrap();
76+
assert_eq!(result, Value::String("llo".into()));
77+
}
78+
79+
#[test]
80+
fn skip_more_than_length() {
81+
let mut parser = Statement::new().unwrap();
82+
let result = parser.parse_and_execute("[skip(createArray('a','b'), 5)]", &Context::new()).unwrap();
83+
assert_eq!(result, Value::Array(vec![]));
84+
}
85+
86+
#[test]
87+
fn skip_array_negative_is_zero() {
88+
let mut parser = Statement::new().unwrap();
89+
let result = parser.parse_and_execute("[skip(createArray('a','b','c'), -1)]", &Context::new()).unwrap();
90+
assert_eq!(result, Value::Array(vec![
91+
Value::String("a".into()),
92+
Value::String("b".into()),
93+
Value::String("c".into()),
94+
]));
95+
}
96+
97+
#[test]
98+
fn skip_string_negative_is_zero() {
99+
let mut parser = Statement::new().unwrap();
100+
let result = parser.parse_and_execute("[skip('ab', -2)]", &Context::new()).unwrap();
101+
assert_eq!(result, Value::String("ab".into()));
102+
}
103+
}

0 commit comments

Comments
 (0)