Skip to content
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

Added padLeft and padRight functions #1422 #1526

Merged
merged 1 commit into from
May 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions docs/CHANGELOG-v2.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ What's changed since pre-release v2.9.0-B0013:
[#1423](https://github.com/microsoft/PSRule/issues/1423)
- Quantifiers allow you to specify the number of matches with `count`, `less`, `lessOrEqual`, `greater`, or `greaterOrEqual`.
- See [Sub-selectors][4] for more information.
- Added support for new functions by @BernieWhite.
[#1422](https://github.com/microsoft/PSRule/issues/1422)
- Added support for `padLeft`, and `padRight`.

## v2.9.0-B0013 (pre-release)

Expand Down
2 changes: 2 additions & 0 deletions docs/expressions/functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ It may be necessary to perform minor transformation before evaluating a conditio
- `first` - Return the first element in an array or the first character of a string.
- `integer` - Convert a value to an integer.
- `last` - Return the last element in an array or the last character of a string.
- `padLeft` - Pad a value with a character on the left to meet the specified length.
- `padRight` - Pad a value with a character on the right to meet the specified length.
- `path` - Get a value from an object path.
- `replace` - Replace an old string with a new string.
- `split` - Split a string into an array by a delimiter.
Expand Down
90 changes: 90 additions & 0 deletions schemas/PSRule-language.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -3357,6 +3357,12 @@
},
{
"$ref": "#/definitions/fn/definitions/function/definitions/split"
},
{
"$ref": "#/definitions/fn/definitions/function/definitions/padLeft"
},
{
"$ref": "#/definitions/fn/definitions/function/definitions/padRight"
}
],
"definitions": {
Expand Down Expand Up @@ -3781,6 +3787,90 @@
"split",
"delimiter"
]
},
"padLeft": {
"type": "object",
"properties": {
"padLeft": {
"oneOf": [
{
"type": "string",
"title": "Pad Left",
"description": "The padLeft function returns a string with a minimum of the specified length.",
"markdownDescription": "The `padLeft` function returns a string with a minimum of the specified length."
},
{
"type": "object",
"title": "Pad Left",
"description": "The padLeft function returns a string with a minimum of the specified length.",
"markdownDescription": "The `padLeft` function returns a string with a minimum of the specified length.",
"$ref": "#/definitions/fn/definitions/function"
}
],
"default": {}
},
"totalLength": {
"type": "integer",
"title": "Total length",
"description": "Sets the number of characters to pad the string to. If the string is less then the specified number of characters one or more padding characters will be added until the length is reached.",
"minimum": 1
},
"paddingCharacter": {
"type": "string",
"title": "Padding characters",
"description": "Sets the character to use for padding the string to reach the configured total length.",
"minLength": 1,
"maxLength": 1,
"default": " "
}
},
"additionalProperties": false,
"required": [
"padLeft",
"totalLength"
]
},
"padRight": {
"type": "object",
"properties": {
"padRight": {
"oneOf": [
{
"type": "string",
"title": "Pad Right",
"description": "The padRight function returns a string with a minimum of the specified length.",
"markdownDescription": "The `padRight` function returns a string with a minimum of the specified length."
},
{
"type": "object",
"title": "Pad Right",
"description": "The padRight function returns a string with a minimum of the specified length.",
"markdownDescription": "The `padRight` function returns a string with a minimum of the specified length.",
"$ref": "#/definitions/fn/definitions/function"
}
],
"default": {}
},
"totalLength": {
"type": "integer",
"title": "Total length",
"description": "Sets the number of characters to pad the string to. If the string is less then the specified number of characters one or more padding characters will be added until the length is reached.",
"minimum": 1
},
"paddingCharacter": {
"type": "string",
"title": "Padding characters",
"description": "Sets the character to use for padding the string to reach the configured total length.",
"minLength": 1,
"maxLength": 1,
"default": " "
}
},
"additionalProperties": false,
"required": [
"padRight",
"totalLength"
]
}
}
}
Expand Down
20 changes: 20 additions & 0 deletions src/PSRule/Common/DictionaryExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,26 @@ public static bool TryGetInt(this IDictionary<string, object> dictionary, string
return false;
}

[DebuggerStepThrough]
public static bool TryGetChar(this IDictionary<string, object> dictionary, string key, out char? value)
{
value = null;
if (!dictionary.TryGetValue(key, out var o))
return false;

if (o is string svalue && svalue.Length == 1)
{
value = svalue[0];
return true;
}
if (o is char cvalue)
{
value = cvalue;
return true;
}
return false;
}

[DebuggerStepThrough]
public static bool TryGetString(this IDictionary<string, object> dictionary, string key, out string value)
{
Expand Down
47 changes: 47 additions & 0 deletions src/PSRule/Definitions/Expressions/Functions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,16 @@ internal static class Functions
private const string FIRST = "first";
private const string LAST = "last";
private const string SPLIT = "split";
private const string PADLEFT = "padLeft";
private const string PADRIGHT = "padRight";
private const string DELIMITER = "delimiter";
private const string OLDSTRING = "oldstring";
private const string NEWSTRING = "newstring";
private const string CASESENSITIVE = "casesensitive";
private const string TOTALLENGTH = "totalLength";
private const string PADDINGCHARACTER = "paddingCharacter";

private const char SPACE = ' ';

/// <summary>
/// The available built-in functions.
Expand All @@ -52,6 +58,8 @@ internal static class Functions
new FunctionDescriptor(FIRST, First),
new FunctionDescriptor(LAST, Last),
new FunctionDescriptor(SPLIT, Split),
new FunctionDescriptor(PADLEFT, PadLeft),
new FunctionDescriptor(PADRIGHT, PadRight),
};

private static ExpressionFnOuter Boolean(IExpressionContext context, PropertyBag properties)
Expand Down Expand Up @@ -244,6 +252,45 @@ private static ExpressionFnOuter Split(IExpressionContext context, PropertyBag p
};
}

private static ExpressionFnOuter PadLeft(IExpressionContext context, PropertyBag properties)
{
if (properties == null ||
properties.Count == 0 ||

!TryProperty(properties, PADLEFT, out ExpressionFnOuter next))
return null;

var paddingChar = properties.TryGetChar(PADDINGCHARACTER, out var c) ? c : SPACE;
var totalWidth = properties.TryGetInt(TOTALLENGTH, out var i) ? i : 0;
return (context) =>
{
var value = next(context);
if (ExpressionHelpers.TryString(value, convert: true, value: out var s))
return totalWidth > s.Length ? s.PadLeft(totalWidth.Value, paddingChar.Value) : s;

return null;
};
}

private static ExpressionFnOuter PadRight(IExpressionContext context, PropertyBag properties)
{
if (properties == null ||
properties.Count == 0 ||
!TryProperty(properties, PADRIGHT, out ExpressionFnOuter next))
return null;

var paddingChar = properties.TryGetChar(PADDINGCHARACTER, out var c) ? c : SPACE;
var totalWidth = properties.TryGetInt(TOTALLENGTH, out var i) ? i : 0;
return (context) =>
{
var value = next(context);
if (ExpressionHelpers.TryString(value, convert: true, value: out var s))
return totalWidth > s.Length ? s.PadRight(totalWidth.Value, paddingChar.Value) : s;

return null;
};
}

#region Helper functions

private static bool TryProperty(PropertyBag properties, string name, out int? value)
Expand Down
168 changes: 168 additions & 0 deletions tests/PSRule.Tests/FunctionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,174 @@ public void Split()
Assert.Null(fn(context, properties)(context));
}

[Fact]
public void PadLeft()
{
var context = GetContext();
var fn = GetFunction("padLeft");

var properties = new LanguageExpression.PropertyBag
{
{ "padLeft", "One" },
{ "totalLength", 5 }
};
Assert.Equal(" One", fn(context, properties)(context));


properties = new LanguageExpression.PropertyBag
{
{ "padLeft", "One" },
{ "totalLength", 3 }
};
Assert.Equal("One", fn(context, properties)(context));

properties = new LanguageExpression.PropertyBag
{
{ "padLeft", "One" },
{ "totalLength", 5 },
{ "paddingCharacter", '_' }
};
Assert.Equal("__One", fn(context, properties)(context));

properties = new LanguageExpression.PropertyBag
{
{ "padLeft", "One" },
{ "totalLength", 5 },
{ "paddingCharacter", "_" }
};
Assert.Equal("__One", fn(context, properties)(context));

properties = new LanguageExpression.PropertyBag
{
{ "padLeft", "One" },
{ "totalLength", 5 },
{ "paddingCharacter", "__" }
};
Assert.Equal(" One", fn(context, properties)(context));

properties = new LanguageExpression.PropertyBag
{
{ "padLeft", "One" },
{ "totalLength", 3 },
{ "paddingCharacter", "_" }
};
Assert.Equal("One", fn(context, properties)(context));

properties = new LanguageExpression.PropertyBag
{
{ "padLeft", "One" },
{ "totalLength", 1 },
{ "paddingCharacter", "_" }
};
Assert.Equal("One", fn(context, properties)(context));

properties = new LanguageExpression.PropertyBag
{
{ "padLeft", "One" },
{ "totalLength", -1 },
{ "paddingCharacter", "_" }
};
Assert.Equal("One", fn(context, properties)(context));

properties = new LanguageExpression.PropertyBag
{
{ "padLeft", null },
{ "totalLength", 5 }
};
Assert.Null(fn(context, properties)(context));

properties = new LanguageExpression.PropertyBag
{
{ "padLeft", "One" },
{ "totalLength", null }
};
Assert.Equal("One", fn(context, properties)(context));
}

[Fact]
public void PadRight()
{
var context = GetContext();
var fn = GetFunction("padRight");

var properties = new LanguageExpression.PropertyBag
{
{ "padRight", "One" },
{ "totalLength", 5 }
};
Assert.Equal("One ", fn(context, properties)(context));


properties = new LanguageExpression.PropertyBag
{
{ "padRight", "One" },
{ "totalLength", 3 }
};
Assert.Equal("One", fn(context, properties)(context));

properties = new LanguageExpression.PropertyBag
{
{ "padRight", "One" },
{ "totalLength", 5 },
{ "paddingCharacter", '_' }
};
Assert.Equal("One__", fn(context, properties)(context));

properties = new LanguageExpression.PropertyBag
{
{ "padRight", "One" },
{ "totalLength", 5 },
{ "paddingCharacter", "_" }
};
Assert.Equal("One__", fn(context, properties)(context));

properties = new LanguageExpression.PropertyBag
{
{ "padRight", "One" },
{ "totalLength", 5 },
{ "paddingCharacter", "__" }
};
Assert.Equal("One ", fn(context, properties)(context));

properties = new LanguageExpression.PropertyBag
{
{ "padRight", "One" },
{ "totalLength", 3 },
{ "paddingCharacter", "_" }
};
Assert.Equal("One", fn(context, properties)(context));

properties = new LanguageExpression.PropertyBag
{
{ "padRight", "One" },
{ "totalLength", 1 },
{ "paddingCharacter", "_" }
};
Assert.Equal("One", fn(context, properties)(context));

properties = new LanguageExpression.PropertyBag
{
{ "padRight", "One" },
{ "totalLength", -1 },
{ "paddingCharacter", "_" }
};
Assert.Equal("One", fn(context, properties)(context));

properties = new LanguageExpression.PropertyBag
{
{ "padRight", null },
{ "totalLength", 5 }
};
Assert.Null(fn(context, properties)(context));

properties = new LanguageExpression.PropertyBag
{
{ "padRight", "One" },
{ "totalLength", null }
};
Assert.Equal("One", fn(context, properties)(context));
}

#region Helper methods

private static ExpressionBuilderFn GetFunction(string name)
Expand Down
Loading