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