Skip to content

Commit 083b3b5

Browse files
committed
Merge pull request #16 from jen20/no-short-options
Removed requirement to have short name for every option.
2 parents eddb2d6 + ba0321c commit 083b3b5

File tree

5 files changed

+222
-61
lines changed

5 files changed

+222
-61
lines changed

FluentCommandLineParser.Tests/FluentCommandLineParser/when_setting_up_a_new_option/with_a_short_name/with_a_short_name_that_is_empty.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ namespace Fclp.Tests.FluentCommandLineParser
3030
{
3131
namespace when_setting_up_a_new_option
3232
{
33-
public class with_a_short_name_that_is_empty : SettingUpAShortOptionTestContext
33+
public class with_a_short_name_that_is_empty_and_a_long_name_that_is_empty : SettingUpAShortOptionTestContext
3434
{
3535
Establish context = AutoMockAll;
3636

FluentCommandLineParser.Tests/FluentCommandLineParserTests.cs

Lines changed: 84 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -356,9 +356,31 @@ public void Ensure_Parser_Calls_The_Callback_With_Expected_DateTime_When_Using_L
356356

357357
#endregion DateTime Option
358358

359-
#region Required
359+
#region Long Option Only
360360

361-
[Test]
361+
[Test]
362+
public void Can_have_long_option_only()
363+
{
364+
var parser = CreateFluentParser();
365+
var s = "";
366+
367+
parser.Setup<string>(null, "my-feature")
368+
.Callback(val => s = val);
369+
370+
var result = parser.Parse(new[] { "--my-feature", "somevalue" });
371+
372+
Assert.IsFalse(result.HasErrors);
373+
Assert.IsFalse(result.EmptyArgs);
374+
Assert.IsFalse(result.HelpCalled);
375+
376+
Assert.AreEqual("somevalue", s);
377+
}
378+
379+
#endregion
380+
381+
#region Required
382+
383+
[Test]
362384
public void Ensure_Expected_Error_Is_Returned_If_A_Option_Is_Required_And_Null_Args_Are_Specified()
363385
{
364386
var parser = CreateFluentParser();
@@ -558,48 +580,66 @@ public void Ensure_Defaults_Are_Called_When_Empty_Args_Specified()
558580

559581
#region Example
560582

561-
[Test]
562-
public void Ensure_Example_Works_As_Expected()
563-
{
564-
const int expectedRecordId = 10;
565-
const string expectedValue = "Mr. Smith";
566-
const bool expectedSilentMode = true;
567-
568-
var args = new[] { "-r", expectedRecordId.ToString(CultureInfo.InvariantCulture), "-v", "\"Mr. Smith\"", "--silent" };
569-
570-
int recordId = 0;
571-
string newValue = null;
572-
bool inSilentMode = false;
573-
574-
IFluentCommandLineParser parser = CreateFluentParser();
575-
576-
// create a new Option using a short and long name
577-
parser.Setup<int>("r", "record")
578-
.WithDescription("The record id to update (required)")
579-
.Callback(record => recordId = record) // use callback to assign the record value to the local RecordID property
580-
.Required(); // fail if this Option is not provided in the arguments
581-
582-
parser.Setup<string>("v", "value")
583-
.WithDescription("The new value for the record (required)") // used when help is requested e.g -? or --help
584-
.Callback(value => newValue = value)
585-
.Required();
586-
587-
// create an optional Option
588-
parser.Setup<bool>("s", "silent")
589-
.WithDescription("Execute the update in silent mode without feedback (default is false)")
590-
.Callback(silent => inSilentMode = silent)
591-
.SetDefault(false); // explicitly set the default value to use if this Option is not specified in the arguments
592-
593-
// do the work
594-
ICommandLineParserResult result = parser.Parse(args);
595-
596-
Assert.IsFalse(result.HasErrors);
597-
Assert.IsFalse(result.Errors.Any());
598-
599-
Assert.AreEqual(expectedRecordId, recordId);
600-
Assert.AreEqual(expectedValue, newValue);
601-
Assert.AreEqual(expectedSilentMode, inSilentMode);
602-
}
583+
[Test]
584+
public void Ensure_Example_Works_As_Expected()
585+
{
586+
const int expectedRecordId = 10;
587+
const string expectedValue = "Mr. Smith";
588+
const bool expectedSilentMode = true;
589+
const bool expectedSwitchA = true;
590+
const bool expectedSwitchB = true;
591+
const bool expectedSwitchC = false;
592+
593+
var args = new[] { "-r", expectedRecordId.ToString(CultureInfo.InvariantCulture), "-v", "\"Mr. Smith\"", "--silent", "-ab", "-c-" };
594+
595+
int recordId = 0;
596+
string newValue = null;
597+
bool inSilentMode = false;
598+
bool switchA = false;
599+
bool switchB = false;
600+
bool switchC = true;
601+
602+
IFluentCommandLineParser parser = CreateFluentParser();
603+
604+
parser.Setup<bool>("a")
605+
.Callback(value => switchA = value);
606+
607+
parser.Setup<bool>("b")
608+
.Callback(value => switchB = value);
609+
610+
parser.Setup<bool>("c")
611+
.Callback(value => switchC = value);
612+
613+
// create a new Option using a short and long name
614+
parser.Setup<int>("r", "record")
615+
.WithDescription("The record id to update (required)")
616+
.Callback(record => recordId = record) // use callback to assign the record value to the local RecordID property
617+
.Required(); // fail if this Option is not provided in the arguments
618+
619+
parser.Setup<bool>(null, "silent")
620+
.WithDescription("Execute the update in silent mode without feedback (default is false)")
621+
.Callback(silent => inSilentMode = silent)
622+
.SetDefault(false); // explicitly set the default value to use if this Option is not specified in the arguments
623+
624+
625+
parser.Setup<string>("v", "value")
626+
.WithDescription("The new value for the record (required)") // used when help is requested e.g -? or --help
627+
.Callback(value => newValue = value)
628+
.Required();
629+
630+
// do the work
631+
ICommandLineParserResult result = parser.Parse(args);
632+
633+
Assert.IsFalse(result.HasErrors);
634+
Assert.IsFalse(result.Errors.Any());
635+
636+
Assert.AreEqual(expectedRecordId, recordId);
637+
Assert.AreEqual(expectedValue, newValue);
638+
Assert.AreEqual(expectedSilentMode, inSilentMode);
639+
Assert.AreEqual(expectedSwitchA, switchA);
640+
Assert.AreEqual(expectedSwitchB, switchB);
641+
Assert.AreEqual(expectedSwitchC, switchC);
642+
}
603643

604644
#endregion
605645

FluentCommandLineParser.Tests/Internals/CommandLineOptionTests.cs

Lines changed: 120 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -89,39 +89,150 @@ public void Ensure_Can_Be_Constructed_With_Whitespace_Only_LongName()
8989
}
9090

9191
[Test]
92-
[ExpectedException(typeof(ArgumentOutOfRangeException))]
93-
public void Ensure_Cannot_Be_Constructed_With_Null_ShortName()
92+
public void Ensure_Can_Be_Constructed_With_Null_ShortName_And_Valid_LongName()
9493
{
9594
const string expectedShortName = null;
9695
const string expectedLongName = "My long name";
9796

9897
var mockParser = Mock.Of<ICommandLineOptionParser<object>>();
9998

100-
new CommandLineOption<object>(expectedShortName, expectedLongName, mockParser);
99+
var cmdOption = new CommandLineOption<object>(expectedShortName, expectedLongName, mockParser);
100+
101+
Assert.AreEqual(expectedShortName, cmdOption.ShortName, "Could not instantiate with null ShortName");
101102
}
102103

103104
[Test]
104-
[ExpectedException(typeof(ArgumentOutOfRangeException))]
105-
public void Ensure_Cannot_Be_Constructed_With_Empty_ShortName()
105+
public void Ensure_Can_Be_Constructed_With_Empty_ShortName_And_Valid_LongName()
106106
{
107107
const string expectedShortName = "";
108108
const string expectedLongName = "My long name";
109109
var mockParser = Mock.Of<ICommandLineOptionParser<object>>();
110110

111-
new CommandLineOption<object>(expectedShortName, expectedLongName, mockParser);
111+
var cmdOption = new CommandLineOption<object>(expectedShortName, expectedLongName, mockParser);
112+
113+
Assert.AreEqual(expectedShortName, cmdOption.ShortName, "Could not instantiate with empty ShortName");
112114
}
113115

114116
[Test]
115-
[ExpectedException(typeof(ArgumentOutOfRangeException))]
116-
public void Ensure_Cannot_Be_Constructed_With_Whitespace_Only_ShortName()
117+
public void Ensure_Can_Be_Constructed_With_Whitespace_Only_ShortName_And_Valid_LongName()
117118
{
118119
const string expectedShortName = " ";
119120
const string expectedLongName = "My long name";
120121
var mockParser = Mock.Of<ICommandLineOptionParser<object>>();
121122

122-
new CommandLineOption<object>(expectedShortName, expectedLongName, mockParser);
123+
var cmdOption = new CommandLineOption<object>(expectedShortName, expectedLongName, mockParser);
124+
125+
Assert.AreEqual(expectedShortName, cmdOption.ShortName, "Could not instantiate with whitespace only ShortName");
123126
}
124127

128+
[Test]
129+
[ExpectedException(typeof(ArgumentOutOfRangeException))]
130+
public void Ensure_Cannot_Be_Constructed_With_Null_ShortName_And_Null_LongName()
131+
{
132+
const string invalidShortName = null;
133+
const string invalidLongName = null;
134+
135+
var mockParser = Mock.Of<ICommandLineOptionParser<object>>();
136+
137+
new CommandLineOption<object>(invalidShortName, invalidLongName, mockParser);
138+
}
139+
140+
[Test]
141+
[ExpectedException(typeof(ArgumentOutOfRangeException))]
142+
public void Ensure_Cannot_Be_Constructed_With_Empty_ShortName_And_Null_LongName()
143+
{
144+
const string invalidShortName = "";
145+
const string invalidLongName = null;
146+
147+
var mockParser = Mock.Of<ICommandLineOptionParser<object>>();
148+
149+
new CommandLineOption<object>(invalidShortName, invalidLongName, mockParser);
150+
}
151+
152+
[Test]
153+
[ExpectedException(typeof(ArgumentOutOfRangeException))]
154+
public void Ensure_Cannot_Be_Constructed_With_WhiteSpaceOnly_ShortName_And_Null_LongName()
155+
{
156+
const string invalidShortName = " ";
157+
const string invalidLongName = null;
158+
159+
var mockParser = Mock.Of<ICommandLineOptionParser<object>>();
160+
161+
new CommandLineOption<object>(invalidShortName, invalidLongName, mockParser);
162+
}
163+
164+
[Test]
165+
[ExpectedException(typeof(ArgumentOutOfRangeException))]
166+
public void Ensure_Cannot_Be_Constructed_With_Null_ShortName_And_Empty_LongName()
167+
{
168+
const string invalidShortName = null;
169+
const string invalidLongName = "";
170+
171+
var mockParser = Mock.Of<ICommandLineOptionParser<object>>();
172+
173+
new CommandLineOption<object>(invalidShortName, invalidLongName, mockParser);
174+
}
175+
176+
[Test]
177+
[ExpectedException(typeof(ArgumentOutOfRangeException))]
178+
public void Ensure_Cannot_Be_Constructed_With_Empty_ShortName_And_Empty_LongName()
179+
{
180+
const string invalidShortName = "";
181+
const string invalidLongName = "";
182+
183+
var mockParser = Mock.Of<ICommandLineOptionParser<object>>();
184+
185+
new CommandLineOption<object>(invalidShortName, invalidLongName, mockParser);
186+
}
187+
188+
[Test]
189+
[ExpectedException(typeof(ArgumentOutOfRangeException))]
190+
public void Ensure_Cannot_Be_Constructed_With_WhiteSpaceOnly_ShortName_And_Empty_LongName()
191+
{
192+
const string invalidShortName = " ";
193+
const string invalidLongName = "";
194+
195+
var mockParser = Mock.Of<ICommandLineOptionParser<object>>();
196+
197+
new CommandLineOption<object>(invalidShortName, invalidLongName, mockParser);
198+
}
199+
200+
[Test]
201+
[ExpectedException(typeof(ArgumentOutOfRangeException))]
202+
public void Ensure_Cannot_Be_Constructed_With_Null_ShortName_And_WhiteSpaceOnly_LongName()
203+
{
204+
const string invalidShortName = null;
205+
const string invalidLongName = " ";
206+
207+
var mockParser = Mock.Of<ICommandLineOptionParser<object>>();
208+
209+
new CommandLineOption<object>(invalidShortName, invalidLongName, mockParser);
210+
}
211+
212+
[Test]
213+
[ExpectedException(typeof(ArgumentOutOfRangeException))]
214+
public void Ensure_Cannot_Be_Constructed_With_Empty_ShortName_And_WhiteSpaceOnly_LongName()
215+
{
216+
const string invalidShortName = "";
217+
const string invalidLongName = " ";
218+
219+
var mockParser = Mock.Of<ICommandLineOptionParser<object>>();
220+
221+
new CommandLineOption<object>(invalidShortName, invalidLongName, mockParser);
222+
}
223+
224+
[Test]
225+
[ExpectedException(typeof(ArgumentOutOfRangeException))]
226+
public void Ensure_Cannot_Be_Constructed_With_WhiteSpaceOnly_ShortName_And_WhiteSpaceOnly_LongName()
227+
{
228+
const string invalidShortName = " ";
229+
const string invalidLongName = " ";
230+
231+
var mockParser = Mock.Of<ICommandLineOptionParser<object>>();
232+
233+
new CommandLineOption<object>(invalidShortName, invalidLongName, mockParser);
234+
}
235+
125236
[Test]
126237
[ExpectedException(typeof(ArgumentNullException))]
127238
public void Ensure_Cannot_Be_Constructed_With_Null_Parser()

FluentCommandLineParser/FluentCommandLineParser.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,10 +123,11 @@ public ICommandLineOptionFluent<T> Setup<T>(string shortOption, string longOptio
123123
{
124124
EnsureIsValidShortName(shortOption);
125125
EnsureIsValidLongName(longOption);
126+
EnsureHasShortNameOrLongName(shortOption, longOption);
126127

127128
foreach (var option in this.Options)
128129
{
129-
if (shortOption.Equals(option.ShortName, this.StringComparison))
130+
if (shortOption != null && shortOption.Equals(option.ShortName, this.StringComparison))
130131
throw new OptionAlreadyExistsException(shortOption);
131132

132133
if (longOption != null && longOption.Equals(option.LongName, this.StringComparison))
@@ -145,6 +146,8 @@ public ICommandLineOptionFluent<T> Setup<T>(string shortOption, string longOptio
145146

146147
private static void EnsureIsValidShortName(string value)
147148
{
149+
if (string.IsNullOrEmpty(value)) return;
150+
148151
var invalidChars = SpecialCharacters.ValueAssignments.Union(new[] { SpecialCharacters.Whitespace });
149152
if (value.IsNullOrWhiteSpace() || invalidChars.Any(value.Contains) || value.Length > 1)
150153
throw new ArgumentOutOfRangeException("value");
@@ -162,6 +165,12 @@ private static void EnsureIsValidLongName(string value)
162165
throw new ArgumentOutOfRangeException("value");
163166
}
164167

168+
private static void EnsureHasShortNameOrLongName(string shortOption, string longOption)
169+
{
170+
if (shortOption.IsNullOrWhiteSpace() && longOption.IsNullOrWhiteSpace())
171+
throw new ArgumentOutOfRangeException("shortOption", "Either shortOption or longOption must be specified");
172+
}
173+
165174
/// <summary>
166175
/// Setup a new <see cref="ICommandLineOptionFluent{T}"/> using the specified short Option name.
167176
/// </summary>

FluentCommandLineParser/Internals/CommandLineOption.cs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,13 @@
2323
#endregion
2424

2525
using System;
26-
using System.Collections.Generic;
2726
using System.Linq;
2827
using Fclp.Internals.Extensions;
2928
using Fclp.Internals.Parsers;
3029

3130
namespace Fclp.Internals
3231
{
33-
/// <summary>
32+
/// <summary>
3433
/// A command line Option
3534
/// </summary>
3635
/// <typeparam name="T">The type of value this Option requires.</typeparam>
@@ -41,14 +40,16 @@ public class CommandLineOption<T> : ICommandLineOptionResult<T>
4140
/// <summary>
4241
/// Initialises a new instance of the <see cref="CommandLineOption{T}"/> class.
4342
/// </summary>
44-
/// <param name="shortName">The short name for this Option. This must not be <c>null</c>, <c>empty</c> or contain only <c>whitespace</c>.</param>
45-
/// <param name="longName">The long name for this Option or <c>null</c> if not required.</param>
43+
/// <param name="shortName">The short name for this Option or <c>null</c> if not required. Either <paramref name="shortName"/> or <paramref name="longName"/> must not be <c>null</c>, <c>empty</c> or contain only <c>whitespace</c>.</param>
44+
/// <param name="longName">The long name for this Option or <c>null</c> if not required. Either <paramref name="shortName"/> or <paramref name="longName"/> must not be <c>null</c>, <c>empty</c> or contain only <c>whitespace</c>.</param>
4645
/// <param name="parser">The parser to use for this Option.</param>
47-
/// <exception cref="ArgumentOutOfRangeException">Thrown if <paramref name="shortName"/> is <c>null</c>, <c>empty</c> or contains only <c>whitespace</c>.</exception>
46+
/// <exception cref="ArgumentOutOfRangeException">Thrown if both <paramref name="shortName"/> and <paramref name="longName"/> are <c>null</c>, <c>empty</c> or contain only <c>whitespace</c>.</exception>
4847
/// <exception cref="ArgumentNullException">If <paramref name="parser"/> is <c>null</c>.</exception>
4948
public CommandLineOption(string shortName, string longName, ICommandLineOptionParser<T> parser)
5049
{
51-
if (shortName.IsNullOrWhiteSpace()) throw new ArgumentOutOfRangeException("shortName");
50+
if (shortName.IsNullOrWhiteSpace() && longName.IsNullOrWhiteSpace())
51+
throw new ArgumentOutOfRangeException("shortName", "shortName and longName cannot both be null");
52+
5253
if (parser == null) throw new ArgumentNullException("parser");
5354

5455
this.ShortName = shortName;

0 commit comments

Comments
 (0)