Skip to content
This repository was archived by the owner on Aug 2, 2023. It is now read-only.

Commit dade269

Browse files
author
Immo Landwerth
committed
Allow options to appear more than once
Unix has a strong tradition for scripting. In order to make it easier to forward arguments to scripts, it's common practice to allow options to appear more than once. The semantics are that the last one wins. So this: $ tool -a this -b -a that should be equivalent to $ tool -b -a that
1 parent 6bea801 commit dade269

5 files changed

Lines changed: 34 additions & 36 deletions

File tree

src/System.CommandLine/README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,18 @@ is equivalent to:
134134

135135
$ tool -a -m "hello"
136136

137+
### Multiple occurrences
138+
139+
Unix has a strong tradition for scripting. In order to make it easier to forward
140+
arguments to scripts, it's common practice to allow options to appear more than
141+
once. The semantics are that the last one wins. So this:
142+
143+
$ tool -a this -b -a that
144+
145+
has the same effect as that:
146+
147+
$ tool -b -a that
148+
137149
### Parameters
138150

139151
Parameters, sometimes also called non-option arguments, can be anywhere in the

src/System.CommandLine/src/System/CommandLine/ArgumentParser.cs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,13 +46,12 @@ public bool TryParseOption<T>(string diagnosticName, IReadOnlyCollection<string>
4646
return false;
4747
}
4848

49-
if (values.Count > 1)
50-
{
51-
var message = string.Format(Strings.OptionSpecifiedMultipleTimesFmt, diagnosticName);
52-
throw new ArgumentSyntaxException(message);
53-
}
49+
// Please note that we don't verify that the option is only specified once.
50+
// It's tradition on Unix to allow single options to occur more than once,
51+
// with 'last once wins' semantics. This simplifies scripting because you
52+
// easily combine arguments.
5453

55-
value = values.Single();
54+
value = values.Last();
5655
return true;
5756
}
5857

src/System.CommandLine/src/System/Strings.Designer.cs

Lines changed: 1 addition & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/System.CommandLine/src/System/Strings.resx

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -150,9 +150,6 @@
150150
<data name="OptionsMustBeDefinedBeforeParameters" xml:space="preserve">
151151
<value>Options must be defined before any parameters.</value>
152152
</data>
153-
<data name="OptionSpecifiedMultipleTimesFmt" xml:space="preserve">
154-
<value>option {0} is specified multiple times</value>
155-
</data>
156153
<data name="ResponseFileDoesNotExistFmt" xml:space="preserve">
157154
<value>Response file '{0}' doesn't exist.</value>
158155
</data>

src/System.CommandLine/tests/System/CommandLine/Tests/ArgumentSyntaxTests.cs

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -430,23 +430,6 @@ public void Option_Usage_Error_Conversion_CustomConverter()
430430
Assert.Equal("value 'abc' isn't valid for -o: invalid format", ex.Message);
431431
}
432432

433-
[Fact]
434-
public void Option_Usage_Error_Duplicate()
435-
{
436-
var ex = Assert.Throws<ArgumentSyntaxException>(() =>
437-
{
438-
Parse("-a -b -a", syntax =>
439-
{
440-
var arg1 = false;
441-
var arg2 = false;
442-
syntax.DefineOption("a", ref arg1, string.Empty);
443-
syntax.DefineOption("b", ref arg2, string.Empty);
444-
});
445-
});
446-
447-
Assert.Equal("option -a is specified multiple times", ex.Message);
448-
}
449-
450433
[Theory]
451434
[InlineData("-a")]
452435
[InlineData("-a:")]
@@ -511,6 +494,22 @@ public void Option_Usage_ViaModifer(string modifier)
511494
Assert.True(o);
512495
}
513496

497+
[Fact]
498+
public void Option_Usage_LastOneWins()
499+
{
500+
var a = string.Empty;
501+
var b = false;
502+
503+
Parse("-a x -b -a y -b:false", syntax =>
504+
{
505+
syntax.DefineOption("a", ref a, string.Empty);
506+
syntax.DefineOption("b", ref b, string.Empty);
507+
});
508+
509+
Assert.Equal("y", a);
510+
Assert.False(b);
511+
}
512+
514513
[Fact]
515514
public void Option_Usage_Flag_DoesNotRequireValue()
516515
{

0 commit comments

Comments
 (0)