Skip to content

Commit 83426f2

Browse files
Add setting to interpret options starting with a single dash as long named options
1 parent af75319 commit 83426f2

File tree

6 files changed

+153
-37
lines changed

6 files changed

+153
-37
lines changed

src/CommandLine/Core/GetoptTokenizer.cs

+47-8
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,22 @@ public static Result<IEnumerable<Token>, Error> Tokenize(
1616
IEnumerable<string> arguments,
1717
Func<string, NameLookupResult> nameLookup)
1818
{
19-
return GetoptTokenizer.Tokenize(arguments, nameLookup, ignoreUnknownArguments:false, allowDashDash:true, posixlyCorrect:false);
19+
return GetoptTokenizer.Tokenize(
20+
arguments,
21+
nameLookup,
22+
ignoreUnknownArguments: false,
23+
allowDashDash: true,
24+
posixlyCorrect: false,
25+
optionsParseMode: OptionsParseMode.Default);
2026
}
2127

2228
public static Result<IEnumerable<Token>, Error> Tokenize(
2329
IEnumerable<string> arguments,
2430
Func<string, NameLookupResult> nameLookup,
2531
bool ignoreUnknownArguments,
2632
bool allowDashDash,
27-
bool posixlyCorrect)
33+
bool posixlyCorrect,
34+
OptionsParseMode optionsParseMode)
2835
{
2936
var errors = new List<Error>();
3037
Action<string> onBadFormatToken = arg => errors.Add(new BadFormatTokenError(arg));
@@ -70,11 +77,31 @@ public static Result<IEnumerable<Token>, Error> Tokenize(
7077
break;
7178

7279
case string arg when arg.StartsWith("--"):
80+
81+
if (optionsParseMode == OptionsParseMode.SingleDashOnly)
82+
{
83+
onBadFormatToken(arg);
84+
continue;
85+
}
86+
7387
tokens.AddRange(TokenizeLongName(arg, nameLookup, onBadFormatToken, onUnknownOption, onConsumeNext));
7488
break;
7589

7690
case string arg when arg.StartsWith("-"):
77-
tokens.AddRange(TokenizeShortName(arg, nameLookup, onUnknownOption, onConsumeNext));
91+
switch(optionsParseMode)
92+
{
93+
case OptionsParseMode.Default:
94+
tokens.AddRange(TokenizeShortName(arg, nameLookup, onUnknownOption, onConsumeNext));
95+
break;
96+
97+
case OptionsParseMode.SingleOrDoubleDash:
98+
case OptionsParseMode.SingleDashOnly:
99+
tokens.AddRange(TokenizeLongName(arg, nameLookup, onBadFormatToken, onUnknownOption, onConsumeNext, dashCount: 1));
100+
break;
101+
102+
default:
103+
throw new ArgumentOutOfRangeException(nameof(optionsParseMode), optionsParseMode, null);
104+
}
78105
break;
79106

80107
case string arg:
@@ -126,12 +153,23 @@ public static Func<
126153
StringComparer nameComparer,
127154
bool ignoreUnknownArguments,
128155
bool enableDashDash,
129-
bool posixlyCorrect)
156+
bool posixlyCorrect,
157+
OptionsParseMode optionsParseMode)
130158
{
131159
return (arguments, optionSpecs) =>
132160
{
133-
var tokens = GetoptTokenizer.Tokenize(arguments, name => NameLookup.Contains(name, optionSpecs, nameComparer), ignoreUnknownArguments, enableDashDash, posixlyCorrect);
134-
var explodedTokens = GetoptTokenizer.ExplodeOptionList(tokens, name => NameLookup.HavingSeparator(name, optionSpecs, nameComparer));
161+
var tokens = GetoptTokenizer.Tokenize(
162+
arguments,
163+
name => NameLookup.Contains(name, optionSpecs, nameComparer),
164+
ignoreUnknownArguments,
165+
enableDashDash,
166+
posixlyCorrect,
167+
optionsParseMode);
168+
169+
var explodedTokens = GetoptTokenizer.ExplodeOptionList(
170+
tokens,
171+
name => NameLookup.HavingSeparator(name, optionSpecs, nameComparer));
172+
135173
return explodedTokens;
136174
};
137175
}
@@ -190,9 +228,10 @@ private static IEnumerable<Token> TokenizeLongName(
190228
Func<string, NameLookupResult> nameLookup,
191229
Action<string> onBadFormatToken,
192230
Action<string> onUnknownOption,
193-
Action<int> onConsumeNext)
231+
Action<int> onConsumeNext,
232+
int dashCount = 2)
194233
{
195-
string[] parts = arg.Substring(2).Split(new char[] { '=' }, 2);
234+
string[] parts = arg.Substring(dashCount).Split(new char[] { '=' }, 2);
196235
string name = parts[0];
197236
string value = (parts.Length > 1) ? parts[1] : null;
198237
// A parameter like "--stringvalue=" is acceptable, and makes stringvalue be the empty string

src/CommandLine/Core/Tokenizer.cs

+38-16
Original file line numberDiff line numberDiff line change
@@ -22,21 +22,41 @@ public static Result<IEnumerable<Token>, Error> Tokenize(
2222
public static Result<IEnumerable<Token>, Error> Tokenize(
2323
IEnumerable<string> arguments,
2424
Func<string, NameLookupResult> nameLookup,
25-
Func<IEnumerable<Token>, IEnumerable<Token>> normalize)
25+
Func<IEnumerable<Token>, IEnumerable<Token>> normalize,
26+
OptionsParseMode optionsParseMode = OptionsParseMode.Default)
2627
{
28+
var tokens = new List<Token>();
2729
var errors = new List<Error>();
30+
2831
Action<Error> onError = errors.Add;
2932

30-
var tokens = (from arg in arguments
31-
from token in !arg.StartsWith("-", StringComparison.Ordinal)
32-
? new[] { Token.Value(arg) }
33-
: arg.StartsWith("--", StringComparison.Ordinal)
34-
? TokenizeLongName(arg, onError)
35-
: TokenizeShortName(arg, nameLookup)
36-
select token)
37-
.Memoize();
33+
foreach (var argument in arguments)
34+
{
35+
if (!argument.StartsWith("-", StringComparison.Ordinal))
36+
{
37+
tokens.Add(Token.Value(argument));
38+
}
39+
else if (argument.StartsWith("--", StringComparison.Ordinal))
40+
{
41+
if (optionsParseMode == OptionsParseMode.SingleDashOnly)
42+
{
43+
onError(new BadFormatTokenError(argument));
44+
continue;
45+
}
46+
47+
tokens.AddRange(TokenizeLongName(argument, onError));
48+
}
49+
else if (optionsParseMode == OptionsParseMode.SingleOrDoubleDash || optionsParseMode == OptionsParseMode.SingleDashOnly)
50+
{
51+
tokens.AddRange(TokenizeLongName(argument, onError, dashCount: 1));
52+
}
53+
else
54+
{
55+
tokens.AddRange(TokenizeShortName(argument, nameLookup));
56+
}
57+
}
3858

39-
var normalized = normalize(tokens).Memoize();
59+
var normalized = normalize(tokens.Memoize()).Memoize();
4060

4161
var unkTokens = (from t in normalized where t.IsName() && nameLookup(t.Text) == NameLookupResult.NoOptionFound select t).Memoize();
4262

@@ -123,7 +143,8 @@ public static Func<
123143
ConfigureTokenizer(
124144
StringComparer nameComparer,
125145
bool ignoreUnknownArguments,
126-
bool enableDashDash)
146+
bool enableDashDash,
147+
OptionsParseMode optionsParseMode = OptionsParseMode.Default)
127148
{
128149
return (arguments, optionSpecs) =>
129150
{
@@ -136,8 +157,8 @@ public static Func<
136157
? Tokenizer.PreprocessDashDash(
137158
arguments,
138159
args =>
139-
Tokenizer.Tokenize(args, name => NameLookup.Contains(name, optionSpecs, nameComparer), normalize))
140-
: Tokenizer.Tokenize(arguments, name => NameLookup.Contains(name, optionSpecs, nameComparer), normalize);
160+
Tokenizer.Tokenize(args, name => NameLookup.Contains(name, optionSpecs, nameComparer), normalize, optionsParseMode))
161+
: Tokenizer.Tokenize(arguments, name => NameLookup.Contains(name, optionSpecs, nameComparer), normalize, optionsParseMode);
141162
var explodedTokens = Tokenizer.ExplodeOptionList(tokens, name => NameLookup.HavingSeparator(name, optionSpecs, nameComparer));
142163
return explodedTokens;
143164
};
@@ -191,11 +212,12 @@ private static IEnumerable<Token> TokenizeShortName(
191212

192213
private static IEnumerable<Token> TokenizeLongName(
193214
string value,
194-
Action<Error> onError)
215+
Action<Error> onError,
216+
int dashCount = 2)
195217
{
196-
if (value.Length > 2 && value.StartsWith("--", StringComparison.Ordinal))
218+
if (value.Length > dashCount && value.StartsWith(new string('-', dashCount), StringComparison.Ordinal))
197219
{
198-
var text = value.Substring(2);
220+
var text = value.Substring(dashCount);
199221
var equalIndex = text.IndexOf('=');
200222
if (equalIndex <= 0)
201223
{

src/CommandLine/OptionsParseMode.cs

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information.
2+
3+
namespace CommandLine
4+
{
5+
/// <summary>
6+
/// Defines how commandline options are being parsed.
7+
/// </summary>
8+
public enum OptionsParseMode
9+
{
10+
/// <summary>
11+
/// Options that start with a double dash must be defined using its full name. E.g. git rebase --interactive
12+
/// Options that start with a single dash are interpreted as list of short named options. E.g. git clean -xdf
13+
/// </summary>
14+
Default,
15+
16+
/// <summary>
17+
/// Options that start with a single or double dash are interpreted as short or full named option.
18+
/// </summary>
19+
SingleOrDoubleDash,
20+
21+
/// <summary>
22+
/// Options that start with a single dash are interpreted as short or full named option.
23+
/// Options that start with a double dash are considered an invalid input.
24+
/// </summary>
25+
SingleDashOnly
26+
}
27+
}

src/CommandLine/Parser.cs

+8-5
Original file line numberDiff line numberDiff line change
@@ -190,27 +190,30 @@ private static Result<IEnumerable<Token>, Error> Tokenize(
190190
settings.NameComparer,
191191
settings.IgnoreUnknownArguments,
192192
settings.EnableDashDash,
193-
settings.PosixlyCorrect)(arguments, optionSpecs)
193+
settings.PosixlyCorrect,
194+
settings.OptionsParseMode)(arguments, optionSpecs)
194195
: Tokenizer.ConfigureTokenizer(
195196
settings.NameComparer,
196197
settings.IgnoreUnknownArguments,
197-
settings.EnableDashDash)(arguments, optionSpecs);
198+
settings.EnableDashDash,
199+
settings.OptionsParseMode)(arguments, optionSpecs);
198200
}
199201

200202
private static ParserResult<T> MakeParserResult<T>(ParserResult<T> parserResult, ParserSettings settings)
201203
{
202204
return DisplayHelp(
203205
parserResult,
204206
settings.HelpWriter,
205-
settings.MaximumDisplayWidth);
207+
settings.MaximumDisplayWidth,
208+
settings.OptionsParseMode != OptionsParseMode.Default);
206209
}
207210

208-
private static ParserResult<T> DisplayHelp<T>(ParserResult<T> parserResult, TextWriter helpWriter, int maxDisplayWidth)
211+
private static ParserResult<T> DisplayHelp<T>(ParserResult<T> parserResult, TextWriter helpWriter, int maxDisplayWidth, bool useSingleDashForOptions)
209212
{
210213
parserResult.WithNotParsed(
211214
errors =>
212215
Maybe.Merge(errors.ToMaybe(), helpWriter.ToMaybe())
213-
.Do((_, writer) => writer.Write(HelpText.AutoBuild(parserResult, maxDisplayWidth)))
216+
.Do((_, writer) => writer.Write(HelpText.AutoBuild(parserResult, maxDisplayWidth, useSingleDashForOptions)))
214217
);
215218

216219
return parserResult;

src/CommandLine/ParserSettings.cs

+10
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ public class ParserSettings : IDisposable
2525
private bool autoVersion;
2626
private CultureInfo parsingCulture;
2727
private Maybe<bool> enableDashDash;
28+
private OptionsParseMode optionsParseMode;
2829
private int maximumDisplayWidth;
2930
private Maybe<bool> allowMultiInstance;
3031
private bool getoptMode;
@@ -174,6 +175,15 @@ public bool EnableDashDash
174175
set => PopsicleSetter.Set(Consumed, ref enableDashDash, Maybe.Just(value));
175176
}
176177

178+
/// <summary>
179+
/// Gets or sets a value indicating how commandline options are being parsed.
180+
/// </summary>
181+
public OptionsParseMode OptionsParseMode
182+
{
183+
get => optionsParseMode;
184+
set => PopsicleSetter.Set(Consumed, ref optionsParseMode, value);
185+
}
186+
177187
/// <summary>
178188
/// Gets or sets the maximum width of the display. This determines word wrap when displaying the text.
179189
/// </summary>

0 commit comments

Comments
 (0)