Koans/Foundations/AboutStringOperators.Koans.ps1

#Requires -Module PSKoans
[Koan(109)]
param()
<#
    String Operators

    While the standard comparison operators can be used with strings, there are also
    several operators that work exclusively with strings.

    The string operators are below:

    Operator Name Purpose
    -------- ---- -------
    -eq, -ne Equal, Not Equal Compare string
    -gt, -lt GreaterThan, LessThan Compare string sort order
    -ge, -le Greater/LessOrEqual Compare string sort order
    -f Format Insert and format variables/expressions
    [ ] Index Access characters in string
    $( ) Subexpression Insert complex expressions into string
    -join Join Join string array into single string
    -replace Replace Regex: replace characters
    -split Split Regex: split string into array
    -match, -notmatch Match, Notmatch Regex: compare string with regex expression

    See 'Get-Help about_Operators' for more information.
#>


Describe 'String Comparison Operators' {
    <#
        String comparisons work a bit differently to other comparisons. Rather than comparing
        the value of data, the strings are sorted, and the result of the sort determines
        whether a given comparison returns $true.

        In other words, 'ab' -lt 'bc' is $true because the first string comes before the
        second in a simple ascending sort). -eq and -ne work basically as expected.

        Unless specified, all operators are case-insensitive. To make a given operator
        case sensitive, prefix it with 'c' (e.g., -ceq, -creplace, -cmatch)
    #>

    Context 'Equals and NotEquals' {

        It 'tells you whether strings are the same' {
            $String = 'this is a string.'
            $OtherString = 'This is a string.'

            $String -eq $OtherString | Should -Be __
            # Watch out for case sensitive operators!
            $String -ceq $OtherString | Should -Be __
        }

        It 'is useful for a straightforward comparison' {
            $String = 'one more string!'
            $OtherString = "ONE MORE STRING!"

            $String -ne $OtherString | Should -Be __
            $String -cne $OtherString | Should -Be __
        }
    }

    Context 'GreaterThan and LessThan' {

        It 'tells you where strings are in a sort' {
            $String = 'my string'
            $OtherString = 'your string'

            $String -gt $OtherString | Should -Be __
            $String -lt $OtherString | Should -Be __
        }
    }
}

Describe 'String Array Operators' {

    Context 'Split Operator' {
        <#
            The -split operator is used to break a longer string into several smaller strings.
            It can utilise either a regex match or a simple string character match, but
            defaults to regex.

            See 'Get-Help about_Split' for more information.
        #>

        It 'can split one string into several' {
            $String = "hello fellows what a lovely day"
            $String -split ' ' | Should -Be @(
                'hello'
                '__'
                'what'
                '__'
                '__'
                'day'
            )
        }

        It 'uses regex by default' {
            $String = 'hello.dear'
            $String -split '.' | Should -Be @('__', '__')
        }

        It 'can use simple matching' {
            $String = 'hello.dear'
            $String -split '.', 0, 'simplematch' | Should -Be @('__', '__')
        }

        It 'can limit the number of substrings' {
            $Planets = 'Mercury,Venus,Earth,Mars,Jupiter,Saturn,Uranus,Neptune'
            $Planets -split ',', 4 | Should -Be @('__', '__', '__', '__')
        }

        It 'can be case sensitive' {
            $String = "applesAareAtotallyAawesome!"
            $String -csplit 'A' | Should -Be @('__', '__', '__', '__')
        }
    }

    Context 'Join Operator' {
        <#
            -join is used to splice an array of strings together, optionally with a separator
            character. It comes in standard and unary forms:

                'string', 'string2' -join ' ' -> string string2
                -join ('string','string2') -> stringstring2

            See 'Get-Help about_Join' for more information.
        #>

        It 'can join an array into a single string' {
            $Array = 'Hi', 'there', ',', 'what', 'are', 'you', 'doing?'
            $Array -join ' ' | Should -Be '__'
        }

        It 'always produces a string result' {
            $Array = 1, 3, 6, 71, 9, 22, 1, 3, 4, 55,6,7,8
            -join $Array | Should -Be '__'
        }

        It 'can join with any delimiters' {
            $Array = 'This', 'is', 'so', 'embarrassing!'
            $Array -join '__' | Should -Be 'This-OW! is-OW! so-OW! embarrassing!'
        }
    }

    Context 'Index Operator' {

        It 'lets you treat strings as [char[]] arrays' {
            $String = 'Beware the man-eating rabbit'

            $String[5] | Should -Be '__'
        }

        It 'can create a [char[]] array from a string' {
            $String = 'Good luck!'

            $String[0..3] | Should -Be @('__', '__', '__', '__', '__')
        }

        It 'can be combined with -join to create substrings' {
            $String = 'Mountains are merely mountains.'

            -join $String[0..8] | Should -Be '__'
        }
    }
}

Describe 'Regex Operators' {
    <#
        Regex is a very complex language that doesn't read well. The examples here will be
        fairly straightforward. If you are not already familiar with regex, check out
        https://regexr.com/ and play around with the example patterns given, and try some of
        your own.

        In this section, try to enter a string that matches the given pattern, using
        the explanations of each pattern on the website above!
    #>

    Context 'Match and NotMatch' {
        # These operators return either $true or $false, indicating whether the string matched.

        It 'can be used as a simple conditional' {
            $String = '__'

            $String -match '[a-z]' | Should -BeTrue
            $String -notmatch '[xfd]' | Should -BeTrue
        }

        It 'can store the matched portions' {
            $String = '__'

            $Result = if ($String -match '[a-z]{4}') {
                $Matches[0]
            }

            $Result | Should -Be '__'
        }

        It 'supports named matches' {
            # Regex uses the (?<NAME>$pattern) syntax to name a portion of a matched string
            $String = '__'
            $Pattern = '^(?<FirstWord>[a-z]+) (?<SecondWord>[a-z]+)$'

            $Result = $String -match $Pattern

            $Result.FirstWord | Should -Be '__'
            $Result.SecondWord | Should -Be '__'
        }

        It 'supports indexed match groups' {
            # When selecting match groups from unnamed groups, the first group is at index 1
            # and the 'entire' matched portion is still at index 0
            $String = '1298-___-0000'
            $Pattern = '^([0-9]{4})-([0-5]{3})'
            $Result = $String -match $Pattern

            $Result | Should -BeTrue
            $Matches[0] | Should -Be '__'
            $Matches[1] | Should -Be '1298'
            $Matches[2] | Should -Be '__'

        }
    }

    Context 'Replace' {
        <#
            -replace also utilises regex to change the input string. Similarly to the string
            method .Replace($a,$b) it will replace every instance of the found pattern in the
            given string.
        #>

        It 'can be used to replace individual characters' {
            $String = 'Keep calm and carry on.'
            $Pattern = 'and'
            $Replacement = '__'

            $String -replace $Pattern, $Replacement | Should -Be 'Keep calm & carry on.'
        }

        It 'can be used to remove specific characters' {
            $String = 'Polish the granite, boy!'
            $Pattern = '[aeiou]|[^a-z]'

            $String -replace $Pattern | Should -Be '__'
        }

        It 'can be used to isolate specific portions of a string' {
            $String = 'Account Number: 0281.3649.8123'
            $Pattern = '\w+ \w+: (\d{4})\.(\d{4})\.(\d{4})'
            # These tokens are Regex variables, not PS ones; literal strings or escaping needed!
            $Replacement = '$1 $2 $3'

            $String -replace $Pattern, $Replacement | Should -Be '__'
        }
    }
}

Describe 'Formatting Operators' {

    Context 'String Format Operator' {
        <#
            The format operator (-f) uses the same formatting parser as the .NET method
            [string]::Format(), allowing for more complex strings to be constructed in
            a clear and concise fashion.
        #>

        It 'allows you to insert values into a literal string' {
            $String = 'Hello {0}, my name is also {0}!'
            $Name = '__'

            $String -f $Name | Should -Be '__'
        }

        It 'can insert multiple values with formatting on each' {
            $String = 'Employee #{0:000000}, you are due in room #{0:000} for a drug test.'

            $String -f 154, 19 | Should -Be '__'
        }
    }

    Context 'Subexpression Operator' {
        <#
            The subexpression operator $() is used to insert complex values into strings.
            Any valid PowerShell code is permitted inside the parentheses.
        #>

        It 'will convert any object to string' {
            $String = "Hello, user $(1..10)"

            $String | Should -Be '__'
        }

        It 'is necessary to insert object properties into strings' {
            $Object = Get-Item $Home

            "$Object.Parent" | Should -Be '__'
            "$($Object.Parent)" | Should -Be '__'
        }
    }
}