|
| 1 | +--- |
| 2 | +RFC: RFC0060 |
| 3 | +Author: Kirk Munro |
| 4 | +Status: Rejected |
| 5 | +SupercededBy: N/A |
| 6 | +Version: 0.3 |
| 7 | +Area: Parser/Tokenizer |
| 8 | +Comments Due: June 16, 2019 |
| 9 | +Plan to implement: Yes |
| 10 | +--- |
| 11 | + |
| 12 | +# Multi-line continuation |
| 13 | + |
| 14 | +Consider this example of a New-ADUser command invocation: |
| 15 | + |
| 16 | +```PowerShell |
| 17 | +New-ADUser -Name 'Jack Robinson' -GivenName 'Jack' -Surname 'Robinson' -SamAccountName 'J.Robinson' -UserPrincipalName '[email protected]' -Path 'OU=Users,DC=enterprise,DC=com' -AccountPassword (Read-Host -AsSecureString 'Input Password') -Enabled $true |
| 18 | +``` |
| 19 | + |
| 20 | +By itself it's not too much to handle, but in a script commands with many |
| 21 | +parameters like this can be difficult to manage. |
| 22 | + |
| 23 | +To wrap this command across multiple lines, users can either use backticks or |
| 24 | +they can use splatting. The former is something many members of the community |
| 25 | +consider to be a syntactical nuisance which should really only be used in |
| 26 | +situations when no other option is available. The latter is helpful, but it |
| 27 | +puts the parameters before the command, making it more difficult for less |
| 28 | +experienced users to learn/use, and all scripters lose the benefits of tab |
| 29 | +completion and Intellisense for parameters when they use splatting. |
| 30 | + |
| 31 | +As a workaround, they can work out the parameters they want to use for the |
| 32 | +command first, and then convert it into a splatted command, but that's onerous. |
| 33 | +Even though Visual Studio Code has an extension that makes splatting easier, as |
| 34 | +can be seen [here](https://sqldbawithabeard.com/2018/03/11/easily-splatting-powershell-with-vs-code/), once you've converted to splatting you still lose Intellisense |
| 35 | +for future updates unless you work from the command first and then add to your |
| 36 | +splatted collection, and that's just in Visual Studio Code. Other editors may |
| 37 | +or may not support that functionality, and users working in a standalone |
| 38 | +terminal won't have that available to them either. |
| 39 | + |
| 40 | +Instead, why not allow users to wrap commands across multiple lines in a more |
| 41 | +intuitive way without having to deal with backticks on every line or splatting, |
| 42 | +like this: |
| 43 | + |
| 44 | +```PowerShell |
| 45 | +New-ADUser @ |
| 46 | + -Name 'Jack Robinson' |
| 47 | + -GivenName 'Jack' |
| 48 | + -Surname 'Robinson' |
| 49 | + -SamAccountName 'J.Robinson' |
| 50 | + -UserPrincipalName '[email protected]' |
| 51 | + -Path 'OU=Users,DC=enterprise,DC=com' |
| 52 | + -AccountPassword (Read-Host -AsSecureString 'Input Password') |
| 53 | + -Enabled $true |
| 54 | +
|
| 55 | +Get-ChildItem @ |
| 56 | + $rootFolder |
| 57 | + -File |
| 58 | + -Filter '*.ps*1' |
| 59 | +
|
| 60 | +``` |
| 61 | + |
| 62 | +Of course, they could invoke external commands and pass through arguments this |
| 63 | +way as well: |
| 64 | + |
| 65 | +```PowerShell |
| 66 | +& "./plink.exe" @ |
| 67 | + --% $Hostname -l $Username -pw $Password $Command |
| 68 | +
|
| 69 | +cacls @ |
| 70 | + c:\docs\work |
| 71 | + /E /T /C /G |
| 72 | + "FinanceUsers":F |
| 73 | +
|
| 74 | +``` |
| 75 | + |
| 76 | +Further, by generalizing multi-line continuation with a `@` character, we're |
| 77 | +allowing users to apply line continuation the way they want to, which opens the |
| 78 | +door to more C#-like line wrapping when you're working with multiple members or |
| 79 | +methods in .NET, one after another. For example, this would work: |
| 80 | + |
| 81 | +```PowerShell |
| 82 | +$string @ |
| 83 | + .ToUpper() |
| 84 | + .Trim() |
| 85 | + .Length |
| 86 | +``` |
| 87 | + |
| 88 | +In each of these examples, the parser starts parsing the command as a |
| 89 | +multi-line command when it encounters the `@` token as the last token on the |
| 90 | +line, and in this mode command parsing stops once one of the following is |
| 91 | +found: |
| 92 | + |
| 93 | +* end of file |
| 94 | +* two newlines (as opposed to the normal one) |
| 95 | +* command-terminating token (i.e. all other ways of ending commands work the |
| 96 | +same as usual, and this does not affect other elements of the PowerShell |
| 97 | +syntax) |
| 98 | + |
| 99 | +The pros/cons to this new syntax are as follows: |
| 100 | + |
| 101 | +**Pros:** |
| 102 | + |
| 103 | +* allows the scripter to wrap commands how they see fit, while still getting |
| 104 | +Intellisense and tab completion, without using backticks. |
| 105 | +* aside from the `@` character to initiate multi-line continuation, the rest of |
| 106 | +the command is entered the exact same way it would be if it was entered on a |
| 107 | +single line. |
| 108 | +* ad hoc could support this syntax as well (PSReadline could wait for a |
| 109 | +double-enter when in multi-line command parsing mode) |
| 110 | +* no breaking changes (a standalone `@` is currently an unrecognized token in |
| 111 | +PowerShell no matter where it is used). |
| 112 | +* users can use a blank line to terminate the command, or they can opt to use |
| 113 | +any valid command-terminating token instead, so it has a proper closing |
| 114 | +character. |
| 115 | + |
| 116 | +**Cons:** |
| 117 | + |
| 118 | +* using a blank line as a statement terminator will be hard for some to accept |
| 119 | +(if you're one of those folks, read below to the alternative proposals and |
| 120 | +considerations section). |
| 121 | + |
| 122 | +## Motivation |
| 123 | + |
| 124 | + As a script/module author, |
| 125 | + I can wrap commands across multiple lines easily and intuitively without backticks or splatting |
| 126 | + so that my scripts remain easy to write and maintain while still giving me the benefits of Intellisense and tab completion. |
| 127 | + |
| 128 | +## Specification |
| 129 | + |
| 130 | +* expand the command parser to accept multi-line commands after an at symbol |
| 131 | +(`@`) is encountered at the end of a line |
| 132 | +* terminate multi-line commands when the parser encounters two newlines |
| 133 | +(rather than one), or when the parser encounters any other command-terminating |
| 134 | +token |
| 135 | + |
| 136 | +Note: |
| 137 | +* for commands that do not use the stop-parsing sigil in their arguments, |
| 138 | +command-terminating tokens include a pipe symbol, a redirection operator, a |
| 139 | +closing enclosure, a semi-colon, or a `&` background operator. |
| 140 | +* for commands that do use the stop-parsing sigil in their arguments, |
| 141 | +command-terminating tokens include a pipe symbol or a redirection operator. |
| 142 | + |
| 143 | +## Alternate Proposals and Considerations |
| 144 | + |
| 145 | +### A different sigil |
| 146 | + |
| 147 | +The original draft of this RFC included different options for the sigil that |
| 148 | +could be used to enter multi-line parameter/argument parsing mode, and others |
| 149 | +were presented in the discussion however none of the other sigils that were |
| 150 | +presented could be used without breaking changes. When considering an alternate |
| 151 | +sigil, it must be something that can be identified as a unique token without |
| 152 | +breaking commands that accept multiple strings as positional parameters, such |
| 153 | +as Write-Host (which can write many sigils to the console) or commands external |
| 154 | +to PowerShell. |
| 155 | + |
| 156 | +### Enclosures instead of a sigil |
| 157 | + |
| 158 | +Instead of only requiring a single leading sigil, some users prefer the notion |
| 159 | +of enclosures such that the multi-line command parsing mode would have a very |
| 160 | +clear and well defined start and end. To meet that need, we could follow the |
| 161 | +here-string syntax in PowerShell as an example, offering syntax like the |
| 162 | +following: |
| 163 | + |
| 164 | +```PowerShell |
| 165 | +. {"./plink.exe" @` |
| 166 | + --% |
| 167 | + $Hostname |
| 168 | + -l $Username |
| 169 | + -pw $Password |
| 170 | + $Command |
| 171 | +`@} |
| 172 | +``` |
| 173 | + |
| 174 | +All this would do is prevent newline tokens within the enclosures from being |
| 175 | +treated as statement terminators in the current statement. |
| 176 | + |
| 177 | +The closing closure could also be a recognized statement terminator even when |
| 178 | +used after the stop parsing sigil, allowing those commands to be wrapped across |
| 179 | +multiple lines as well. |
| 180 | + |
| 181 | +Like here-string enclosures, we could require that the opening closure be the |
| 182 | +last token on a line. Unlike here-string enclosures, however, it would be |
| 183 | +preferable if the closing closure did not have to be at the start of a line, |
| 184 | +since there is no need for it to be. It would simply have to be the first token |
| 185 | +on line to close the statement, allowing for indentation within scripts. |
| 186 | + |
| 187 | +This alternative also allows for blank lines to be used within the enclosures, |
| 188 | +such that Scenario 3 in @dragonwolf83's comment could be supported and written |
| 189 | +like this: |
| 190 | + |
| 191 | +```PowerShell |
| 192 | +New-ADUser @` |
| 193 | + -Name 'Jack Robinson' |
| 194 | + -GivenName 'Jack' |
| 195 | + -Surname 'Robinson' |
| 196 | + -SamAccountName 'J.Robinson' |
| 197 | +
|
| 198 | + -UserPrincipalName ( |
| 199 | + |
| 200 | + ) |
| 201 | +
|
| 202 | + # Get the list of regions for where a user would reside in to put the user into the correct region |
| 203 | + -Path ( |
| 204 | + $region = Get-Region -Name 'Jack Robinson' |
| 205 | + "OU=Users,OU=$region,DC=enterprise,DC=com" |
| 206 | + ) |
| 207 | +
|
| 208 | + -AccountPassword ( |
| 209 | + Read-Host -AsSecureString 'Input Password' |
| 210 | + ) |
| 211 | +
|
| 212 | + -Enabled $true |
| 213 | +`@ |
| 214 | +
|
| 215 | +Get-ChildItem @` |
| 216 | + $rootFolder |
| 217 | + -File |
| 218 | + -Filter '*.ps*1' |
| 219 | +`@ |
| 220 | +``` |
| 221 | + |
| 222 | +The best part is that it doesn't appear this syntax would introduce a breaking |
| 223 | +change at all. |
| 224 | + |
| 225 | +If people feel the backtick is still not visible enough here (and therefore not |
| 226 | +desirable for this purpose), we should keep the `@` portion of the enclosures |
| 227 | +so that we can avoid breaking changes, and simply replace the backtick with |
| 228 | +something else. For example, we could do this instead: |
| 229 | + |
| 230 | +```PowerShell |
| 231 | +Get-ChildItem @- |
| 232 | + $rootFolder |
| 233 | + -File |
| 234 | + -Filter '*.ps*1' |
| 235 | +-@ |
| 236 | +``` |
| 237 | + |
| 238 | +Backticks offer the advantage of continuing what they represent in PowerShell |
| 239 | +already (line continuation), and they are syntactically very similar to here- |
| 240 | +strings when used with the `@` symbol. That last point could be seen as a |
| 241 | +disadvantage as well, because they may be visually harder to distinguish from |
| 242 | +a single-quoted here-string. |
| 243 | + |
| 244 | +The `@-|-@` alternative doesn't pick up on the backtick for line continuation, |
| 245 | +but it is visually unique and easy to see in a script. The `-` character could |
| 246 | +be seen as representing the line that makes up the statement as well. |
| 247 | + |
| 248 | +### Inline splatting |
| 249 | + |
| 250 | +There has also been some discussion about the idea of inline splatting, using a |
| 251 | +format like `-@{...}` or `-@(...)`. Inline splatting has also been discussed |
| 252 | +separately on [RFC0002: Generalized Splatting](https://github.com/PowerShell/PowerShell-RFC/blob/master/2-Draft-Accepted/RFC0002-Generalized-Splatting.md), but using the syntax `@@{...}` or |
| 253 | +`@@(...)`. |
| 254 | + |
| 255 | +Here is an example showing what that might look like: |
| 256 | + |
| 257 | +```PowerShell |
| 258 | +Get-ChildItem -@{ |
| 259 | + LiteralPath = $rootFolder |
| 260 | + File = $true |
| 261 | + Filter = '*.ps*1' |
| 262 | +} |
| 263 | +``` |
| 264 | + |
| 265 | +Using inline splatting to be able to span a single command across multiple |
| 266 | +lines like this has several limitations, including: |
| 267 | + |
| 268 | +1. You cannot transition to/from the inline splatted syntax without a bunch of |
| 269 | +manual tweaks to the command (either converting parameter syntax into hashtable |
| 270 | +or array syntax or vice versa). |
| 271 | +1. You're forced to choose between named parameters or positional |
| 272 | +parameters/arguments for each splatted collection. i.e. You can splat in a |
| 273 | +hashtable of named parameter/value pairs or an array of positional values, but |
| 274 | +you can't mix the two (the example shown just above is also used earlier in |
| 275 | +this RFC with positional parameters and switch parameters used without values, |
| 276 | +matching the way it is often used as a single-line command). |
| 277 | +1. Inline splatting does not provide any support for wrapping unparsed |
| 278 | +arguments after the stop-parsing sigil. In contrast, with this proposal it |
| 279 | +would be possible to span commands that use the stop-parsing sigil across |
| 280 | +multiple lines. |
| 281 | +1. Splatting requires a different syntax than typical parameter/argument input, |
| 282 | +which is more to learn. In contrast, the proposal above only requires learning |
| 283 | +about the `@` sigil (borrowed from splatting, but without specifying hashtables |
| 284 | +or arrays -- just allow all content until a newline), reducing the learning |
| 285 | +curve and allowing users to use parameters the same way in either case. |
| 286 | +1. Inline splatting attempts to resolve the issue for commands with arguments, |
| 287 | +but it does nothing for other scenarios where you want specific line wrapping |
| 288 | +other than the defaults that PowerShell implicitly supports. |
| 289 | + |
| 290 | +Further, unlike using a leading sigil such as `@`, which would work with |
| 291 | +Intellisense and tab expansion as they are coded now, inline splatting would |
| 292 | +require special work to make Intellisense and tab expansion work with it. That |
| 293 | +is not a reason not to do it, but it is more code to write and maintain. |
| 294 | + |
| 295 | +### Breaking changes |
| 296 | + |
| 297 | +No known breaking changes in the original proposal, nor the alternative version |
| 298 | +that uses enclosures. |
| 299 | + |
| 300 | +All previous options from the original RFC and the discussion about it that |
| 301 | +would have introduced breaking changes have been removed in favor of a syntax |
| 302 | +that just works to the specification without any breaking changes, regardless |
| 303 | +of how you use it. |
0 commit comments