diff --git a/Simple.ArgumentParser.Example/Program.cs b/Simple.ArgumentParser.Example/Program.cs index eb4bb20..63acfec 100644 --- a/Simple.ArgumentParser.Example/Program.cs +++ b/Simple.ArgumentParser.Example/Program.cs @@ -1,74 +1,67 @@ using Simple.ArgumentParser; -using Type = Simple.ArgumentParser.Type; -args = ["--key", "just", "a", "key", "--bool", "true", "--number", "42"]; +//args = ["-t", "hejsan", "-n", "3.1", "-b", "true", "-f", "-fl", "1.3" ]; -var arguments = new Parser() - .Options - .Add(name: "key", - shortName: "k", - description: "An alphanumeric value", - type: Type.Alpha, - required: true) - .Add(name: "flag", - shortName: "f", - description: "Just a simple flag", - type: Type.None, +var arguments = new ArgumentParser() + .AddAlphaOption(name: "name", + shortName: 'n', + description: "Your full name", required: false) - .Add(name: "bool", - shortName: "b", - description: "A boolean value (true/false)", - type: Type.Boolean, - required: true) - .Add(name: "a-super-long-option", - shortName: "l", - description: "This is a really long option", - type: Type.Alpha, + .AddIntegerOption(name: "age", + shortName: 'a', + description: "Your age", required: false) - .Add(name: "number", - shortName: "n", - description: "An integer value", - type: Type.Integer, - required: true) - .AddHelp() - .AddVersion() - .AddDescription("A description of the application.") - .Build() + .AddEnumerateOption(name: "sex", + shortName: 's', + description: "Enter your sex (male/female/idiot)", + ["male", "female", "idiot"], + required: false) + .AddBooleanOption(name: "happy", + shortName: 'H', + description: "If you are happy or not", + required: false) + .AddHelpOption("A description of the application.") + .AddVersionOption("1.2.3-alpha") .Parse(args); - // handle help command -if (arguments.ShowHelpRequested) +if (arguments.HelpRequested) { Console.WriteLine(arguments.HelpSection); return 1; } // handle version command -if (arguments.ShowVersionRequested) +if (arguments.VersionRequested) { Console.WriteLine(arguments.Version); return 2; } // handle invalid commands -if (arguments.InvalidCommands.Count > 0) +if (arguments.HasInvalidCommands) { - arguments.InvalidCommands.ForEach(Console.WriteLine); - return -2; + arguments.Invalid.ForEach(Console.WriteLine); } // handle missing commands -if (arguments.MissingCommands.Count > 0) +if (arguments.HasMissingCommands) +{ + arguments.Missing.ForEach(Console.WriteLine); +} + +// handle ignored commands +if (arguments.HasIgnoredCommands) { - arguments.MissingCommands.ForEach(c => Console.WriteLine($"Required command is missing: {c}")); - return -1; + Console.WriteLine("Ignored commands:"); + arguments.Ignored.ForEach(c => Console.WriteLine($"Name: {c.Name}, Type: {c.OptionType}, Value: {c.Value}")); } -arguments.ValidCommands.ForEach(c => Console.WriteLine($"Name: {c.Name}, Type: {c.Type}, Value: {c.Value}")); +// handle valid commands +if (arguments.IsValid && arguments.Any()) +{ + Console.WriteLine("Valid commands:"); + arguments.GetAll().ForEach(c => Console.WriteLine($"Name: {c.Name}, Type: {c.OptionType}, Value: {c.Value}")); +} -Console.WriteLine($"Hello, {arguments.ValidCommands.Single(c => c.Name == "name").Value}! You look " + - $"a lot older than {arguments.ValidCommands.Single(c => c.Name == "age").Value}. " + - $"Also, you are {(arguments.ValidCommands.Single(c => c.Name == "is-cool").Value == "true" ? "very" : "not very")} cool."); -Console.WriteLine($"Flag: {arguments.ValidCommands.Any(c => c.Name == "just-a-flag")}"); return 0; \ No newline at end of file diff --git a/Simple.ArgumentParser.Tests/ArgumentParserTests.cs b/Simple.ArgumentParser.Tests/ArgumentParserTests.cs new file mode 100644 index 0000000..8649022 --- /dev/null +++ b/Simple.ArgumentParser.Tests/ArgumentParserTests.cs @@ -0,0 +1,79 @@ +namespace Simple.ArgumentParser.Tests; + +public class ArgumentParserTests +{ + [Fact] + public void Parse_should_handle_expected_arguments() + { + // arrange + + string[] args = [ + "--alpha", "value", + "--boolean", "true", + "--char", "c", + "--double", "3.14", + "--enumerate", "a", + "--flag", + "--integer", "42", + "--help", + "--version" + ]; + + var subjectUnderTest = new ArgumentParser() + .AddAlphaOption("alpha", 'a', "description") + .AddBooleanOption("boolean", 'b', "description") + .AddCharOption("char", 'c', "description") + .AddDoubleOption("double", 'd', "description") + .AddEnumerateOption("enumerate", 'e', "description", ["a", "b", "c"]) + .AddFlagOption("flag", 'f', "description") + .AddIntegerOption("integer", 'i', "description") + .AddVersionOption() + .AddHelpOption(); + + // act + + var result = subjectUnderTest.Parse(args); + + // assert + + Assert.NotNull(result); + Assert.True(result.HelpRequested); + Assert.True(result.VersionRequested); + Assert.True(result.IsValid); + Assert.False(result.HasMissingCommands); + Assert.False(result.HasIgnoredCommands); + Assert.False(result.HasInvalidCommands); + Assert.NotNull(result.Version); + Assert.NotEmpty(result.Version); + Assert.NotNull(result.HelpSection); + Assert.NotEmpty(result.HelpSection); + Assert.NotNull(result.Get("alpha")); + } + + [Fact] + public void Parse_should_ignore_unexpected_arguments() + { + // arrange + + var args = new[] { "--help" }; + + var subjectUnderTest = new ArgumentParser(); + + // act + + var result = subjectUnderTest.Parse(args); + + // assert + + Assert.NotNull(result); + Assert.False(result.HelpRequested); + Assert.False(result.VersionRequested); + Assert.True(result.IsValid); + Assert.False(result.HasMissingCommands); + Assert.True(result.HasIgnoredCommands); + Assert.False(result.HasInvalidCommands); + Assert.Null(result.Version); + Assert.Null(result.HelpSection); + Assert.Null(result.Get("help")); + } +} \ No newline at end of file diff --git a/Simple.ArgumentParser.Tests/Options/AlphaOptionTests.cs b/Simple.ArgumentParser.Tests/Options/AlphaOptionTests.cs new file mode 100644 index 0000000..a697a35 --- /dev/null +++ b/Simple.ArgumentParser.Tests/Options/AlphaOptionTests.cs @@ -0,0 +1,33 @@ +using Simple.ArgumentParser.Options; + +namespace Simple.ArgumentParser.Tests.Options; + +public class AlphaOptionTests +{ + private static AlphaOption GetSubjectUnderTest(OptionSettings? settings = null) => new ( + name: "test-name", + shortName: 't', + description: "test-description", + settings: settings ?? new OptionSettings()); + + [Theory] + [InlineData("alpha")] + [InlineData("123")] + [InlineData("true")] + [InlineData(" ")] + public void ValueIsValid_should_return_true(string inlineData) + { + // arrange + + var subjectUnderTest = GetSubjectUnderTest(); + + // act + + var result = subjectUnderTest.ValueIsValid(inlineData, out var message); + + // assert + + Assert.True(result); + Assert.Empty(message); + } +} \ No newline at end of file diff --git a/Simple.ArgumentParser.Tests/Options/BooleanOptionTests.cs b/Simple.ArgumentParser.Tests/Options/BooleanOptionTests.cs new file mode 100644 index 0000000..bed1863 --- /dev/null +++ b/Simple.ArgumentParser.Tests/Options/BooleanOptionTests.cs @@ -0,0 +1,78 @@ +using Simple.ArgumentParser.Options; + +namespace Simple.ArgumentParser.Tests.Options; + +public class BooleanOptionTests +{ + private static BooleanOption GetSubjectUnderTest(OptionSettings? settings = null) => new( + name: "test-name", + shortName: 't', + description: "test-description", + settings: settings ?? new OptionSettings()); + + private static OptionSettings GetStrictSettings() => new OptionSettings + { + Strict = true, + Required = false + }; + + [Theory] + [InlineData("true")] + [InlineData("false")] + [InlineData("1")] + [InlineData("0")] + public void ValueIsValid_should_return_true_with_strict_off(string inlineData) + { + // arrange + + var subjectUnderTest = GetSubjectUnderTest(); + + // act + + var result = subjectUnderTest.ValueIsValid(inlineData, out var message); + + // assert + + Assert.True(result); + Assert.Empty(message); + } + + [Theory] + [InlineData("1")] + [InlineData("0")] + public void ValueIsValid_should_return_false_with_strict_on(string inlineData) + { + // arrange + + var subjectUnderTest = GetSubjectUnderTest(GetStrictSettings()); + + // act + + var result = subjectUnderTest.ValueIsValid(inlineData, out var message); + + // assert + + Assert.False(result); + Assert.NotEmpty(message); + } + + [Theory] + [InlineData("-1")] + [InlineData("-0")] + [InlineData("text")] + public void ValueIsValid_should_return_false_with_strict_off(string inlineData) + { + // arrange + + var subjectUnderTest = GetSubjectUnderTest(); + + // act + + var result = subjectUnderTest.ValueIsValid(inlineData, out var message); + + // assert + + Assert.False(result); + Assert.NotEmpty(message); + } +} \ No newline at end of file diff --git a/Simple.ArgumentParser.Tests/Simple.ArgumentParser.Tests.csproj b/Simple.ArgumentParser.Tests/Simple.ArgumentParser.Tests.csproj index 085bddd..906c421 100644 --- a/Simple.ArgumentParser.Tests/Simple.ArgumentParser.Tests.csproj +++ b/Simple.ArgumentParser.Tests/Simple.ArgumentParser.Tests.csproj @@ -20,4 +20,8 @@ + + + + diff --git a/Simple.ArgumentParser.Tests/UnitTest1.cs b/Simple.ArgumentParser.Tests/UnitTest1.cs deleted file mode 100644 index 14f084d..0000000 --- a/Simple.ArgumentParser.Tests/UnitTest1.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Simple.ArgumentParser.Tests; - -public class UnitTest1 -{ - [Fact] - public void Test1() - { - } -} \ No newline at end of file diff --git a/Simple.ArgumentParser/Args.cs b/Simple.ArgumentParser/Args.cs new file mode 100644 index 0000000..f875945 --- /dev/null +++ b/Simple.ArgumentParser/Args.cs @@ -0,0 +1,34 @@ +namespace Simple.ArgumentParser; + +public class Args +{ + public bool IsValid => !(HasInvalidCommands || HasMissingCommands); + public bool HasIgnoredCommands => Ignored.Count > 0; + public bool HasMissingCommands => Missing.Count > 0; + public bool HasInvalidCommands => Invalid.Count > 0; + public bool HelpRequested { get; internal set; } + public bool VersionRequested { get; internal set; } + + + public List Valid { get; set; } = []; + public List Ignored { get; } = []; + public List Missing { get; } = []; + public List Invalid { get; } = []; + public string? HelpSection { get; internal set; } + public string? Version { get; internal set; } + + + internal Args() + { + } + + + public bool Any() => Valid.Count > 0; + + public Command? Get(string key) + { + return Valid.SingleOrDefault(c => c.Name == key); + } + + public List GetAll() => Valid; +} \ No newline at end of file diff --git a/Simple.ArgumentParser/ArgumentOptionException.cs b/Simple.ArgumentParser/ArgumentOptionException.cs new file mode 100644 index 0000000..efb6500 --- /dev/null +++ b/Simple.ArgumentParser/ArgumentOptionException.cs @@ -0,0 +1,8 @@ +namespace Simple.ArgumentParser; + +public class ArgumentOptionException : Exception +{ + public ArgumentOptionException(string message) : base(message) + { + } +} \ No newline at end of file diff --git a/Simple.ArgumentParser/ArgumentParser.cs b/Simple.ArgumentParser/ArgumentParser.cs new file mode 100644 index 0000000..5411adc --- /dev/null +++ b/Simple.ArgumentParser/ArgumentParser.cs @@ -0,0 +1,211 @@ +using System.Reflection; +using System.Text.RegularExpressions; +using Simple.ArgumentParser.Builders; +using Simple.ArgumentParser.Options; + +namespace Simple.ArgumentParser; + +public class ArgumentParser +{ + private readonly bool _strictMode; + private readonly List