Skip to content

Commit

Permalink
Fix #31 #34 rework exception and trigger handling
Browse files Browse the repository at this point in the history
  • Loading branch information
nemec committed Jan 29, 2017
1 parent 7b300fd commit 4fcc9e1
Show file tree
Hide file tree
Showing 21 changed files with 1,192 additions and 728 deletions.
87 changes: 62 additions & 25 deletions README.md
Expand Up @@ -38,28 +38,39 @@ public class Options

static void Main()
{
var opt = CliParser.Parse<Options>(
var result = CliParser.Parse<Options>(
"-vvv output.txt 1 2 -1 7".Split());
Console.WriteLine(opt.Verbosity);
// >>> 3
Console.WriteLine(opt.OutputFile);
// >>> output.txt
var sum = 0;
foreach (var number in opt.Numbers)
{
sum += number;
}
Console.WriteLine(sum);
// >>> 9
result.Handle(
opt =>
{
Console.WriteLine(opt.Verbosity);
// >>> 3
Console.WriteLine(opt.OutputFile);
// >>> output.txt
var sum = 0;
foreach (var number in opt.Numbers)
{
sum += number;
}
Console.WriteLine(sum);
// >>> 9
},
t => Assert.Fail("Trigger {0} executed.", t),
e => Assert.Fail("Error parsing arguments."));
}
```

##Changelog

###Master

* Remove exception throwing when an error occurs. (#31)
* Change parsing return type to a union indicating whether parsing
succeeded, a trigger was hit, or an error occurred. (#34)


###2017-01-02 1.6.0

* Add ability to mask password.
Expand Down Expand Up @@ -120,6 +131,29 @@ a user passes the wrong arguments in to discover that you've defined a
duplicate argument short name or that the constant values you're storing
aren't actually convertible to the property type.

In order to use Integrity Checking you must call one of two validation
methods:

1. The `ValidateAttributeConfig` method returns an array of Exceptions that
list the various Integrity issues with your configuration. You may
inspect that collection and handle them accordingly.

var parser = new CliParser<Options>();
var errs = parser.ValidateAttributeConfig();
// do something with errors

2. The `EnsureValidAttributeConfig` method throws an AggregateException
when any integrity issues are found. The expectation is that this
exception remains *unhandled* so that, during development, any
configuration issues are immediately found and handled. Since this
method does not rely upon runtime input, there is no danger in
leaving it in when shipping, but for performance reasons you may
wish to exclude the validation from Release builds.

var parser = new CliParser<Options>();
parser.EnsureValidAttributeConfig();


#Features

##Named and Positional Arguments
Expand Down Expand Up @@ -264,7 +298,7 @@ public class Options
```

```csharp
var opt = CliParser.Parse<Options>(new[] {"-s"});
var result = CliParser.Parse<Options>(new[] {"-s"});
```

```csharp
Expand All @@ -280,7 +314,7 @@ public class OptionsWithPositional
```

```csharp
var opt = CliParser.Parse<OptionsWithPositional>(new[] {"-s - Nemec"});
var result = CliParser.Parse<OptionsWithPositional>(new[] {"-s - Nemec"});
```

Prints:
Expand Down Expand Up @@ -387,9 +421,9 @@ You may also programmatically generate the help and usage for a class:
var parser = new CliParser<Options>(new Options());
var help = new AutomaticHelpGenerator<Options>();
// Gets the usage message, a short summary of available arguments.
Console.WriteLine(help.GetUsage(parser.Config));
Console.WriteLine(help.GetUsage(parser.BuildConfig()));
// Gets the full help message with argument descriptions.
Console.WriteLine(help.GetHelp(parser.Config));
Console.WriteLine(help.GetHelp(parser.BuildConfig()));
```

Version information is similarly auto-generated from the version string
Expand Down Expand Up @@ -501,7 +535,10 @@ public static void Main()
{
var obj = CliParser.Parse<StaticEnumerationOptions>(
"-e first".Split());
Assert.AreSame(SomeEnum.First, obj.Value);
result.Handle(
opt => Assert.AreSame(SomeEnum.First, opt.Value),
t => Assert.Fail("Trigger {0} executed.", t),
e => Assert.Fail("Error parsing arguments."));
}
```

Expand All @@ -520,7 +557,7 @@ configuring.
```csharp
var opt = new Options();

new CliParserBuilder<Options>(opt)
new CliParserBuilder<Options>()
.HasNamedArgument(o => o.Verbosity)
.WithShortName('v')
.CountsInvocations()
Expand All @@ -532,7 +569,7 @@ new CliParserBuilder<Options>(opt)
.HasDescription("These are numbers.")
.Consumes.AtLeast(1)
.And.Parser
.Parse("-vvv -o out.txt 3 4 5 6".Split());
.Parse("-vvv -o out.txt 3 4 5 6".Split(), opt);

Console.WriteLine(opt.Verbosity);
// >>> 3
Expand All @@ -553,7 +590,7 @@ You may also add a Verb to the parser config with this syntax:

```csharp
var opt = new Options();
new CliParserBuilder<Options>(opt)
new CliParserBuilder<Options>()
.HasVerb("add", c => c.AddVerb,
// Note that in the Fluent Interface, you're nesting parsers
// Theoretically this means you can nest an
Expand All @@ -564,7 +601,7 @@ new CliParserBuilder<Options>(opt)
.HasPositionalArgument(c => c.Filename)
.And) // A necessary evil if defining inline.
.And.Parser
.Parse("add myfile.txt");
.Parse("add myfile.txt", opt);

Console.WriteLine(opt.AddVerb);
// myfile.txt
Expand All @@ -585,11 +622,11 @@ in Expressions).
```csharp
var key = 1;
var opt = new Dictionary<int, string>();
var parser = new CliParser<Dictionary<int, string>>(opt);
var parser = new CliParser<Dictionary<int, string>>();
parser.HasNamedArgument(c => c[key])
.WithShortName('n');

parser.Parse("-n frank".Split());
parser.Parse("-n frank".Split(), opt);

Console.WriteLine("Parsed Keys:");
foreach (var kv in opt)
Expand Down
48 changes: 35 additions & 13 deletions clipr.Sample/Program.cs
Expand Up @@ -198,8 +198,11 @@ public class CustomDateTimeOptions

static void CustomDateTime(string[] args)
{
var opt = CliParser.Parse<CustomDateTimeOptions>(args);
Console.WriteLine(opt.CurrentDate[1]);
var result = CliParser.Parse<CustomDateTimeOptions>(args);
result.Handle(
opt => Console.WriteLine(opt.CurrentDate[1]),
t => { },
e => { });
// >>>
}

Expand All @@ -216,8 +219,11 @@ public class RequiredNamedArgument

static void ParseRequiredNamedArgument(string[] args)
{
var opt = CliParser.Parse<RequiredNamedArgument>(args);
Console.WriteLine(opt.CurrentDate);
var result = CliParser.Parse<RequiredNamedArgument>(args);
result.Handle(
opt => Console.WriteLine(opt.CurrentDate),
t => { },
e => { });
// >>>
}

Expand Down Expand Up @@ -289,8 +295,11 @@ public class OptVerb

public static void DoVerb()
{
var opt = CliParser.Parse<OptsWithVerb>("download --delete".Split());
Console.WriteLine(opt.Download.Delete);
var result = CliParser.Parse<OptsWithVerb>("download --delete".Split());
result.Handle(
opt => Console.WriteLine(opt.Download.Delete),
t => { },
e => { });
}

public class OptsWithPwMasking
Expand All @@ -302,8 +311,11 @@ public class OptsWithPwMasking

public static void DoPwMasking()
{
var opt = CliParser.Parse<OptsWithPwMasking>("-p".Split());
Console.WriteLine(opt.Password);
var result = CliParser.Parse<OptsWithPwMasking>("-p".Split());
result.Handle(
opt => Console.WriteLine(opt.Password),
t => { },
e => { });
}

public class OptsWithPwMaskingAndPositional
Expand All @@ -318,8 +330,11 @@ public class OptsWithPwMaskingAndPositional

public static void DoPwMaskingAndPositional()
{
var opt = CliParser.Parse<OptsWithPwMaskingAndPositional>("-p - test".Split());
Console.WriteLine(opt.Password);
var result = CliParser.Parse<OptsWithPwMaskingAndPositional>("-p - test".Split());
result.Handle(
opt => Console.WriteLine(opt.Password),
t => { },
e => { });
}

public enum Color
Expand Down Expand Up @@ -360,9 +375,16 @@ internal class StaticEnumListOptions

public static void ParseStaticEnumList()
{
var options = CliParser.Parse<StaticEnumListOptions>("-c Red".Split());
foreach (var color in options.Colors)
Console.WriteLine("Color: {0}", color);
var result = CliParser.Parse<StaticEnumListOptions>("-c Red".Split());
result.Handle(
opt =>
{
foreach (var color in opt.Colors)
Console.WriteLine("Color: {0}", color);
},
t => { },
e => { });

}


Expand Down
69 changes: 48 additions & 21 deletions clipr.UnitTests/ArgumentIntegrityUnitTest.cs
@@ -1,9 +1,7 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using Microsoft.VisualStudio.TestTools.UnitTesting;

using AggregateException = clipr.Utils.AggregateException;
using System.Linq;
using System.Text;

// ReSharper disable ObjectCreationAsStatement
// ReSharper disable UseObjectOrCollectionInitializer
Expand All @@ -27,10 +25,28 @@ internal class DuplicateArgumentWhenCaseInsensitive
[TestMethod]
public void ParseArgument_WithArgumentsOfDifferingCaseWhenCaseSensitive_ParsesArguments()
{
var opt = CliParser.Parse<DuplicateArgumentWhenCaseInsensitive>(
var result = CliParser.Parse<DuplicateArgumentWhenCaseInsensitive>(
"-n tim -N robert".Split());
Assert.AreEqual("tim", opt.Name);
Assert.AreEqual("robert", opt.Neighbor);
result.Handle(
opt =>
{
Assert.AreEqual("tim", opt.Name);
Assert.AreEqual("robert", opt.Neighbor);
},
trig =>
{
Assert.Fail(trig.ToString());
},
errs =>
{
var sb = new StringBuilder();
foreach (var err in errs)
{
sb.AppendLine(err.ToString());
}
Assert.Fail(sb.ToString());
});

}

[TestMethod]
Expand Down Expand Up @@ -467,8 +483,10 @@ public void Version_WithInvalidShortName_ThrowsException()
var help = new Usage.AutomaticHelpGenerator<object>();
//help.Version.ShortName = '.';

AssertEx.ThrowsAggregateContaining<ArgumentIntegrityException>(
() => new CliParser<object>(help));
var errs = new CliParser<object>(help).ValidateAttributeConfig();
Assert.IsTrue(errs
.OfType<ArgumentIntegrityException>()
.Any());
}

[TestMethod]
Expand All @@ -478,10 +496,10 @@ public void Version_WithInvalidShortNameAsDigit_ThrowsException()
var help = new Usage.AutomaticHelpGenerator<object>();
//help.Version.ShortName = '1';

AssertEx.ThrowsAggregateContaining<ArgumentIntegrityException>(() =>
{
new CliParser<object>(help);
});
var errs = new CliParser<object>(help).ValidateAttributeConfig();
Assert.IsTrue(errs
.OfType<ArgumentIntegrityException>()
.Any());
}

#endregion
Expand All @@ -492,10 +510,13 @@ public void Version_WithInvalidShortNameAsDigit_ThrowsException()
[Ignore]
public void Version_WithInvalidLongName_ThrowsException()
{
// TODO better way to add/remove triggers
var help = new Usage.AutomaticHelpGenerator<object>();
//help.Version.LongName = "no.thing";
AssertEx.ThrowsAggregateContaining<ArgumentIntegrityException>(() =>
new CliParser<object>(help));
var errs = new CliParser<object>(help).ValidateAttributeConfig();
Assert.IsTrue(errs
.OfType<ArgumentIntegrityException>()
.Any());
}

[TestMethod]
Expand All @@ -504,8 +525,10 @@ public void Version_WithInvalidLongNameLength_ThrowsException()
{
var help = new Usage.AutomaticHelpGenerator<object>();
//help.Version.LongName = "n";
AssertEx.ThrowsAggregateContaining<ArgumentIntegrityException>(() =>
new CliParser<object>(help));
var errs = new CliParser<object>(help).ValidateAttributeConfig();
Assert.IsTrue(errs
.OfType<ArgumentIntegrityException>()
.Any());
}

[TestMethod]
Expand All @@ -515,8 +538,10 @@ public void Version_WithInvalidLongNameEndingWithDash_ThrowsException()
var help = new Usage.AutomaticHelpGenerator<object>();
//help.Version.LongName = "none-";

AssertEx.ThrowsAggregateContaining<ArgumentIntegrityException>(() =>
new CliParser<object>(help));
var errs = new CliParser<object>(help).ValidateAttributeConfig();
Assert.IsTrue(errs
.OfType<ArgumentIntegrityException>()
.Any());
}

[TestMethod]
Expand All @@ -526,8 +551,10 @@ public void Version_WithInvalidLongNameBeginningWithDigit_ThrowsException()
var help = new Usage.AutomaticHelpGenerator<object>();
//help.Version.LongName = "1none";

AssertEx.ThrowsAggregateContaining<ArgumentIntegrityException>(() =>
new CliParser<object>(help));
var errs = new CliParser<object>(help).ValidateAttributeConfig();
Assert.IsTrue(errs
.OfType<ArgumentIntegrityException>()
.Any());
}

#endregion
Expand Down

0 comments on commit 4fcc9e1

Please sign in to comment.