Commands/Get-Turtle.ps1
function Get-Turtle { <# .SYNOPSIS Gets Turtle in PowerShell .DESCRIPTION Gets, sets, and moves a turtle object in PowerShell. .NOTES Each argument can be the name of a member of the turtle object. After a member name is encountered, subsequent arguments will be passed to the member as parameters. .EXAMPLE turtle square 50 .EXAMPLE turtle circle 10 .EXAMPLE turtle polygon 10 6 .EXAMPLE turtle ('forward', 10, 'rotate', 120 * 3) #> [CmdletBinding(PositionalBinding=$false)] [Alias('turtle')] param( # The arguments to pass to turtle. [ArgumentCompleter({ param ( $commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters ) if (-not $script:TurtleTypeData) { $script:TurtleTypeData = Get-TypeData -TypeName Turtle } $memberNames = @($script:TurtleTypeData.Members.Keys) if ($wordToComplete) { return $memberNames -like "$wordToComplete*" } else { return $memberNames } })] [Parameter(ValueFromRemainingArguments)] [PSObject[]] $ArgumentList, # Any input object to process. # If this is already a turtle object, the arguments will be applied to this object. # If the input object is not a turtle object, it will be ignored and a new turtle object will be created. [Parameter(ValueFromPipeline)] [PSObject] $InputObject ) begin { # Get information about our turtle pseudo-type. $turtleType = Get-TypeData -TypeName Turtle # any member name is a potential command $memberNames = $turtleType.Members.Keys # We want to sort the member names by length, in case we need them in a pattern or want to sort quickly. $memberNames = $memberNames | Sort-Object @{Expression={ $_.Length };Descending=$true}, name # Create a new turtle object in case we have no turtle input. $currentTurtle = [PSCustomObject]@{PSTypeName='Turtle'} } process { if ($PSBoundParameters.InputObject -and $PSBoundParameters.InputObject.pstypenames -eq 'Turtle') { $currentTurtle = $PSBoundParameters.InputObject } elseif ($PSBoundParameters.InputObject) { # If input was passed, and it was not a turtle, pass it through. return $PSBoundParameters.InputObject } # First we want to split each argument into words. # This way, it is roughly the same if you say: # * `turtle 'forward 10'` # * `turtle forward 10` # * `turtle 'forward', 10` $wordsAndArguments = @(foreach ($arg in $ArgumentList) { # If the argument is a string, split it by whitespace. if ($arg -is [string]) { $arg -split '\s{1,}' } else { # otherwise, leave the argument alone. $arg } }) # Now that we have a series of words, we can process them. # We want to keep track of the current member, # and continue to the next word until we find a member name. $currentMember = $null $outputTurtle = $false # To do this in one pass, we will iterate through the words and arguments. # We use an indexed loop so we can skip past claimed arguments. for ($argIndex =0; $argIndex -lt $wordsAndArguments.Length; $argIndex++) { $arg = $wordsAndArguments[$argIndex] # If the argument is not in the member names list, we can complain about it. if ($arg -notin $memberNames) { if (-not $currentMember -and $arg -is [string]) { Write-Warning "Unknown command '$arg'." } continue } # If we have a current member, we can invoke it or get it. $currentMember = $arg # We can also begin looking for arguments for ( # at the next index. $methodArgIndex = $argIndex + 1; # We will continue until we reach the end of the words and arguments, $methodArgIndex -lt $wordsAndArguments.Length -and $wordsAndArguments[$methodArgIndex] -notin $memberNames; $methodArgIndex++) { } # Now we know how long it took to get to the next member name. # And we can determine if we have any parameters $argList = if ($methodArgIndex -eq ($argIndex + 1)) { @() } else { $wordsAndArguments[($argIndex + 1)..($methodArgIndex - 1)] $argIndex = $methodArgIndex - 1 } # Look up the member information for the current member. $memberInfo = $turtleType.Members[$currentMember] # If it's an alias if ($memberInfo.ReferencedMemberName) { # try to resolve it. $currentMember = $memberInfo.ReferencedMemberName $memberInfo = $turtleType.Members[$currentMember] } # Now we want to get the output from the step. $stepOutput = if ( # If the member is a method, let's invoke it. $memberInfo -is [Management.Automation.Runspaces.ScriptMethodData] -or $memberInfo -is [Management.Automation.PSMethod] ) { # If we have arguments, if ($argList) { # pass them to the method. $currentTurtle.$currentMember.Invoke($argList) } else { # otherwise, just invoke the method with no arguments. $currentTurtle.$currentMember.Invoke() } } else { # If the member is a property, we can get it or set it. # If we have any arguments, if ($argList) { # lets try to set it. $currentTurtle.$currentMember = $argList } else { # otherwise, lets get the property $currentTurtle.$currentMember } } # If the output is not a turtle object, we can output it. # NOTE: This will lead to multiple types of output in the pipeline. # Luckily, this should be one of the few cases where this does not annoy too much. # Properties being returned will largely be strings or numbers. if (-not ($stepOutput.pstypenames -eq 'Turtle')) { # Output the step $stepOutput # and set the output turtle to false. $outputTurtle = $false } else { # Set the current turtle to the step output. $currentTurtle = $stepOutput # and output it later (presumably). $outputTurtle = $true } } # If the last members returned a turtle object, we can output it. if ($outputTurtle) { return $currentTurtle } } } |