Koans/Foundations/AboutStringOperators.Koans.ps1

using module PSKoans
[Koan(Position = 115)]
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.'

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

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

            $____ | Should -Be ($String -ne $OtherString)
            $____ | Should -Be ($String -cne $OtherString)
        }
    }

    Context 'GreaterThan and LessThan' {

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

            $____ | Should -Be ($String -gt $OtherString)
            $____ | Should -Be ($String -lt $OtherString)
        }
    }
}

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"
            $Split = @(
                'hello'
                '____'
                'what'
                '____'
                '____'
                'day'
            )
            $Split | Should -Be ($String -split ' ')
        }

        It 'uses regex by default' {
            $String = 'hello.dear'
            # Since -split uses regex, we have to escape certain characters to treat them literally.
            @('____', '____') | Should -Be ($String -split '\.')
        }

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

        It 'can use simple matching' {
            $String = 'hello.dear'
            # Using SimpleMatch mode disables regex.
            @('____', '____') | Should -Be ($String -split '.', 0, 'SimpleMatch')
        }

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

    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?'
            '____' | Should -Be ($Array -join ' ')
        }

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

        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'

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

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

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

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

            '____' | Should -Be (-join $String[0..8])
        }
    }
}

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 = '____'

            # -match automatically stores results in the $matches automatic variable for later use.
            $Result = if ($String -match '[a-z]{4}') {
                $Matches[0]
            }

            $Result | Should -Be '____'
        }

        It 'supports named matches' {
            $String = 'PowerShell is useful'
            $Pattern = '^(?<FirstWord>[a-z]+) [a-z]+ (?<LastWord>[a-z]+)$'

            $String -match $Pattern | Should -BeTrue

            $Matches.FirstWord | Should -BeExactly '____'
            $Matches.LastWord | Should -BeExactly '____'

            # Reflect on the above and below... can you find what that will match the pattern?
            $String = '____'
            $Pattern = '^(?<FirstWord>[a-z]+) (?<SecondWord>[a-z]+)$'

            $String -match $Pattern | Should -BeTrue -Because 'The string must match the given pattern.'

            $Matches.FirstWord | Should -BeExactly 'Cool'
            $Matches.SecondWord | Should -BeExactly 'Koans'
        }

        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
            '__' | Should -Be $Matches[0]
            '1298' | Should -Be $Matches[1]
            '__' | Should -Be $Matches[2]
        }
    }

    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 string segments' {
            $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]'

            # By not providing a replacement string, the matching segments are removed.
            $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 is needed!
            $Replacement = '$1 $2 $3'

            '____' | Should -Be ($String -replace $Pattern, $Replacement)
        }
    }
}

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 = '____'

            '____' | Should -Be ($String -f $Name)
        }

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

            '____' | Should -Be ($String -f 154, 19)
        }
    }

    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)"

            '____' | Should -Be $String
        }

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

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