Skip to content

Commit d53934e

Browse files
authored
Merge pull request #8 from jpdillingham/dev
Added support for the explicit operand delimiter, "--".
2 parents afad336 + da23e72 commit d53934e

File tree

4 files changed

+98
-7
lines changed

4 files changed

+98
-7
lines changed

README.md

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ located [here](http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap1
7171

7272
Each argument is treated as a key-value pair, regardless of whether a value is present. The general format is as follows:
7373

74-
```<-|--|/>argument-name<=|:| >["|']value['|"] [operand] ... [operand]```
74+
```<-|--|/>argument-name<=|:| >["|']value['|"] [--] [operand] ... [operand]```
7575

7676
The key-value pair may begin with a single dash, a pair of dashes (double dash), or a forward slash. Single and double dashes indicate the use of short
7777
or long names, respectively, which are covered below. The forward slash may represent either, but does not allow for the grouping of parameterless
@@ -86,6 +86,9 @@ Values may be any alphanumeric sequence, however if a value contains a space it
8686
Any word, or phrase enclosed in single or double quotes, will be parsed as an operand. The official specification requires operands to appear last, however this library will
8787
parse them in any position.
8888

89+
A double-hyphen ```--```, not enclosed in single or double quotes, and appearing with whitespace on either side, designates the end of the argument list and beginning of
90+
the operand list. Anything appearing after this delimiter is treated as an operand, even if it begins with a hyphen, double-hyphen or forward slash.
91+
8992
### Short Names
9093

9194
Short names consist of a single character, and arguments without parameters may be grouped. A grouping may be terminated with a single argument containing
@@ -158,11 +161,12 @@ new | slashes are ok too
158161

159162
### Operands
160163

161-
Any text in the string that doesn't match the argument-value format is considered an operand.
164+
Any text in the string that doesn't match the argument-value format is considered an operand. Any text which appears after a double-hyphen ```--```, not enclosed in single or double quotes, and with spaces on either side,
165+
is treated as an operand regardless of whether it matches the argument-value format.
162166

163167
#### Example
164168

165-
```-a foo bar "hello world" -b```
169+
```-a foo bar "hello world" -b -- -explicit operand```
166170

167171
Key | Value
168172
--- | ---
@@ -172,6 +176,8 @@ b |
172176
Operands
173177
1. bar
174178
2. "hello world"
179+
3. -explicit
180+
4. operand
175181

176182
## Parsing
177183

Utility.CommandLine.Arguments.Tests/Arguments.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,24 @@ public void Parse()
167167
Assert.NotEmpty(test);
168168
}
169169

170+
/// <summary>
171+
/// Tests the <see cref="Utility.CommandLine.Arguments.Parse(string)"/> method with an explicit operand delimiter.
172+
/// </summary>
173+
[Fact]
174+
public void ParseStrictOperands()
175+
{
176+
CommandLine.Arguments test = CommandLine.Arguments.Parse("--test one two -- three -four --five /six \"seven eight\" 'nine ten'");
177+
178+
Assert.Equal(7, test.OperandList.Count);
179+
Assert.Equal("two", test.OperandList[0]);
180+
Assert.Equal("three", test.OperandList[1]);
181+
Assert.Equal("-four", test.OperandList[2]);
182+
Assert.Equal("--five", test.OperandList[3]);
183+
Assert.Equal("/six", test.OperandList[4]);
184+
Assert.Equal("\"seven eight\"", test.OperandList[5]);
185+
Assert.Equal("'nine ten'", test.OperandList[6]);
186+
}
187+
170188
/// <summary>
171189
/// Tests the <see cref="Utility.CommandLine.Arguments.Parse(string)"/> method with an explicit command line string
172190
/// containing a mixture of upper and lower case arguments.

Utility.CommandLine.Arguments/Arguments.cs

Lines changed: 69 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,21 @@ public class Arguments
137137
/// </summary>
138138
private const string GroupRegEx = "^-[^-]+";
139139

140+
/// <summary>
141+
/// The regular expression with which to split the command line string explicitly among argument/value pairs and
142+
/// operands, and strictly operands.
143+
/// </summary>
144+
/// <remarks>
145+
/// This regular expression effectively splits a string into two parts; the part before the first "--", and the part
146+
/// after. Instances of "--" not surrounded by a word boundary and those enclosed in quotes are ignored.
147+
/// </remarks>
148+
private const string StrictOperandSplitRegEx = "(.*?)[^\\\"\\\']\\B-{2}\\B[^\\\"\\\'](.*)";
149+
150+
/// <summary>
151+
/// The regular expression with which to parse strings strictly containing operands.
152+
/// </summary>
153+
private const string OperandRegEx = "([^ ([^'\\\"]+|\\\"[^\\\"]+\\\"|\\\'[^']+\\\')";
154+
140155
#endregion Private Fields
141156

142157
#region Private Constructors
@@ -206,7 +221,37 @@ public static Arguments Parse(string commandLineString = "")
206221
{
207222
commandLineString = commandLineString.Equals(string.Empty) ? Environment.CommandLine : commandLineString;
208223

209-
return new Arguments(GetArgumentDictionary(commandLineString), GetOperands(commandLineString));
224+
Dictionary<string, string> argumentDictionary;
225+
List<string> operandList;
226+
227+
// use the strict operand regular expression to test for/extract the two halves of the string, if the operator is used.
228+
MatchCollection matches = Regex.Matches(commandLineString, StrictOperandSplitRegEx);
229+
230+
// if there is a match, the string contains the strict operand delimiter. parse the first and second matches accordingly.
231+
if (matches.Count > 0 && matches[0].Groups.Count >= 1)
232+
{
233+
// the first group of the first match will contain everything in the string prior to the strict operand delimiter,
234+
// so extract the argument key/value pairs and list of operands from that string.
235+
argumentDictionary = GetArgumentDictionary(matches[0].Groups[1].Value);
236+
operandList = GetOperandList(matches[0].Groups[1].Value);
237+
238+
// the first group of the second match will contain everything in the string after the strict operand delimiter, so
239+
// extract the operands from that string using the strict method.
240+
if (matches[0].Groups.Count > 1)
241+
{
242+
List<string> operandListStrict = GetOperandListStrict(matches[0].Groups[2].Value);
243+
244+
// join the operand lists.
245+
operandList.AddRange(operandListStrict);
246+
}
247+
}
248+
else
249+
{
250+
argumentDictionary = GetArgumentDictionary(commandLineString);
251+
operandList = GetOperandList(commandLineString);
252+
}
253+
254+
return new Arguments(argumentDictionary, operandList);
210255
}
211256

212257
/// <summary>
@@ -423,7 +468,7 @@ private static Dictionary<string, PropertyInfo> GetArgumentProperties(Type type)
423468
/// <returns>
424469
/// A list containing the operands specified in the command line arguments with which the application was started.
425470
/// </returns>
426-
private static List<string> GetOperands(string commandLineString)
471+
private static List<string> GetOperandList(string commandLineString)
427472
{
428473
List<string> operands = new List<string>();
429474

@@ -444,6 +489,28 @@ private static List<string> GetOperands(string commandLineString)
444489
return operands;
445490
}
446491

492+
/// <summary>
493+
/// Populates and returns a list containing the operands within the specified string grouped by whole words and groups
494+
/// of words contained within single or double quotes, treating strings that would otherwise be treated as argument
495+
/// keys as operands.
496+
/// </summary>
497+
/// <param name="operandListString">The string from which the list of operands is to be parsed.</param>
498+
/// <returns>
499+
/// A list containing the operands within the specified string grouped by whole words and groups of words contained
500+
/// within single or double quotes, treating strings that would otherwise be treated as argument keys as operands.
501+
/// </returns>
502+
private static List<string> GetOperandListStrict(string operandListString)
503+
{
504+
List<string> operands = new List<string>();
505+
506+
foreach (Match match in Regex.Matches(operandListString, OperandRegEx))
507+
{
508+
operands.Add(match.Groups[0].Value);
509+
}
510+
511+
return operands;
512+
}
513+
447514
/// <summary>
448515
/// Retrieves the property in the target <see cref="Type"/> marked with the
449516
/// <see cref="OperandsAttribute"/><see cref="Attribute"/>, if one exists.

Utility.CommandLine.Arguments/Properties/AssemblyInfo.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,5 @@
1111
[assembly: AssemblyCulture("")]
1212
[assembly: ComVisible(false)]
1313
[assembly: Guid("b739dd22-4bd3-4d17-a351-33d129e9fe30")]
14-
[assembly: AssemblyVersion("1.1.2")]
15-
[assembly: AssemblyFileVersion("1.1.2")]
14+
[assembly: AssemblyVersion("1.2.0")]
15+
[assembly: AssemblyFileVersion("1.2.0")]

0 commit comments

Comments
 (0)