Koans/Foundations/AboutLoopsAndPipelines.Koans.ps1

using module PSKoans
[Koan(Position = 121)]
param()
<#
    The Pipeline & Loops

    A core tenet of PowerShell scripts is its pipeline. It's mainly just a fancy way
    of iterating over an array of items, so we'll also cover standard loops as well
    to give a comparison.

    Unlike standard loops, where altering the collection you're iterating through is
    considered a terrible idea, so much so that many languages (including PowerShell)
    actually throw an error if you try, pipelines are designed to break apart a
    collection into its parts and operate on the pieces one at a time.

    As such, modifying the 'collection' mid-pipeline is very common. Pipelines are
    commonly used to take an array of input and perform multiple actions on each item
    before storing or outputting the result.
#>

Describe 'Pipelines and Loops' {

    Context 'The Pipeline' {

        It 'is used to process input in several stages' {
            # The .. operator is used to quickly create arrays with a range of numbers
            $Values = 1..5
            # Where-Object is a filtering cmdlet, used to discard selected objects mid-pipeline.
            $Values | Where-Object { $_ -lt 3 } | Should -Be @(1, 2)
        }

        It 'often uses $_ or $PSItem to indicate the current item in the pipeline' {
            <#
                The pipeline takes each element of the array and feeds them one by one
                into the next cmdlet.
            #>

            1..5 |
                ForEach-Object {
                    <#
                        ForEach-Object is a cmdlet that utilises the pipeline to create a
                        sort of loop, where each object that it receives from the pipeline
                        has the same set of actions performed on it.

                        In pipeline contexts, you will frequently see $_ or $PSItem variables
                        used. These are automatic variables used to denote 'the current
                        object in the pipeline'.
                    #>

                    $_ | Should -Be $PSItem
                    <#
                        Expressions not stored in a variable are dropped back to the output
                        stream. In pipelines, this means that they are passed along to the
                        next pipeline command. If a pipeline ends and it is not stored in a
                        variable, all output will eventually end up in the main output stream.

                        In the console, this will be displayed on screen.
                    #>

                    $_ + 2
                } |
                Should -Be __ # What happens to the array after we change each value?
        }

        It 'can store output after processing it through multiple cmdlets' {
            <#
                Values can be stored at the end of a pipeline by storing the entire
                pipeline sequence in a variable. This will create an array of the final
                values.
            #>

            $Strings = 3..7 |
                ForEach-Object { "Hello $_!" } | # Line breaks after a pipe character are OK!
                Where-Object { $_ -notlike '*5*' } # (Indents are optional.)
            __ | Should -Be $Strings
        }

        It 'can reference previous values from each pipeline segment with -PipelineVariable' {
            <#
                -PipelineVariable is a common parameter available to all cmdlets and advanced
                functions. It takes a single string value, which becomes an extra variable in
                the pipeline, similar to $PSItem or $_.

                However, rather than referencing the "current" item in the pipeline at that
                stage, it instead refers to the "current" item that is output from that
                specific pipeline step, allowing a pipeline to self-reference a value from
                earlier in the sequence.
            #>


            1..5 |
                Write-Output -PipelineVariable InitialValue |
                ForEach-Object { $_ * 2 } |
                ForEach-Object { "Initial: $InitialValue, Current: $_" } |
                Should -Be @(
                    "Initial: 1, Current: 2"
                    "Initial: __, Current: 4"
                    "Initial: 3, Current: __"
                    "Initial: __, Current: __"
                    "Initial: __, Current: __"
                )
        }
    }

    Context 'Loop Statements' {

        It 'can loop over a collection of items' {
            <#
                Standard loops are also available in PowerShell, but unlike most other languages we
                can still utilise PowerShell's output stream to bundle all their output by assigning
                the loop statement to a variable.
            #>

            $Values = foreach ($Number in 1..5) {
                <#
                    In these kinds of loops, the specified variable name ($Number in this case)
                    takes the place of the $_ or $PSItem automatic variables instead.
                #>

                $Number
            }
            __ | Should -Be $Values
        }

        It 'can loop a set number of times' {
            $Values = for ($i = 0; $i -lt 5; $i++) {
                <#
                    For loops are quite rare in native PowerShell code. They have their uses, but are
                    frequently too semantically obscure to have a place in PS's verbose ecosystem.
                    Remember:
                        1. ++ after the variable will reference the existing value then increment the variable.
                        2. ++ before the variable will increment the variable and then reference the new value.
                #>

                $i
            }
            $Values | Should -Be @(0, 1, 2, 3, 4)
        }

        It 'can loop while a condition is $true' {
            $Values = while ($true) {
                <#
                    Watch out for infinite loops!

                    Remember: an undeclared variable acts as zero until we increment it!
                    Incrementing a variable won't output the value
                    unless the expression is wrapped in parentheses.
                #>

                (++$Tick)

                <#
                    An alternative would be the following:
                    $returnvalue = ++$Tick
                    $returnvalue
                    Perhaps slightly more clear but more verbose
                #>


                if ($Tick -gt 2) {
                    break # the break statement breaks out of the current loop.
                }
            }
            __ | Should -Be $Values
        }

        It 'can loop one or more times depending on the conditional statement' {
            <#
                Do..While loops are just like their standard counterparts, but will always
                execute the loop at least once:
                    do { Do-Things } while ($condition -eq $true)

                There is also a Do..Until loop, which will run the loop while the condition
                evaluates to $false (i.e., until the condition becomes $true):
                    do { Do-Things } until ($condition -eq $true)
            #>

            $Values = do {
                'eat me!'
                # Exactly one iteration occurs despite the condition being $false.
            } while ($false)
            __ | Should -Be $Values
        }
    }
}