11function Get-Turtle {
22 <#
3+ . SYNOPSIS
4+ Gets Turtle in PowerShell
5+ . DESCRIPTION
6+ Gets, sets, and moves a turtle object in PowerShell.
7+ . NOTES
8+ Each argument can be the name of a member of the turtle object.
9+
10+ After a member name is encountered, subsequent arguments will be passed to the member as parameters.
311 . EXAMPLE
412 turtle square 50
513 . EXAMPLE
@@ -8,6 +16,7 @@ function Get-Turtle {
816 turtle polygon 10 6
917 . EXAMPLE
1018 turtle ('forward', 10, 'rotate', 120 * 3)
19+
1120 #>
1221 [CmdletBinding (PositionalBinding = $false )]
1322 [Alias (' turtle' )]
@@ -18,17 +27,12 @@ function Get-Turtle {
1827 if (-not $script :TurtleTypeData ) {
1928 $script :TurtleTypeData = Get-TypeData - TypeName Turtle
2029 }
21- $methodNames = @ (foreach ($memberName in $script :TurtleTypeData.Members.Keys ) {
22- if ($script :TurtleTypeData.Members [$memberName ] -is
23- [Management.Automation.Runspaces.ScriptMethodData ]) {
24- $memberName
25- }
26- })
30+ $memberNames = @ ($script :TurtleTypeData.Members.Keys )
2731
2832 if ($wordToComplete ) {
29- return $methodNames -like " $wordToComplete *"
33+ return $memberNames -like " $wordToComplete *"
3034 } else {
31- return $methodNames
35+ return $memberNames
3236 }
3337 })]
3438 [Parameter (ValueFromRemainingArguments )]
@@ -43,68 +47,143 @@ function Get-Turtle {
4347 $InputObject
4448 )
4549
46- begin {
47- $turtleType = Get-TypeData - TypeName Turtle
48- $memberNames = @ (foreach ($memberName in $turtleType.Members.Keys ) {
49- if (
50- ($turtleType.Members [$memberName ] -is [Management.Automation.Runspaces.ScriptMethodData ]) -or
51- (
52- $turtleType.Members [$memberName ] -is
53- [Management.Automation.Runspaces.AliasPropertyData ] -and
54- $turtleType.Members [
55- $turtleType.Members [$memberName ].ReferencedMemberName
56- ] -is [Management.Automation.Runspaces.ScriptMethodData ]
57- )
58- ) {
59- $memberName
60- }
61- })
50+ begin {
51+ # Get information about our turtle pseudo-type.
52+ $turtleType = Get-TypeData - TypeName Turtle
53+ # any member name is a potential command
54+ $memberNames = $turtleType.Members.Keys
6255
63- $memberNames = $memberNames | Sort-Object @ {Expression = { $_.Length };Descending = $true }, Name
56+ # We want to sort the member names by length, in case we need them in a pattern or want to sort quickly.
57+ $memberNames = $memberNames | Sort-Object @ {Expression = { $_.Length };Descending = $true }, name
58+ # Create a new turtle object in case we have no turtle input.
6459 $currentTurtle = [PSCustomObject ]@ {PSTypeName = ' Turtle' }
6560 }
6661
67- process {
68-
62+ process {
6963 if ($PSBoundParameters.InputObject -and
7064 $PSBoundParameters.InputObject.pstypenames -eq ' Turtle' ) {
7165 $currentTurtle = $PSBoundParameters.InputObject
66+ } elseif ($PSBoundParameters.InputObject ) {
67+ # If input was passed, and it was not a turtle, pass it through.
68+ return $PSBoundParameters.InputObject
7269 }
7370
74- $currentMethod = $null
7571
76- $wordsAndArguments = foreach ($arg in $ArgumentList ) {
72+ # First we want to split each argument into words.
73+ # This way, it is roughly the same if you say:
74+ # * `turtle 'forward 10'`
75+ # * `turtle forward 10`
76+ # * `turtle 'forward', 10`
77+ $wordsAndArguments = @ (foreach ($arg in $ArgumentList ) {
78+ # If the argument is a string, split it by whitespace.
7779 if ($arg -is [string ]) {
7880 $arg -split ' \s{1,}'
7981 } else {
82+ # otherwise, leave the argument alone.
8083 $arg
8184 }
82- }
85+ })
8386
84- :findCommand for ($argIndex = 0 ; $argIndex -lt $wordsAndArguments.Length ; $argIndex ++ ) {
87+ # Now that we have a series of words, we can process them.
88+ # We want to keep track of the current member,
89+ # and continue to the next word until we find a member name.
90+ $currentMember = $null
91+ $outputTurtle = $false
92+
93+ # To do this in one pass, we will iterate through the words and arguments.
94+ # We use an indexed loop so we can skip past claimed arguments.
95+ for ($argIndex = 0 ; $argIndex -lt $wordsAndArguments.Length ; $argIndex ++ ) {
8596 $arg = $wordsAndArguments [$argIndex ]
86- if ($arg -in $memberNames ) {
87- $currentMethod = $arg
88- for (
89- $methodArgIndex = $argIndex + 1 ;
90- $methodArgIndex -lt $wordsAndArguments.Length -and
91- $wordsAndArguments [$methodArgIndex ] -notin $memberNames ;
92- $methodArgIndex ++ ) {
97+ # If the argument is not in the member names list, we can complain about it.
98+ if ($arg -notin $memberNames ) {
99+ if (-not $currentMember -and $arg -is [string ]) {
100+ Write-Warning " Unknown command '$arg '."
93101 }
94- # Command without parameters
95- if ($methodArgIndex -eq $argIndex ) {
96- $argList = @ ()
97- $currentTurtle = $currentTurtle .$currentMethod.Invoke ()
102+ continue
103+ }
104+
105+
106+ # If we have a current member, we can invoke it or get it.
107+ $currentMember = $arg
108+ # We can also begin looking for arguments
109+ for (
110+ # at the next index.
111+ $methodArgIndex = $argIndex + 1 ;
112+ # We will continue until we reach the end of the words and arguments,
113+ $methodArgIndex -lt $wordsAndArguments.Length -and
114+ $wordsAndArguments [$methodArgIndex ] -notin $memberNames ;
115+ $methodArgIndex ++ ) {
116+ }
117+ # Now we know how long it took to get to the next member name.
118+
119+ # And we can determine if we have any parameters
120+ $argList =
121+ if ($methodArgIndex -eq ($argIndex + 1 )) {
122+ @ ()
98123 }
99124 else {
100- $argList = $wordsAndArguments [($argIndex + 1 ).. ($methodArgIndex - 1 )]
101- $currentTurtle = $currentTurtle .$currentMethod.Invoke ($argList )
102- # "$($currentMethod) $($argList -join ' ')"
125+ $wordsAndArguments [($argIndex + 1 ).. ($methodArgIndex - 1 )]
103126 $argIndex = $methodArgIndex - 1
104127 }
128+
129+ # Look up the member information for the current member.
130+ $memberInfo = $turtleType.Members [$currentMember ]
131+ # If it's an alias
132+ if ($memberInfo.ReferencedMemberName ) {
133+ # try to resolve it.
134+ $currentMember = $memberInfo.ReferencedMemberName
135+ $memberInfo = $turtleType.Members [$currentMember ]
136+ }
137+
138+
139+ # Now we want to get the output from the step.
140+ $stepOutput =
141+ if (
142+ # If the member is a method, let's invoke it.
143+ $memberInfo -is [Management.Automation.Runspaces.ScriptMethodData ] -or
144+ $memberInfo -is [Management.Automation.PSMethod ]
145+ ) {
146+ # If we have arguments,
147+ if ($argList ) {
148+ # pass them to the method.
149+ $currentTurtle .$currentMember.Invoke ($argList )
150+ } else {
151+ # otherwise, just invoke the method with no arguments.
152+ $currentTurtle .$currentMember.Invoke ()
153+ }
154+ } else {
155+ # If the member is a property, we can get it or set it.
156+
157+ # If we have any arguments,
158+ if ($argList ) {
159+ # lets try to set it.
160+ $currentTurtle .$currentMember = $argList
161+ } else {
162+ # otherwise, lets get the property
163+ $currentTurtle .$currentMember
164+ }
165+ }
166+
167+ # If the output is not a turtle object, we can output it.
168+ # NOTE: This will lead to multiple types of output in the pipeline.
169+ # Luckily, this should be one of the few cases where this does not annoy too much.
170+ # Properties being returned will largely be strings or numbers.
171+ if (-not ($stepOutput.pstypenames -eq ' Turtle' )) {
172+ # Output the step
173+ $stepOutput
174+ # and set the output turtle to false.
175+ $outputTurtle = $false
176+ } else {
177+ # Set the current turtle to the step output.
178+ $currentTurtle = $stepOutput
179+ # and output it later (presumably).
180+ $outputTurtle = $true
105181 }
106182 }
107-
108- return $currentTurtle
183+
184+ # If the last members returned a turtle object, we can output it.
185+ if ($outputTurtle ) {
186+ return $currentTurtle
187+ }
109188 }
110189}
0 commit comments