diff --git a/src/CommandLine/BaseAttribute.cs b/src/CommandLine/BaseAttribute.cs index be0a3826..83379bb2 100644 --- a/src/CommandLine/BaseAttribute.cs +++ b/src/CommandLine/BaseAttribute.cs @@ -12,6 +12,7 @@ public abstract class BaseAttribute : Attribute private int min; private int max; private object @default; + private string env; private Infrastructure.LocalizableAttributeProperty helpText; private string metaValue; private Type resourceType; @@ -87,6 +88,18 @@ public object Default } } + /// + /// Gets or sets mapped property env value. + /// + public string Env + { + get { return env; } + set + { + env = value; + } + } + /// /// Gets or sets a short description of this command line option. Usually a sentence summary. /// diff --git a/src/CommandLine/Core/InstanceBuilder.cs b/src/CommandLine/Core/InstanceBuilder.cs index dce377f1..438ad611 100644 --- a/src/CommandLine/Core/InstanceBuilder.cs +++ b/src/CommandLine/Core/InstanceBuilder.cs @@ -88,7 +88,7 @@ public static ParserResult Build( T instance; if(typeInfo.IsMutable() == true) { - instance = BuildMutable(factory, specPropsWithValue, setPropertyErrors); + instance = BuildMutable(factory, specPropsWithValue, setPropertyErrors, ignoreValueCase, parsingCulture); } else { @@ -126,7 +126,12 @@ public static ParserResult Build( return result; } - private static T BuildMutable(Maybe> factory, IEnumerable specPropsWithValue, List setPropertyErrors ) + private static T BuildMutable( + Maybe> factory, + IEnumerable specPropsWithValue, + List setPropertyErrors, + bool ignoreValueCase, + CultureInfo parsingCulture) { var mutable = factory.MapValueOrDefault(f => f(), () => Activator.CreateInstance()); @@ -141,7 +146,27 @@ private static T BuildMutable(Maybe> factory, IEnumerable sp.Value.IsNothing() && sp.Specification.DefaultValue.IsJust(), + sp => sp.Value.IsNothing() && sp.Specification.Env.Map(Environment.GetEnvironmentVariable).Map(n => !(n is null)).GetValueOrDefault(false), + sp => sp.Specification.Env + .Map(Environment.GetEnvironmentVariable) + .Bind(v => + { + var isSequence = sp.Specification.TargetType == TargetType.Sequence; + if (isSequence) + { + throw new Exception($"Sequences not supported for options with \"Env\" as is the case with {sp.Property.Name}"); + } + return TypeConverter + .ChangeType(new string[] { v }, sp.Specification.ConversionType, true, parsingCulture, ignoreValueCase); + }) + .FromJustOrFail() + ) + ); + + setPropertyErrors.AddRange( + mutable.SetProperties( + specPropsWithValue, + sp => sp.Value.IsNothing() && sp.Specification.DefaultValue.IsJust() && sp.Specification.Env.Map(Environment.GetEnvironmentVariable).Map(n => n is null).GetValueOrDefault(true), sp => sp.Specification.DefaultValue.FromJustOrFail() ) ); diff --git a/src/CommandLine/Core/OptionSpecification.cs b/src/CommandLine/Core/OptionSpecification.cs index 77e7977f..3bfd4700 100644 --- a/src/CommandLine/Core/OptionSpecification.cs +++ b/src/CommandLine/Core/OptionSpecification.cs @@ -16,10 +16,10 @@ sealed class OptionSpecification : Specification private readonly string group; public OptionSpecification(string shortName, string longName, bool required, string setName, Maybe min, Maybe max, - char separator, Maybe defaultValue, string helpText, string metaValue, IEnumerable enumValues, + char separator, Maybe defaultValue, Maybe env, string helpText, string metaValue, IEnumerable enumValues, Type conversionType, TargetType targetType, string group, bool hidden = false) : base(SpecificationType.Option, - required, min, max, defaultValue, helpText, metaValue, enumValues, conversionType, targetType, hidden) + required, min, max, defaultValue, env, helpText, metaValue, enumValues, conversionType, targetType, hidden) { this.shortName = shortName; this.longName = longName; @@ -39,6 +39,7 @@ public static OptionSpecification FromAttribute(OptionAttribute attribute, Type attribute.Max == -1 ? Maybe.Nothing() : Maybe.Just(attribute.Max), attribute.Separator, attribute.Default.ToMaybe(), + attribute.Env.ToMaybe(), attribute.HelpText, attribute.MetaValue, enumValues, @@ -51,7 +52,7 @@ public static OptionSpecification FromAttribute(OptionAttribute attribute, Type public static OptionSpecification NewSwitch(string shortName, string longName, bool required, string helpText, string metaValue, bool hidden = false) { return new OptionSpecification(shortName, longName, required, string.Empty, Maybe.Nothing(), Maybe.Nothing(), - '\0', Maybe.Nothing(), helpText, metaValue, Enumerable.Empty(), typeof(bool), TargetType.Switch, string.Empty, hidden); + '\0', Maybe.Nothing(), Maybe.Nothing(), helpText, metaValue, Enumerable.Empty(), typeof(bool), TargetType.Switch, string.Empty, hidden); } public string ShortName diff --git a/src/CommandLine/Core/Specification.cs b/src/CommandLine/Core/Specification.cs index b95b998c..8127d44c 100644 --- a/src/CommandLine/Core/Specification.cs +++ b/src/CommandLine/Core/Specification.cs @@ -30,6 +30,7 @@ abstract class Specification private readonly Maybe min; private readonly Maybe max; private readonly Maybe defaultValue; + private readonly Maybe env; private readonly string helpText; private readonly string metaValue; private readonly IEnumerable enumValues; @@ -38,7 +39,7 @@ abstract class Specification private readonly TargetType targetType; protected Specification(SpecificationType tag, bool required, Maybe min, Maybe max, - Maybe defaultValue, string helpText, string metaValue, IEnumerable enumValues, + Maybe defaultValue, Maybe env, string helpText, string metaValue, IEnumerable enumValues, Type conversionType, TargetType targetType, bool hidden = false) { this.tag = tag; @@ -46,6 +47,7 @@ protected Specification(SpecificationType tag, bool required, Maybe min, Ma this.min = min; this.max = max; this.defaultValue = defaultValue; + this.env = env; this.conversionType = conversionType; this.targetType = targetType; this.helpText = helpText; @@ -54,7 +56,7 @@ protected Specification(SpecificationType tag, bool required, Maybe min, Ma this.hidden = hidden; } - public SpecificationType Tag + public SpecificationType Tag { get { return tag; } } @@ -79,6 +81,12 @@ public Maybe DefaultValue get { return defaultValue; } } + public Maybe Env + { + get { return env; } + } + + public string HelpText { get { return helpText; } @@ -110,13 +118,13 @@ public bool Hidden } public static Specification FromProperty(PropertyInfo property) - { + { var attrs = property.GetCustomAttributes(true); var oa = attrs.OfType(); if (oa.Count() == 1) { var spec = OptionSpecification.FromAttribute(oa.Single(), property.PropertyType, - ReflectionHelper.GetNamesOfEnum(property.PropertyType)); + ReflectionHelper.GetNamesOfEnum(property.PropertyType)); if (spec.ShortName.Length == 0 && spec.LongName.Length == 0) { diff --git a/src/CommandLine/Core/SpecificationExtensions.cs b/src/CommandLine/Core/SpecificationExtensions.cs index e223e987..1b577915 100644 --- a/src/CommandLine/Core/SpecificationExtensions.cs +++ b/src/CommandLine/Core/SpecificationExtensions.cs @@ -29,6 +29,7 @@ public static OptionSpecification WithLongName(this OptionSpecification specific specification.Max, specification.Separator, specification.DefaultValue, + specification.Env, specification.HelpText, specification.MetaValue, specification.EnumValues, diff --git a/src/CommandLine/Core/ValueSpecification.cs b/src/CommandLine/Core/ValueSpecification.cs index bd90252e..647850b3 100644 --- a/src/CommandLine/Core/ValueSpecification.cs +++ b/src/CommandLine/Core/ValueSpecification.cs @@ -11,10 +11,10 @@ sealed class ValueSpecification : Specification private readonly int index; private readonly string metaName; - public ValueSpecification(int index, string metaName, bool required, Maybe min, Maybe max, Maybe defaultValue, + public ValueSpecification(int index, string metaName, bool required, Maybe min, Maybe max, Maybe defaultValue, Maybe env, string helpText, string metaValue, IEnumerable enumValues, Type conversionType, TargetType targetType, bool hidden = false) - : base(SpecificationType.Value, required, min, max, defaultValue, helpText, metaValue, enumValues, conversionType, targetType, hidden) + : base(SpecificationType.Value, required, min, max, defaultValue, env, helpText, metaValue, enumValues, conversionType, targetType, hidden) { this.index = index; this.metaName = metaName; @@ -29,6 +29,7 @@ public static ValueSpecification FromAttribute(ValueAttribute attribute, Type co attribute.Min == -1 ? Maybe.Nothing() : Maybe.Just(attribute.Min), attribute.Max == -1 ? Maybe.Nothing() : Maybe.Just(attribute.Max), attribute.Default.ToMaybe(), + attribute.Env.ToMaybe(), attribute.HelpText, attribute.MetaValue, enumValues, diff --git a/src/CommandLine/Infrastructure/StringExtensions.cs b/src/CommandLine/Infrastructure/StringExtensions.cs index 7bfab66a..539b29a3 100644 --- a/src/CommandLine/Infrastructure/StringExtensions.cs +++ b/src/CommandLine/Infrastructure/StringExtensions.cs @@ -66,12 +66,14 @@ public static string JoinTo(this string value, params string[] others) public static bool IsBooleanString(this string value) { return value.Equals("true", StringComparison.OrdinalIgnoreCase) - || value.Equals("false", StringComparison.OrdinalIgnoreCase); + || value.Equals("false", StringComparison.OrdinalIgnoreCase) + || value.Equals("1", StringComparison.OrdinalIgnoreCase) + || value.Equals("0", StringComparison.OrdinalIgnoreCase); } public static bool ToBoolean(this string value) { - return value.Equals("true", StringComparison.OrdinalIgnoreCase); + return value.Equals("true", StringComparison.OrdinalIgnoreCase) || value.Equals("1", StringComparison.OrdinalIgnoreCase); } } -} \ No newline at end of file +} diff --git a/tests/CommandLine.Tests/Fakes/Simple_Options_With_Env.cs b/tests/CommandLine.Tests/Fakes/Simple_Options_With_Env.cs new file mode 100644 index 00000000..9218b2e0 --- /dev/null +++ b/tests/CommandLine.Tests/Fakes/Simple_Options_With_Env.cs @@ -0,0 +1,28 @@ +// Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information. + +using System.Collections.Generic; + +namespace CommandLine.Tests.Fakes +{ + class Simple_Options_With_Env + { + [Option('s', Default = "", Env = "StringValue")] + public string StringValue { get; set; } + + [Option("bvff", Env = "BoolValueFullFalse", HelpText = "Define a boolean or switch value here.")] + public bool BoolValueFullFalse { get; set; } + [Option("bvft", Env = "BoolValueFullTrue", HelpText = "Define a boolean or switch value here.")] + public bool BoolValueFullTrue { get; set; } + [Option("bvst", Env = "BoolValueShortTrue", HelpText = "Define a boolean or switch value here.")] + public bool BoolValueShortTrue { get; set; } + [Option("bvsf", Env = "BoolValueShortFalse", HelpText = "Define a boolean or switch value here.")] + public bool BoolValueShortFalse { get; set; } + + + [Option('l', Default = 1, Env = "LongValue")] + public long LongValue { get; set; } + + [Option('i', Default = 2, Env = "IntValue")] + public long IntValue { get; set; } + } +} diff --git a/tests/CommandLine.Tests/Unit/Core/NameLookupTests.cs b/tests/CommandLine.Tests/Unit/Core/NameLookupTests.cs index f009c49e..22f07e75 100644 --- a/tests/CommandLine.Tests/Unit/Core/NameLookupTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/NameLookupTests.cs @@ -17,7 +17,7 @@ public void Lookup_name_of_sequence_option_with_separator() // Fixture setup var expected = Maybe.Just("."); var specs = new[] { new OptionSpecification(string.Empty, "string-seq", - false, string.Empty, Maybe.Nothing(), Maybe.Nothing(), '.', null, string.Empty, string.Empty, new List(), typeof(IEnumerable), TargetType.Sequence, string.Empty)}; + false, string.Empty, Maybe.Nothing(), Maybe.Nothing(), '.', null, null, string.Empty, string.Empty, new List(), typeof(IEnumerable), TargetType.Sequence, string.Empty)}; // Exercize system var result = NameLookup.HavingSeparator("string-seq", specs, StringComparer.Ordinal); @@ -35,7 +35,7 @@ public void Get_name_from_option_specification() // Fixture setup var expected = new NameInfo(ShortName, LongName); - var spec = new OptionSpecification(ShortName, LongName, false, string.Empty, Maybe.Nothing(), Maybe.Nothing(), '.', null, string.Empty, string.Empty, new List(), typeof(IEnumerable), TargetType.Sequence, string.Empty); + var spec = new OptionSpecification(ShortName, LongName, false, string.Empty, Maybe.Nothing(), Maybe.Nothing(), '.', null, null, string.Empty, string.Empty, new List(), typeof(IEnumerable), TargetType.Sequence, string.Empty); // Exercize system var result = spec.FromOptionSpecification(); diff --git a/tests/CommandLine.Tests/Unit/Core/OptionMapperTests.cs b/tests/CommandLine.Tests/Unit/Core/OptionMapperTests.cs index b2219683..19a6f8b1 100644 --- a/tests/CommandLine.Tests/Unit/Core/OptionMapperTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/OptionMapperTests.cs @@ -28,7 +28,7 @@ public void Map_boolean_switch_creates_boolean_value() var specProps = new[] { SpecificationProperty.Create( - new OptionSpecification("x", string.Empty, false, string.Empty, Maybe.Nothing(), Maybe.Nothing(), '\0', Maybe.Nothing(), string.Empty, string.Empty, new List(), typeof(bool), TargetType.Switch, string.Empty), + new OptionSpecification("x", string.Empty, false, string.Empty, Maybe.Nothing(), Maybe.Nothing(), '\0', Maybe.Nothing(), Maybe.Nothing(), string.Empty, string.Empty, new List(), typeof(bool), TargetType.Switch, string.Empty), typeof(Simple_Options).GetProperties().Single(p => p.Name.Equals("BoolValue", StringComparison.Ordinal)), Maybe.Nothing()) }; diff --git a/tests/CommandLine.Tests/Unit/Core/TokenPartitionerTests.cs b/tests/CommandLine.Tests/Unit/Core/TokenPartitionerTests.cs index 20006e59..248ccd54 100644 --- a/tests/CommandLine.Tests/Unit/Core/TokenPartitionerTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/TokenPartitionerTests.cs @@ -21,8 +21,8 @@ public void Partition_sequence_returns_sequence() }; var specs = new[] { - new OptionSpecification(string.Empty, "stringvalue", false, string.Empty, Maybe.Nothing(), Maybe.Nothing(), '\0', null, string.Empty, string.Empty, new List(), typeof(string), TargetType.Scalar, string.Empty), - new OptionSpecification("i", string.Empty, false, string.Empty, Maybe.Just(3), Maybe.Just(4), '\0', null, string.Empty, string.Empty, new List(), typeof(IEnumerable), TargetType.Sequence, string.Empty) + new OptionSpecification(string.Empty, "stringvalue", false, string.Empty, Maybe.Nothing(), Maybe.Nothing(), '\0', null, null, string.Empty, string.Empty, new List(), typeof(string), TargetType.Scalar, string.Empty), + new OptionSpecification("i", string.Empty, false, string.Empty, Maybe.Just(3), Maybe.Just(4), '\0', null, null, string.Empty, string.Empty, new List(), typeof(IEnumerable), TargetType.Sequence, string.Empty) }; // Exercize system @@ -48,8 +48,8 @@ public void Partition_sequence_returns_sequence_with_duplicates() }; var specs = new[] { - new OptionSpecification(string.Empty, "stringvalue", false, string.Empty, Maybe.Nothing(), Maybe.Nothing(), '\0', null, string.Empty, string.Empty, new List(), typeof(string), TargetType.Scalar, string.Empty), - new OptionSpecification("i", string.Empty, false, string.Empty, Maybe.Just(3), Maybe.Just(4), '\0', null, string.Empty, string.Empty, new List(), typeof(IEnumerable), TargetType.Sequence, string.Empty) + new OptionSpecification(string.Empty, "stringvalue", false, string.Empty, Maybe.Nothing(), Maybe.Nothing(), '\0', null, null, string.Empty, string.Empty, new List(), typeof(string), TargetType.Scalar, string.Empty), + new OptionSpecification("i", string.Empty, false, string.Empty, Maybe.Just(3), Maybe.Just(4), '\0', null, null, string.Empty, string.Empty, new List(), typeof(IEnumerable), TargetType.Sequence, string.Empty) }; // Exercize system diff --git a/tests/CommandLine.Tests/Unit/Core/TokenizerTests.cs b/tests/CommandLine.Tests/Unit/Core/TokenizerTests.cs index f14eea51..115470ac 100644 --- a/tests/CommandLine.Tests/Unit/Core/TokenizerTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/TokenizerTests.cs @@ -21,7 +21,7 @@ public void Explode_scalar_with_separator_in_odd_args_input_returns_sequence() var expectedTokens = new[] { Token.Name("i"), Token.Value("10"), Token.Name("string-seq"), Token.Value("aaa"), Token.Value("bb"), Token.Value("cccc"), Token.Name("switch") }; var specs = new[] { new OptionSpecification(string.Empty, "string-seq", - false, string.Empty, Maybe.Nothing(), Maybe.Nothing(), ',', null, string.Empty, string.Empty, new List(), typeof(IEnumerable), TargetType.Sequence, string.Empty)}; + false, string.Empty, Maybe.Nothing(), Maybe.Nothing(), ',', null, null, string.Empty, string.Empty, new List(), typeof(IEnumerable), TargetType.Sequence, string.Empty)}; // Exercize system var result = @@ -44,7 +44,7 @@ public void Explode_scalar_with_separator_in_even_args_input_returns_sequence() var expectedTokens = new[] { Token.Name("x"), Token.Name("string-seq"), Token.Value("aaa"), Token.Value("bb"), Token.Value("cccc"), Token.Name("switch") }; var specs = new[] { new OptionSpecification(string.Empty, "string-seq", - false, string.Empty, Maybe.Nothing(), Maybe.Nothing(), ',', null, string.Empty, string.Empty, new List(), typeof(IEnumerable), TargetType.Sequence, string.Empty)}; + false, string.Empty, Maybe.Nothing(), Maybe.Nothing(), ',', null, null, string.Empty, string.Empty, new List(), typeof(IEnumerable), TargetType.Sequence, string.Empty)}; // Exercize system var result = diff --git a/tests/CommandLine.Tests/Unit/Core/TypeConverterTests.cs b/tests/CommandLine.Tests/Unit/Core/TypeConverterTests.cs index c62a7836..593dd4eb 100644 --- a/tests/CommandLine.Tests/Unit/Core/TypeConverterTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/TypeConverterTests.cs @@ -1,124 +1,124 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using Xunit; -using FluentAssertions; -using CSharpx; -using CommandLine.Core; - -namespace CommandLine.Tests.Unit.Core -{ - public class TypeConverterTests - { - enum TestEnum - { - ValueA = 1, - ValueB = 2 - } - - [Flags] - enum TestFlagEnum - { - ValueA = 0x1, - ValueB = 0x2 - } - - [Theory] - [MemberData(nameof(ChangeType_scalars_source))] - public void ChangeType_scalars(string testValue, Type destinationType, bool expectFail, object expectedResult) - { - Maybe result = TypeConverter.ChangeType(new[] {testValue}, destinationType, true, CultureInfo.InvariantCulture, true); - - if (expectFail) - { - result.MatchNothing().Should().BeTrue("should fail parsing"); - } - else - { - result.MatchJust(out object matchedValue).Should().BeTrue("should parse successfully"); - Assert.Equal(matchedValue, expectedResult); - } - } - - public static IEnumerable ChangeType_scalars_source - { - get - { - return new[] - { - new object[] {"1", typeof (int), false, 1}, - new object[] {"0", typeof (int), false, 0}, - new object[] {"-1", typeof (int), false, -1}, - new object[] {"abcd", typeof (int), true, null}, - new object[] {"1.0", typeof (int), true, null}, - new object[] {int.MaxValue.ToString(), typeof (int), false, int.MaxValue}, - new object[] {int.MinValue.ToString(), typeof (int), false, int.MinValue}, - new object[] {((long) int.MaxValue + 1).ToString(), typeof (int), true, null}, - new object[] {((long) int.MinValue - 1).ToString(), typeof (int), true, null}, - - new object[] {"1", typeof (uint), false, (uint) 1}, - // new object[] {"0", typeof (uint), false, (uint) 0}, //cause warning: Skipping test case with duplicate ID - // new object[] {"-1", typeof (uint), true, null}, //cause warning: Skipping test case with duplicate ID - new object[] {uint.MaxValue.ToString(), typeof (uint), false, uint.MaxValue}, - new object[] {uint.MinValue.ToString(), typeof (uint), false, uint.MinValue}, - new object[] {((long) uint.MaxValue + 1).ToString(), typeof (uint), true, null}, - new object[] {((long) uint.MinValue - 1).ToString(), typeof (uint), true, null}, - - new object[] {"true", typeof (bool), false, true}, - new object[] {"True", typeof (bool), false, true}, - new object[] {"TRUE", typeof (bool), false, true}, - new object[] {"false", typeof (bool), false, false}, - new object[] {"False", typeof (bool), false, false}, - new object[] {"FALSE", typeof (bool), false, false}, - new object[] {"abcd", typeof (bool), true, null}, - new object[] {"0", typeof (bool), true, null}, - new object[] {"1", typeof (bool), true, null}, - - new object[] {"1.0", typeof (float), false, 1.0f}, - new object[] {"0.0", typeof (float), false, 0.0f}, - new object[] {"-1.0", typeof (float), false, -1.0f}, - new object[] {"abcd", typeof (float), true, null}, - - new object[] {"1.0", typeof (double), false, 1.0}, - new object[] {"0.0", typeof (double), false, 0.0}, - new object[] {"-1.0", typeof (double), false, -1.0}, - new object[] {"abcd", typeof (double), true, null}, - - new object[] {"1.0", typeof (decimal), false, 1.0m}, - new object[] {"0.0", typeof (decimal), false, 0.0m}, - new object[] {"-1.0", typeof (decimal), false, -1.0m}, - new object[] {"-1.123456", typeof (decimal), false, -1.123456m}, - new object[] {"abcd", typeof (decimal), true, null}, - - new object[] {"", typeof (string), false, ""}, - new object[] {"abcd", typeof (string), false, "abcd"}, - - new object[] {"ValueA", typeof (TestEnum), false, TestEnum.ValueA}, - new object[] {"VALUEA", typeof (TestEnum), false, TestEnum.ValueA}, - new object[] {"ValueB", typeof(TestEnum), false, TestEnum.ValueB}, - new object[] {((int) TestEnum.ValueA).ToString(), typeof (TestEnum), false, TestEnum.ValueA}, - new object[] {((int) TestEnum.ValueB).ToString(), typeof (TestEnum), false, TestEnum.ValueB}, - new object[] {((int) TestEnum.ValueB + 1).ToString(), typeof (TestEnum), true, null}, - new object[] {((int) TestEnum.ValueA - 1).ToString(), typeof (TestEnum), true, null}, - - new object[] {"ValueA", typeof (TestFlagEnum), false, TestFlagEnum.ValueA}, - new object[] {"VALUEA", typeof (TestFlagEnum), false, TestFlagEnum.ValueA}, - new object[] {"ValueB", typeof(TestFlagEnum), false, TestFlagEnum.ValueB}, - new object[] {"ValueA,ValueB", typeof (TestFlagEnum), false, TestFlagEnum.ValueA | TestFlagEnum.ValueB}, - new object[] {"ValueA, ValueB", typeof (TestFlagEnum), false, TestFlagEnum.ValueA | TestFlagEnum.ValueB}, - new object[] {"VALUEA,ValueB", typeof (TestFlagEnum), false, TestFlagEnum.ValueA | TestFlagEnum.ValueB}, - new object[] {((int) TestFlagEnum.ValueA).ToString(), typeof (TestFlagEnum), false, TestFlagEnum.ValueA}, - new object[] {((int) TestFlagEnum.ValueB).ToString(), typeof (TestFlagEnum), false, TestFlagEnum.ValueB}, - new object[] {((int) (TestFlagEnum.ValueA | TestFlagEnum.ValueB)).ToString(), typeof (TestFlagEnum), false, TestFlagEnum.ValueA | TestFlagEnum.ValueB}, - new object[] {((int) TestFlagEnum.ValueB + 2).ToString(), typeof (TestFlagEnum), true, null}, - new object[] {((int) TestFlagEnum.ValueA - 1).ToString(), typeof (TestFlagEnum), true, null}, - - - // Failed before #339 - new object[] {"false", typeof (int), true, 0}, - new object[] {"true", typeof (int), true, 0} - }; - } - } - } -} +using System; +using System.Collections.Generic; +using System.Globalization; +using Xunit; +using FluentAssertions; +using CSharpx; +using CommandLine.Core; + +namespace CommandLine.Tests.Unit.Core +{ + public class TypeConverterTests + { + enum TestEnum + { + ValueA = 1, + ValueB = 2 + } + + [Flags] + enum TestFlagEnum + { + ValueA = 0x1, + ValueB = 0x2 + } + + [Theory] + [MemberData(nameof(ChangeType_scalars_source))] + public void ChangeType_scalars(string testValue, Type destinationType, bool expectFail, object expectedResult) + { + Maybe result = TypeConverter.ChangeType(new[] { testValue }, destinationType, true, CultureInfo.InvariantCulture, true); + + if (expectFail) + { + result.MatchNothing().Should().BeTrue("should fail parsing"); + } + else + { + result.MatchJust(out object matchedValue).Should().BeTrue("should parse successfully"); + Assert.Equal(matchedValue, expectedResult); + } + } + + public static IEnumerable ChangeType_scalars_source + { + get + { + return new[] + { + new object[] {"1", typeof (int), false, 1}, + new object[] {"0", typeof (int), false, 0}, + new object[] {"-1", typeof (int), false, -1}, + new object[] {"abcd", typeof (int), true, null}, + new object[] {"1.0", typeof (int), true, null}, + new object[] {int.MaxValue.ToString(), typeof (int), false, int.MaxValue}, + new object[] {int.MinValue.ToString(), typeof (int), false, int.MinValue}, + new object[] {((long) int.MaxValue + 1).ToString(), typeof (int), true, null}, + new object[] {((long) int.MinValue - 1).ToString(), typeof (int), true, null}, + + new object[] {"1", typeof (uint), false, (uint) 1}, + // new object[] {"0", typeof (uint), false, (uint) 0}, //cause warning: Skipping test case with duplicate ID + // new object[] {"-1", typeof (uint), true, null}, //cause warning: Skipping test case with duplicate ID + new object[] {uint.MaxValue.ToString(), typeof (uint), false, uint.MaxValue}, + new object[] {uint.MinValue.ToString(), typeof (uint), false, uint.MinValue}, + new object[] {((long) uint.MaxValue + 1).ToString(), typeof (uint), true, null}, + new object[] {((long) uint.MinValue - 1).ToString(), typeof (uint), true, null}, + + new object[] {"true", typeof (bool), false, true}, + new object[] {"True", typeof (bool), false, true}, + new object[] {"TRUE", typeof (bool), false, true}, + new object[] {"false", typeof (bool), false, false}, + new object[] {"False", typeof (bool), false, false}, + new object[] {"FALSE", typeof (bool), false, false}, + new object[] {"abcd", typeof (bool), true, null}, + new object[] {"0", typeof (bool), false, false}, + new object[] {"1", typeof (bool), false, true}, + + new object[] {"1.0", typeof (float), false, 1.0f}, + new object[] {"0.0", typeof (float), false, 0.0f}, + new object[] {"-1.0", typeof (float), false, -1.0f}, + new object[] {"abcd", typeof (float), true, null}, + + new object[] {"1.0", typeof (double), false, 1.0}, + new object[] {"0.0", typeof (double), false, 0.0}, + new object[] {"-1.0", typeof (double), false, -1.0}, + new object[] {"abcd", typeof (double), true, null}, + + new object[] {"1.0", typeof (decimal), false, 1.0m}, + new object[] {"0.0", typeof (decimal), false, 0.0m}, + new object[] {"-1.0", typeof (decimal), false, -1.0m}, + new object[] {"-1.123456", typeof (decimal), false, -1.123456m}, + new object[] {"abcd", typeof (decimal), true, null}, + + new object[] {"", typeof (string), false, ""}, + new object[] {"abcd", typeof (string), false, "abcd"}, + + new object[] {"ValueA", typeof (TestEnum), false, TestEnum.ValueA}, + new object[] {"VALUEA", typeof (TestEnum), false, TestEnum.ValueA}, + new object[] {"ValueB", typeof(TestEnum), false, TestEnum.ValueB}, + new object[] {((int) TestEnum.ValueA).ToString(), typeof (TestEnum), false, TestEnum.ValueA}, + new object[] {((int) TestEnum.ValueB).ToString(), typeof (TestEnum), false, TestEnum.ValueB}, + new object[] {((int) TestEnum.ValueB + 1).ToString(), typeof (TestEnum), true, null}, + new object[] {((int) TestEnum.ValueA - 1).ToString(), typeof (TestEnum), true, null}, + + new object[] {"ValueA", typeof (TestFlagEnum), false, TestFlagEnum.ValueA}, + new object[] {"VALUEA", typeof (TestFlagEnum), false, TestFlagEnum.ValueA}, + new object[] {"ValueB", typeof(TestFlagEnum), false, TestFlagEnum.ValueB}, + new object[] {"ValueA,ValueB", typeof (TestFlagEnum), false, TestFlagEnum.ValueA | TestFlagEnum.ValueB}, + new object[] {"ValueA, ValueB", typeof (TestFlagEnum), false, TestFlagEnum.ValueA | TestFlagEnum.ValueB}, + new object[] {"VALUEA,ValueB", typeof (TestFlagEnum), false, TestFlagEnum.ValueA | TestFlagEnum.ValueB}, + new object[] {((int) TestFlagEnum.ValueA).ToString(), typeof (TestFlagEnum), false, TestFlagEnum.ValueA}, + new object[] {((int) TestFlagEnum.ValueB).ToString(), typeof (TestFlagEnum), false, TestFlagEnum.ValueB}, + new object[] {((int) (TestFlagEnum.ValueA | TestFlagEnum.ValueB)).ToString(), typeof (TestFlagEnum), false, TestFlagEnum.ValueA | TestFlagEnum.ValueB}, + new object[] {((int) TestFlagEnum.ValueB + 2).ToString(), typeof (TestFlagEnum), true, null}, + new object[] {((int) TestFlagEnum.ValueA - 1).ToString(), typeof (TestFlagEnum), true, null}, + + + // Failed before #339 + new object[] {"false", typeof (int), true, 0}, + new object[] {"true", typeof (int), true, 0} + }; + } + } + } +} diff --git a/tests/CommandLine.Tests/Unit/ParserTests.cs b/tests/CommandLine.Tests/Unit/ParserTests.cs index bc6d77a8..e5f1e0ff 100644 --- a/tests/CommandLine.Tests/Unit/ParserTests.cs +++ b/tests/CommandLine.Tests/Unit/ParserTests.cs @@ -132,6 +132,39 @@ public void Parse_options_with_double_dash() // Teardown } + [Fact] + public void Parse_spec_with_enviroment_variables() + { + // Fixture setup + var expectedOptions = new Simple_Options_With_Env + { + StringValue = "astring", + LongValue = 20L, + IntValue = 2, + BoolValueFullFalse = false, + BoolValueFullTrue = true, + BoolValueShortTrue = true, + BoolValueShortFalse = false, + }; + var sut = new Parser(with => with.EnableDashDash = true); + + // Exercize system + Environment.SetEnvironmentVariable("StringValue", "astring"); + Environment.SetEnvironmentVariable("LongValue", "20"); + Environment.SetEnvironmentVariable("IntValue", null); + Environment.SetEnvironmentVariable("BoolValueFullFalse", "false"); + Environment.SetEnvironmentVariable("BoolValueFullTrue", "true"); + Environment.SetEnvironmentVariable("BoolValueShortTrue", "1"); + Environment.SetEnvironmentVariable("BoolValueShortFalse", "0"); + var result = + sut.ParseArguments( + new string[0]); + + // Verify outcome + ((Parsed)result).Value.Should().BeEquivalentTo(expectedOptions); + // Teardown + } + [Fact] public void Parse_options_with_double_dash_and_option_sequence() {