Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expose value and error on parser result #634

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 28 additions & 29 deletions src/CommandLine/ParserResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace CommandLine
public sealed class TypeInfo
{
private readonly Type current;
private readonly IEnumerable<Type> choices;
private readonly IEnumerable<Type> choices;

private TypeInfo(Type current, IEnumerable<Type> choices)
{
Expand Down Expand Up @@ -64,10 +64,20 @@ public abstract class ParserResult<T>
private readonly ParserResultType tag;
private readonly TypeInfo typeInfo;

internal ParserResult(ParserResultType tag, TypeInfo typeInfo)
internal ParserResult(IEnumerable<Error> errors, TypeInfo typeInfo)
{
this.tag = tag;
this.typeInfo = typeInfo;
this.tag = ParserResultType.NotParsed;
this.typeInfo = typeInfo ?? TypeInfo.Create(typeof(T));
Errors = errors ?? new Error[0];
Value = default;
}

internal ParserResult(T value, TypeInfo typeInfo)
{
Value = value ?? throw new ArgumentNullException(nameof(value));
this.tag = ParserResultType.Parsed;
this.typeInfo = typeInfo ?? TypeInfo.Create(value.GetType());
Errors = new Error[0];
}

/// <summary>
Expand All @@ -82,6 +92,16 @@ public TypeInfo TypeInfo
{
get { return typeInfo; }
}

/// <summary>
/// Gets the instance with parsed values. If one or more errors occures, <see langword="default"/> is returned.
/// </summary>
public T Value { get; }

/// <summary>
/// Gets the sequence of parsing errors. If there are no errors, then an empty IEnumerable is returned.
/// </summary>
public IEnumerable<Error> Errors { get; }
}

/// <summary>
Expand All @@ -90,26 +110,16 @@ public TypeInfo TypeInfo
/// <typeparam name="T">The type with attributes that define the syntax of parsing rules.</typeparam>
public sealed class Parsed<T> : ParserResult<T>, IEquatable<Parsed<T>>
{
private readonly T value;

internal Parsed(T value, TypeInfo typeInfo)
: base(ParserResultType.Parsed, typeInfo)
: base(value, typeInfo)
{
this.value = value;
}

internal Parsed(T value)
: this(value, TypeInfo.Create(value.GetType()))
{
}

/// <summary>
/// Gets the instance with parsed values.
/// </summary>
public T Value
{
get { return value; }
}

/// <summary>
/// Determines whether the specified <see cref="System.Object"/> is equal to the current <see cref="System.Object"/>.
Expand All @@ -118,8 +128,7 @@ public T Value
/// <returns><value>true</value> if the specified <see cref="System.Object"/> is equal to the current <see cref="System.Object"/>; otherwise, <value>false</value>.</returns>
public override bool Equals(object obj)
{
var other = obj as Parsed<T>;
if (other != null)
if (obj is Parsed<T> other)
{
return Equals(other);
}
Expand Down Expand Up @@ -159,21 +168,12 @@ public bool Equals(Parsed<T> other)
/// <typeparam name="T">The type with attributes that define the syntax of parsing rules.</typeparam>
public sealed class NotParsed<T> : ParserResult<T>, IEquatable<NotParsed<T>>
{
private readonly IEnumerable<Error> errors;

internal NotParsed(TypeInfo typeInfo, IEnumerable<Error> errors)
: base(ParserResultType.NotParsed, typeInfo)
: base(errors, typeInfo)
{
this.errors = errors;
}

/// <summary>
/// Gets the sequence of parsing errors.
/// </summary>
public IEnumerable<Error> Errors
{
get { return errors; }
}

/// <summary>
/// Determines whether the specified <see cref="System.Object"/> is equal to the current <see cref="System.Object"/>.
Expand All @@ -182,8 +182,7 @@ public IEnumerable<Error> Errors
/// <returns><value>true</value> if the specified <see cref="System.Object"/> is equal to the current <see cref="System.Object"/>; otherwise, <value>false</value>.</returns>
public override bool Equals(object obj)
{
var other = obj as NotParsed<T>;
if (other != null)
if (obj is NotParsed<T> other)
{
return Equals(other);
}
Expand Down
108 changes: 108 additions & 0 deletions tests/CommandLine.Tests/Unit/Issue543Tests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
using System.Collections.Generic;
using System.Linq;
using Xunit;
using CommandLine.Text;

namespace CommandLine.Tests.Unit
{
//Reference: PR# 634
public class Issue543Tests
{

private const int ERROR_SUCCESS = 0;

[Fact]
public void Parser_GiveHelpArgument_ExpectSuccess()
{
var result = Parser.Default.ParseArguments<Options>(new[] { "--help" });

Assert.Equal(ParserResultType.NotParsed, result.Tag);
Assert.Null(result.Value);
Assert.NotEmpty(result.Errors);
}

[Fact]
public void Parser_GiveConnectionStringAndJobId_ExpectSuccess()
{
var result = Parser.Default.ParseArguments<Options>(new[] {
"-c", "someConnectionString",
"-j", "1234",
});

Assert.Equal(ParserResultType.Parsed, result.Tag);
Assert.NotNull(result.Value);
Assert.Empty(result.Errors);
Assert.Equal("someConnectionString", result.Value.ConnectionString);
Assert.Equal(1234, result.Value.JobId);
}

[Fact]
public void Parser_GiveVerb1_ExpectSuccess()
{
var result = Parser.Default.ParseArguments<Verb1Options, Verb2Options>(new[] {
"verb1",
"-j", "1234",
});

Assert.Equal(ParserResultType.Parsed, result.Tag);
Assert.Empty(result.Errors);
Assert.NotNull(result.Value);
Assert.NotNull(result.Value as Verb1Options);
Assert.Equal(1234, (result.Value as Verb1Options).JobId);
}

[Fact]
public void Parser_GiveVerb2_ExpectSuccess()
{
var result = Parser.Default.ParseArguments<Verb1Options, Verb2Options>(new[] {
"verb2",
"-c", "someConnectionString",
});

Assert.Equal(ParserResultType.Parsed, result.Tag);
Assert.Empty(result.Errors);
Assert.NotNull(result.Value);
Assert.NotNull(result.Value as Verb2Options);
Assert.Equal("someConnectionString", (result.Value as Verb2Options).ConnectionString);
}

// Options
internal class Options
{
[Option('c', "connectionString", Required = true, HelpText = "Texts.ExplainConnection")]
public string ConnectionString { get; set; }

[Option('j', "jobId", Required = true, HelpText = "Texts.ExplainJob")]
public int JobId { get; set; }

[Usage(ApplicationAlias = "Importer.exe")]
public static IEnumerable<Example> Examples
{
get => new[] {
new Example("Texts.ExplainExampleExecution", new Options() {
ConnectionString="Server=MyServer;Database=MyDatabase",
JobId = 5
}),
};
}
}

// Options
[Verb("verb1")]
internal class Verb1Options
{
[Option('j', "jobId", Required = false, HelpText = "Texts.ExplainJob")]
public int JobId { get; set; }
}

// Options
[Verb("verb2")]
internal class Verb2Options
{
[Option('c', "connectionString", Required = false, HelpText = "Texts.ExplainConnection")]
public string ConnectionString { get; set; }
}

}
}