Skip to content

Commit

Permalink
Added padLeft and padRight functions #1422 (#1526)
Browse files Browse the repository at this point in the history
  • Loading branch information
BernieWhite authored May 7, 2023
1 parent 4b38a9c commit 431e13a
Show file tree
Hide file tree
Showing 9 changed files with 424 additions and 14 deletions.
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

0 comments on commit 431e13a

Please sign in to comment.