Skip to content

Commit d120bed

Browse files
Reject RFC0060 - Multi line continuation (#179)
* initial commit * added new breaking change findings * add alternatives and stop-parsing sigil support * incorporated some additional feedback * added missing copyright notice * added more alternatives * formatting changes * cleaned up wording to keep focus on the intent of the RFC * correction of misleading wording * reworked RFC based on comments/discussion * renamed RFC to match most recent proposal * cleaned up wording * corrected wording * responded to feedback * more feedback * Add alternate proposal * generalize focus, clean up formatting * add alternate proposal with enclosures * minor update * replace continuance with continuation * update based on feedback from @joeyaiello * Prepare RFC0060 (multi-line continuation) for rejection Co-authored-by: Joey Aiello <[email protected]>
1 parent e41474e commit d120bed

File tree

1 file changed

+303
-0
lines changed

1 file changed

+303
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,303 @@
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

Comments
 (0)