Skip to content
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
48 changes: 41 additions & 7 deletions src/System.Management.Automation/engine/lang/parserutils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -881,8 +881,8 @@ private static object AsChar(object obj)
/// <returns>The result of the operator</returns>
internal static object ReplaceOperator(ExecutionContext context, IScriptExtent errorPosition, object lval, object rval, bool ignoreCase)
{
string replacement = "";
object pattern = "";
object substitute = "";

rval = PSObject.Base(rval);
IList rList = rval as IList;
Expand All @@ -900,7 +900,7 @@ internal static object ReplaceOperator(ExecutionContext context, IScriptExtent e
pattern = rList[0];
if (rList.Count > 1)
{
replacement = PSObject.ToStringParser(context, rList[1]);
substitute = rList[1];
}
}
}
Expand Down Expand Up @@ -935,23 +935,58 @@ internal static object ReplaceOperator(ExecutionContext context, IScriptExtent e
{
string lvalString = lval?.ToString() ?? String.Empty;

// Find a single match in the string.
return rr.Replace(lvalString, replacement);
return ReplaceOperatorImpl(context, lvalString, rr, substitute);
}
else
{
List<object> resultList = new List<object>();
while (ParserOps.MoveNext(context, errorPosition, list))
{
string lvalString = PSObject.ToStringParser(context, ParserOps.Current(errorPosition, list));

resultList.Add(rr.Replace(lvalString, replacement));
resultList.Add(ReplaceOperatorImpl(context, lvalString, rr, substitute));
}

return resultList.ToArray();
}
}

/// <summary>
/// ReplaceOperator implementation.
/// Abstracts away conversion of the optional substitute parameter to either a string or a MatchEvaluator delegate
/// and finally returns the result of the final Regex.Replace operation.
/// </summary>
/// <param name="context">The execution context in which to evaluate the expression</param>
/// <param name="input">The input string</param>
/// <param name="regex">A Regex instance.</param>
/// <param name="substitute">The substitute value</param>
/// <returns>The result of the regex.Replace operation</returns>
private static object ReplaceOperatorImpl(ExecutionContext context, string input, Regex regex, object substitute)
{
switch (substitute)
{
case ScriptBlock sb:
MatchEvaluator me = match => {
var result = sb.DoInvokeReturnAsIs(
useLocalScope: false, /* Use current scope to be consistent with 'ForEach/Where-Object {}' and 'collection.ForEach{}/Where{}' */
errorHandlingBehavior: ScriptBlock.ErrorHandlingBehavior.WriteToCurrentErrorPipe,
dollarUnder: match,
input: AutomationNull.Value,
scriptThis: AutomationNull.Value,
args: Utils.EmptyArray<object>());

return PSObject.ToStringParser(context, result);;
};
return regex.Replace(input, me);

case object val when LanguagePrimitives.TryConvertTo(val, out MatchEvaluator matchEvaluator):
return regex.Replace(input, matchEvaluator);

default:
string replacement = PSObject.ToStringParser(context, substitute);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see tests for PSObject.ToStringParser - why we use this?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's used in the original code, to convert an object to a string:

if (rList.Count > 1)
{
    replacement = PSObject.ToStringParser(context, rList[1]);
}

Copy link
Collaborator

@iSazonov iSazonov Mar 14, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What PowerShell magic it give us? If it is useful I'd prefer to see related tests.

Copy link
Member

@daxian-dbw daxian-dbw Mar 14, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It converts an object to a string in a powershell way and is used extensively in powershell. You can see the code yourself for more information.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@daxian-dbw Thanks! I thought that using the method add some magic in the PR but no - it is only to get string.

return regex.Replace(input, replacement);
}
}

/// <summary>
/// Implementation of the PowerShell type operators...
/// </summary>
Expand Down Expand Up @@ -1906,4 +1941,3 @@ internal static void Trace(ExecutionContext context, int level, string messageId
}
#endregion ScriptTrace
}

67 changes: 67 additions & 0 deletions test/powershell/Language/Operators/ReplaceOperator.Tests.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.

Describe "Replace Operator" -Tags CI {
Context "Replace operator" {
It "Replace operator can replace string values using regular expressions" {
$res = "Get-Process" -replace "Get", "Stop"
$res | Should BeExactly "Stop-Process"

$res = "image.gif" -replace "\.gif$",".jpg"
$res | Should BeExactly "image.jpg"
}

It "Replace operator can be case-insensitive and case-sensitive" {
$res = "book" -replace "B","C"
$res | Should BeExactly "Cook"

$res = "book" -ireplace "B","C"
$res | Should BeExactly "Cook"

$res = "book" -creplace "B","C"
$res | Should BeExactly "book"
}

It "Replace operator can take 2 arguments, a mandatory pattern, and an optional substitution" {
$res = "PowerPoint" -replace "Point","Shell"
$res | Should BeExactly "PowerShell"

$res = "PowerPoint" -replace "Point"
$res | Should BeExactly "Power"
}
}

Context "Replace operator substitutions" {
It "Replace operator supports numbered substitution groups using ```$n" {
$res = "domain.example" -replace ".*\.(\w+)$","Tld of '`$0' is - '`$1'"
$res | Should BeExactly "Tld of 'domain.example' is - 'example'"
}

It "Replace operator supports named substitution groups using ```${name}" {
$res = "domain.example" -replace ".*\.(?<tld>\w+)$","`${tld}"
$res | Should BeExactly "example"
}

It "Replace operator can take a ScriptBlock in place of a substitution string" {
$res = "ID ABC123" -replace "\b[A-C]+", {return "X" * $_[0].Value.Length}
$res | Should BeExactly "ID XXX123"
}

It "Replace operator can take a MatchEvaluator in place of a substitution string" {
$matchEvaluator = {return "X" * $args[0].Value.Length} -as [System.Text.RegularExpressions.MatchEvaluator]
$res = "ID ABC123" -replace "\b[A-C]+", $matchEvaluator
$res | Should BeExactly "ID XXX123"
}

It "Replace operator can take a static PSMethod in place of a substitution string" {
class R {
static [string] Replace([System.Text.RegularExpressions.Match]$Match) {
return "X" * $Match.Value.Length
}
}
$substitutionMethod = [R]::Replace
$res = "ID 0000123" -replace "\b0+", $substitutionMethod
$res | Should BeExactly "ID XXXX123"
}
}
}