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
Original file line number Diff line number Diff line change
Expand Up @@ -1075,6 +1075,104 @@ void IContextTracker.TrackEOF()
}
}

/// <summary>
/// Gets or sets a culture name.
/// </summary>
[Parameter]
[ValidateSet(typeof(ValidateMatchStringCultureNamesGenerator))]
[ValidateNotNull]
public string Culture
{
get
{
switch (_stringComparison)
{
case StringComparison.Ordinal:
case StringComparison.OrdinalIgnoreCase:
{
return OrdinalCultureName;
}

case StringComparison.InvariantCulture:
case StringComparison.InvariantCultureIgnoreCase:
{
return InvariantCultureName;
}

case StringComparison.CurrentCulture:
case StringComparison.CurrentCultureIgnoreCase:
{
return CurrentCultureName;
}

default:
{
break;
}
}

return _cultureName;
}

set
{
_cultureName = value;
InitCulture();
}
}

internal const string OrdinalCultureName = "Ordinal";
internal const string InvariantCultureName = "Invariant";
internal const string CurrentCultureName = "Current";

private string _cultureName = CultureInfo.CurrentCulture.Name;
private StringComparison _stringComparison = StringComparison.CurrentCultureIgnoreCase;
private CompareOptions _compareOptions = CompareOptions.IgnoreCase;

private delegate int CultureInfoIndexOf(string source, string value, int startIndex, int count, CompareOptions options);

private CultureInfoIndexOf _cultureInfoIndexOf = CultureInfo.CurrentCulture.CompareInfo.IndexOf;

private void InitCulture()
{
_stringComparison = default;

switch (_cultureName)
{
case OrdinalCultureName:
{
_stringComparison = CaseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase;
_compareOptions = CaseSensitive ? CompareOptions.Ordinal : CompareOptions.OrdinalIgnoreCase;
_cultureInfoIndexOf = CultureInfo.InvariantCulture.CompareInfo.IndexOf;
break;
}

case InvariantCultureName:
{
_stringComparison = CaseSensitive ? StringComparison.InvariantCulture : StringComparison.InvariantCultureIgnoreCase;
_compareOptions = CaseSensitive ? CompareOptions.None : CompareOptions.IgnoreCase;
_cultureInfoIndexOf = CultureInfo.InvariantCulture.CompareInfo.IndexOf;
break;
}

case CurrentCultureName:
{
_stringComparison = CaseSensitive ? StringComparison.CurrentCulture : StringComparison.CurrentCultureIgnoreCase;
_compareOptions = CaseSensitive ? CompareOptions.None : CompareOptions.IgnoreCase;
_cultureInfoIndexOf = CultureInfo.CurrentCulture.CompareInfo.IndexOf;
break;
}

default:
{
var _cultureInfo = CultureInfo.GetCultureInfo(_cultureName);
_compareOptions = CaseSensitive ? CompareOptions.None : CompareOptions.IgnoreCase;
_cultureInfoIndexOf = _cultureInfo.CompareInfo.IndexOf;
break;
}
}
}

/// <summary>
/// Gets or sets the current pipeline object.
/// </summary>
Expand Down Expand Up @@ -1322,6 +1420,15 @@ private IContextTracker GetContextTracker() => (Raw || (_preContext == 0 && _pos
/// </summary>
protected override void BeginProcessing()
{
if (this.MyInvocation.BoundParameters.ContainsKey(nameof(Culture)) && !this.MyInvocation.BoundParameters.ContainsKey(nameof(SimpleMatch)))
{
InvalidOperationException exception = new InvalidOperationException(MatchStringStrings.CannotSpecifyCultureWithoutSimpleMatch);
ErrorRecord errorRecord = new ErrorRecord(exception, "CannotSpecifyCultureWithoutSimpleMatch", ErrorCategory.InvalidData, null);
this.ThrowTerminatingError(errorRecord);
}

InitCulture();

string suppressVt = Environment.GetEnvironmentVariable("__SuppressAnsiEscapeSequences");
if (!string.IsNullOrEmpty(suppressVt))
{
Expand Down Expand Up @@ -1728,13 +1835,11 @@ private bool DoMatchWorker(string operandString, MatchInfo matchInfo, out MatchI
}
else
{
StringComparison compareOption = CaseSensitive ?
StringComparison.CurrentCulture : StringComparison.CurrentCultureIgnoreCase;
while (patternIndex < Pattern.Length)
{
Copy link
Contributor

Choose a reason for hiding this comment

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

This old 'StringComparison compareOption = ...' code can be removed.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done.

string pat = Pattern[patternIndex];

int index = operandString.IndexOf(pat, compareOption);
int index = _cultureInfoIndexOf(operandString, pat, 0, operandString.Length, _compareOptions);
if (index >= 0)
{
if (shouldEmphasize)
Expand Down Expand Up @@ -1972,4 +2077,25 @@ private bool MeetsIncludeExcludeCriteria(string filename)
return ok;
}
}

/// <summary>
/// Get list of valid culture names for ValidateSet attribute.
/// </summary>
public class ValidateMatchStringCultureNamesGenerator : IValidateSetValuesGenerator
Copy link
Member

Choose a reason for hiding this comment

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

Can this be internal?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It must be public:

public ValidateSetAttribute(Type valuesGeneratorType)
{
// We check 'IsNotPublic' because we don't want allow 'Activator.CreateInstance' create an
// instance of non-public type.
if (!typeof(IValidateSetValuesGenerator).IsAssignableFrom(
valuesGeneratorType) || valuesGeneratorType.IsNotPublic)
{
throw PSTraceSource.NewArgumentException(nameof(valuesGeneratorType));
}

{
string[] IValidateSetValuesGenerator.GetValidValues()
{
var cultures = CultureInfo.GetCultures(CultureTypes.AllCultures);
var result = new List<string>(cultures.Length + 3);
result.Add(SelectStringCommand.OrdinalCultureName);
result.Add(SelectStringCommand.InvariantCultureName);
result.Add(SelectStringCommand.CurrentCultureName);
foreach (var cultureInfo in cultures)
{
result.Add(cultureInfo.Name);
}

return result.ToArray();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -129,4 +129,7 @@
<data name="InvalidRegex" xml:space="preserve">
<value>The string {0} is not a valid regular expression: {1}</value>
</data>
<data name="CannotSpecifyCultureWithoutSimpleMatch" xml:space="preserve">
<value>You must specify -Culture parameter only with -SimpleMatch parameter.</value>
</data>
</root>
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ Describe "Select-String" -Tags "CI" {
}

Context "String actions" {
$testinputone = "hello","Hello","goodbye"
$testinputtwo = "hello","Hello"
BeforeAll {
$testinputone = "hello","Hello","goodbye"
$testinputtwo = "hello","Hello"
}

it "Should be called without errors" {
{ $testinputone | Select-String -Pattern "hello" } | Should -Not -Throw
Expand Down Expand Up @@ -251,4 +253,45 @@ Describe "Select-String" -Tags "CI" {
Select-String second $testInputFile -Raw -Context 2,2 | Should -BeExactly $expected
}
}

Context "Culture parameter" {
It "Should throw if -Culture parameter is used without -SimpleMatch parameter" {
{ "1" | Select-String -Pattern "hello" -Culture "ru-RU" } | Should -Throw -ErrorId "CannotSpecifyCultureWithoutSimpleMatch,Microsoft.PowerShell.Commands.SelectStringCommand"
}

It "Should accept a culture: '<culture>'" -TestCases: @(
@{ culture = "Ordinal"},
@{ culture = "Invariant"},
@{ culture = "Current"},
@{ culture = "ru-RU"}
) {
param ($culture)
{ "1" | Select-String -Pattern "hello" -Culture $culture -SimpleMatch } | Should -Not -Throw
}

It "Should works if -Culture parameter is a culture name: '<culture>'-'<pattern>'-'CaseSensitive:<casesensitive>'" -TestCases: @(
@{pattern = 'file'; culture = 'tr-TR'; expected = 'file'; casesensitive = $false }
@{pattern = 'fIle'; culture = 'tr-TR'; expected = $null; casesensitive = $false }
@{pattern = 'fIle'; culture = 'tr-TR'; expected = $null; casesensitive = $true }
@{pattern = "f`u{0130}le"; culture = 'tr-TR';expected = 'file'; casesensitive = $false }
@{pattern = 'file'; culture = 'en-US'; expected = 'file'; casesensitive = $false }
@{pattern = 'fIle'; culture = 'en-US'; expected = 'file'; casesensitive = $false }
@{pattern = 'fIle'; culture = 'en-US'; expected = $null; casesensitive = $true }
@{pattern = 'file'; culture = 'Ordinal'; expected = 'file'; casesensitive = $false }
@{pattern = 'fIle'; culture = 'Ordinal'; expected = 'file'; casesensitive = $false }
@{pattern = 'fIle'; culture = 'Ordinal'; expected = $null; casesensitive = $true }
@{pattern = 'file'; culture = 'Invariant'; expected = 'file'; casesensitive = $false }
@{pattern = 'fIle'; culture = 'Invariant'; expected = 'file'; casesensitive = $false }
@{pattern = 'fIle'; culture = 'Invariant'; expected = $null; casesensitive = $true }
@{pattern = 'file'; culture = 'Current'; expected = 'file'; casesensitive = $false }
@{pattern = 'fIle'; culture = 'Current'; expected = 'file'; casesensitive = $false }
@{pattern = 'fIle'; culture = 'Current'; expected = $null; casesensitive = $true }
) {
param ($pattern, $culture, $expected, $casesensitive)

if ($culture -ne 'Current' -or [CultureInfo]::CurrentCulture.Name -ne "tr-TR") {
'file' | Select-String -Pattern $pattern -Culture $culture -SimpleMatch -CaseSensitive:$casesensitive | Should -BeExactly $expected
}
}
}
}