From d704cec97b94d4722e9988c87507a3af0ebd3eee Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Wed, 4 Dec 2024 19:28:34 -0500 Subject: [PATCH] Add currently unused Input type --- CHANGELIST.md | 1 + MPF.ExecutionContexts.Test/InputTests.cs | 526 ++++++++++++++++ MPF.ExecutionContexts/Input.cs | 753 +++++++++++++++++++++++ 3 files changed, 1280 insertions(+) create mode 100644 MPF.ExecutionContexts.Test/InputTests.cs create mode 100644 MPF.ExecutionContexts/Input.cs diff --git a/CHANGELIST.md b/CHANGELIST.md index 6069d91c..0c1d18d3 100644 --- a/CHANGELIST.md +++ b/CHANGELIST.md @@ -24,6 +24,7 @@ - Use recommended Min/Max/TryParse - Add tests around ProcessingTool - Add BaseExecutionContext tests +- Add currently unused Input type ### 3.2.4 (2024-11-24) diff --git a/MPF.ExecutionContexts.Test/InputTests.cs b/MPF.ExecutionContexts.Test/InputTests.cs new file mode 100644 index 00000000..6838ec1a --- /dev/null +++ b/MPF.ExecutionContexts.Test/InputTests.cs @@ -0,0 +1,526 @@ +using Xunit; + +namespace MPF.ExecutionContexts.Test +{ + public class InputTests + { + #region ProcessAsFlag + + [Theory] + // Invalid type + [InlineData(InputType.None, "flag", new string[] { "flag" }, 0, false)] + [InlineData(InputType.Boolean, "flag", new string[] { "flag" }, 0, false)] + [InlineData(InputType.Int8, "flag", new string[] { "flag" }, 0, false)] + [InlineData(InputType.UInt8, "flag", new string[] { "flag" }, 0, false)] + [InlineData(InputType.Int16, "flag", new string[] { "flag" }, 0, false)] + [InlineData(InputType.UInt16, "flag", new string[] { "flag" }, 0, false)] + [InlineData(InputType.Int32, "flag", new string[] { "flag" }, 0, false)] + [InlineData(InputType.UInt32, "flag", new string[] { "flag" }, 0, false)] + [InlineData(InputType.Int64, "flag", new string[] { "flag" }, 0, false)] + [InlineData(InputType.UInt64, "flag", new string[] { "flag" }, 0, false)] + [InlineData(InputType.String, "flag", new string[] { "flag" }, 0, false)] + // Invalid parts + [InlineData(InputType.Flag, "flag", new string[0], 0, false)] + // Invalid index + [InlineData(InputType.Flag, "flag", new string[] { "flag" }, -1, false)] + [InlineData(InputType.Flag, "flag", new string[] { "flag" }, 1, false)] + // Invalid name + [InlineData(InputType.Flag, "flag", new string[] { "" }, 0, false)] + [InlineData(InputType.Flag, "flag", new string[] { "flag2" }, 0, false)] + // Valid + [InlineData(InputType.Flag, "flag", new string[] { "flag" }, 0, true)] + public void ProcessAsFlagTest(InputType type, string name, string[] parts, int index, bool expected) + { + Input input = new Input(type, name); + bool actual = input.ProcessAsFlag(parts, ref index); + Assert.Equal(expected, actual); + } + + #endregion + + #region ProcessAsBoolean + + [Theory] + // Invalid type + [InlineData(InputType.None, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.Flag, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.Int8, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.UInt8, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.Int16, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.UInt16, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.Int32, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.UInt32, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.Int64, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.UInt64, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.String, "flag", true, new string[] { "flag" }, 0, null)] + // Invalid parts + [InlineData(InputType.Boolean, "flag", true, new string[0], 0, null)] + // Invalid index + [InlineData(InputType.Boolean, "flag", true, new string[] { "flag" }, -1, null)] + [InlineData(InputType.Boolean, "flag", true, new string[] { "flag" }, 1, null)] + // Invalid name + [InlineData(InputType.Boolean, "flag", true, new string[] { "" }, 0, null)] + [InlineData(InputType.Boolean, "flag", true, new string[] { "flag2" }, 0, null)] + // Valid name, no following + [InlineData(InputType.Boolean, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.Boolean, "flag", false, new string[] { "flag" }, 0, true)] + // Valid name, invalid following + [InlineData(InputType.Boolean, "flag", true, new string[] { "flag", "invalid" }, 0, null)] + [InlineData(InputType.Boolean, "flag", false, new string[] { "flag", "invalid" }, 0, true)] + // Valid name, valid following + [InlineData(InputType.Boolean, "flag", true, new string[] { "flag", "true" }, 0, true)] + [InlineData(InputType.Boolean, "flag", true, new string[] { "flag", "false" }, 0, false)] + public void ProcessAsBooleanTest(InputType type, string name, bool required, string[] parts, int index, bool? expected) + { + Input input = new Input(type, name, required); + bool? actual = input.ProcessAsBoolean(parts, ref index); + Assert.Equal(expected, actual); + } + + #endregion + + #region ProcessAsInt8 + + [Theory] + // Invalid type + [InlineData(InputType.None, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.Flag, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.Boolean, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.UInt8, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.Int16, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.UInt16, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.Int32, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.UInt32, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.Int64, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.UInt64, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.String, "flag", true, new string[] { "flag" }, 0, null)] + // Invalid parts + [InlineData(InputType.Int8, "flag", true, new string[0], 0, null)] + // Invalid index + [InlineData(InputType.Int8, "flag", true, new string[] { "flag" }, -1, null)] + [InlineData(InputType.Int8, "flag", true, new string[] { "flag" }, 1, null)] + // Invalid name + [InlineData(InputType.Int8, "flag", true, new string[] { "" }, 0, null)] + [InlineData(InputType.Int8, "flag", true, new string[] { "flag2" }, 0, null)] + // Valid name, no following + [InlineData(InputType.Int8, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.Int8, "flag", false, new string[] { "flag" }, 0, sbyte.MinValue)] + // Valid name, invalid following + [InlineData(InputType.Int8, "flag", true, new string[] { "flag", "invalid" }, 0, null)] + [InlineData(InputType.Int8, "flag", false, new string[] { "flag", "invalid" }, 0, sbyte.MinValue)] + // Valid name, valid following + [InlineData(InputType.Int8, "flag", true, new string[] { "flag", "1" }, 0, (sbyte)1)] + [InlineData(InputType.Int8, "flag", true, new string[] { "flag", "-1" }, 0, (sbyte)-1)] + public void ProcessAsInt8Test(InputType type, string name, bool required, string[] parts, int index, sbyte? expected) + { + Input input = new Input(type, name, required); + sbyte? actual = input.ProcessAsInt8(parts, ref index); + Assert.Equal(expected, actual); + } + + #endregion + + #region ProcessAsUInt8 + + [Theory] + // Invalid type + [InlineData(InputType.None, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.Flag, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.Boolean, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.Int8, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.Int16, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.UInt16, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.Int32, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.UInt32, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.Int64, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.UInt64, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.String, "flag", true, new string[] { "flag" }, 0, null)] + // Invalid parts + [InlineData(InputType.UInt8, "flag", true, new string[0], 0, null)] + // Invalid index + [InlineData(InputType.UInt8, "flag", true, new string[] { "flag" }, -1, null)] + [InlineData(InputType.UInt8, "flag", true, new string[] { "flag" }, 1, null)] + // Invalid name + [InlineData(InputType.UInt8, "flag", true, new string[] { "" }, 0, null)] + [InlineData(InputType.UInt8, "flag", true, new string[] { "flag2" }, 0, null)] + // Valid name, no following + [InlineData(InputType.UInt8, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.UInt8, "flag", false, new string[] { "flag" }, 0, byte.MinValue)] + // Valid name, invalid following + [InlineData(InputType.UInt8, "flag", true, new string[] { "flag", "invalid" }, 0, null)] + [InlineData(InputType.UInt8, "flag", false, new string[] { "flag", "invalid" }, 0, byte.MinValue)] + // Valid name, valid following + [InlineData(InputType.UInt8, "flag", true, new string[] { "flag", "1" }, 0, (byte)1)] + public void ProcessAsUInt8Test(InputType type, string name, bool required, string[] parts, int index, byte? expected) + { + Input input = new Input(type, name, required); + byte? actual = input.ProcessAsUInt8(parts, ref index); + Assert.Equal(expected, actual); + } + + #endregion + + #region ProcessAsInt16 + + [Theory] + // Invalid type + [InlineData(InputType.None, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.Flag, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.Boolean, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.Int8, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.UInt8, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.UInt16, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.Int32, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.UInt32, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.Int64, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.UInt64, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.String, "flag", true, new string[] { "flag" }, 0, null)] + // Invalid parts + [InlineData(InputType.Int16, "flag", true, new string[0], 0, null)] + // Invalid index + [InlineData(InputType.Int16, "flag", true, new string[] { "flag" }, -1, null)] + [InlineData(InputType.Int16, "flag", true, new string[] { "flag" }, 1, null)] + // Invalid name + [InlineData(InputType.Int16, "flag", true, new string[] { "" }, 0, null)] + [InlineData(InputType.Int16, "flag", true, new string[] { "flag2" }, 0, null)] + // Valid name, no following + [InlineData(InputType.Int16, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.Int16, "flag", false, new string[] { "flag" }, 0, short.MinValue)] + // Valid name, invalid following + [InlineData(InputType.Int16, "flag", true, new string[] { "flag", "invalid" }, 0, null)] + [InlineData(InputType.Int16, "flag", false, new string[] { "flag", "invalid" }, 0, short.MinValue)] + // Valid name, valid following + [InlineData(InputType.Int16, "flag", true, new string[] { "flag", "1" }, 0, (short)1)] + [InlineData(InputType.Int16, "flag", true, new string[] { "flag", "-1" }, 0, (short)-1)] + public void ProcessAsInt16Test(InputType type, string name, bool required, string[] parts, int index, short? expected) + { + Input input = new Input(type, name, required); + short? actual = input.ProcessAsInt16(parts, ref index); + Assert.Equal(expected, actual); + } + + #endregion + + #region ProcessAsUInt16 + + [Theory] + // Invalid type + [InlineData(InputType.None, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.Flag, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.Boolean, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.Int8, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.UInt8, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.Int16, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.Int32, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.UInt32, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.Int64, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.UInt64, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.String, "flag", true, new string[] { "flag" }, 0, null)] + // Invalid parts + [InlineData(InputType.UInt16, "flag", true, new string[0], 0, null)] + // Invalid index + [InlineData(InputType.UInt16, "flag", true, new string[] { "flag" }, -1, null)] + [InlineData(InputType.UInt16, "flag", true, new string[] { "flag" }, 1, null)] + // Invalid name + [InlineData(InputType.UInt16, "flag", true, new string[] { "" }, 0, null)] + [InlineData(InputType.UInt16, "flag", true, new string[] { "flag2" }, 0, null)] + // Valid name, no following + [InlineData(InputType.UInt16, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.UInt16, "flag", false, new string[] { "flag" }, 0, ushort.MinValue)] + // Valid name, invalid following + [InlineData(InputType.UInt16, "flag", true, new string[] { "flag", "invalid" }, 0, null)] + [InlineData(InputType.UInt16, "flag", false, new string[] { "flag", "invalid" }, 0, ushort.MinValue)] + // Valid name, valid following + [InlineData(InputType.UInt16, "flag", true, new string[] { "flag", "1" }, 0, (ushort)1)] + public void ProcessAsUInt16Test(InputType type, string name, bool required, string[] parts, int index, ushort? expected) + { + Input input = new Input(type, name, required); + ushort? actual = input.ProcessAsUInt16(parts, ref index); + Assert.Equal(expected, actual); + } + + #endregion + + #region ProcessAsInt32 + + [Theory] + // Invalid type + [InlineData(InputType.None, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.Flag, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.Boolean, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.Int8, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.UInt8, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.Int16, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.UInt16, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.UInt32, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.Int64, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.UInt64, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.String, "flag", true, new string[] { "flag" }, 0, null)] + // Invalid parts + [InlineData(InputType.Int32, "flag", true, new string[0], 0, null)] + // Invalid index + [InlineData(InputType.Int32, "flag", true, new string[] { "flag" }, -1, null)] + [InlineData(InputType.Int32, "flag", true, new string[] { "flag" }, 1, null)] + // Invalid name + [InlineData(InputType.Int32, "flag", true, new string[] { "" }, 0, null)] + [InlineData(InputType.Int32, "flag", true, new string[] { "flag2" }, 0, null)] + // Valid name, no following + [InlineData(InputType.Int32, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.Int32, "flag", false, new string[] { "flag" }, 0, int.MinValue)] + // Valid name, invalid following + [InlineData(InputType.Int32, "flag", true, new string[] { "flag", "invalid" }, 0, null)] + [InlineData(InputType.Int32, "flag", false, new string[] { "flag", "invalid" }, 0, int.MinValue)] + // Valid name, valid following + [InlineData(InputType.Int32, "flag", true, new string[] { "flag", "1" }, 0, (int)1)] + [InlineData(InputType.Int32, "flag", true, new string[] { "flag", "-1" }, 0, (int)-1)] + public void ProcessAsInt32Test(InputType type, string name, bool required, string[] parts, int index, int? expected) + { + Input input = new Input(type, name, required); + int? actual = input.ProcessAsInt32(parts, ref index); + Assert.Equal(expected, actual); + } + + #endregion + + #region ProcessAsUInt32 + + [Theory] + // Invalid type + [InlineData(InputType.None, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.Flag, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.Boolean, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.Int8, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.UInt8, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.Int16, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.UInt16, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.Int32, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.Int64, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.UInt64, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.String, "flag", true, new string[] { "flag" }, 0, null)] + // Invalid parts + [InlineData(InputType.UInt32, "flag", true, new string[0], 0, null)] + // Invalid index + [InlineData(InputType.UInt32, "flag", true, new string[] { "flag" }, -1, null)] + [InlineData(InputType.UInt32, "flag", true, new string[] { "flag" }, 1, null)] + // Invalid name + [InlineData(InputType.UInt32, "flag", true, new string[] { "" }, 0, null)] + [InlineData(InputType.UInt32, "flag", true, new string[] { "flag2" }, 0, null)] + // Valid name, no following + [InlineData(InputType.UInt32, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.UInt32, "flag", false, new string[] { "flag" }, 0, uint.MinValue)] + // Valid name, invalid following + [InlineData(InputType.UInt32, "flag", true, new string[] { "flag", "invalid" }, 0, null)] + [InlineData(InputType.UInt32, "flag", false, new string[] { "flag", "invalid" }, 0, uint.MinValue)] + // Valid name, valid following + [InlineData(InputType.UInt32, "flag", true, new string[] { "flag", "1" }, 0, (uint)1)] + public void ProcessAsUInt32Test(InputType type, string name, bool required, string[] parts, int index, uint? expected) + { + Input input = new Input(type, name, required); + uint? actual = input.ProcessAsUInt32(parts, ref index); + Assert.Equal(expected, actual); + } + + #endregion + + #region ProcessAsInt64 + + [Theory] + // Invalid type + [InlineData(InputType.None, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.Flag, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.Boolean, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.Int8, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.UInt8, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.Int16, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.UInt16, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.Int32, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.UInt32, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.UInt64, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.String, "flag", true, new string[] { "flag" }, 0, null)] + // Invalid parts + [InlineData(InputType.Int64, "flag", true, new string[0], 0, null)] + // Invalid index + [InlineData(InputType.Int64, "flag", true, new string[] { "flag" }, -1, null)] + [InlineData(InputType.Int64, "flag", true, new string[] { "flag" }, 1, null)] + // Invalid name + [InlineData(InputType.Int64, "flag", true, new string[] { "" }, 0, null)] + [InlineData(InputType.Int64, "flag", true, new string[] { "flag2" }, 0, null)] + // Valid name, no following + [InlineData(InputType.Int64, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.Int64, "flag", false, new string[] { "flag" }, 0, long.MinValue)] + // Valid name, invalid following + [InlineData(InputType.Int64, "flag", true, new string[] { "flag", "invalid" }, 0, null)] + [InlineData(InputType.Int64, "flag", false, new string[] { "flag", "invalid" }, 0, long.MinValue)] + // Valid name, valid following + [InlineData(InputType.Int64, "flag", true, new string[] { "flag", "1" }, 0, (long)1)] + [InlineData(InputType.Int64, "flag", true, new string[] { "flag", "-1" }, 0, (long)-1)] + public void ProcessAsInt64Test(InputType type, string name, bool required, string[] parts, int index, long? expected) + { + Input input = new Input(type, name, required); + long? actual = input.ProcessAsInt64(parts, ref index); + Assert.Equal(expected, actual); + } + + #endregion + + #region ProcessAsUInt64 + + [Theory] + // Invalid type + [InlineData(InputType.None, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.Flag, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.Boolean, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.Int8, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.UInt8, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.Int16, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.UInt16, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.Int32, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.UInt32, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.Int64, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.String, "flag", true, new string[] { "flag" }, 0, null)] + // Invalid parts + [InlineData(InputType.UInt64, "flag", true, new string[0], 0, null)] + // Invalid index + [InlineData(InputType.UInt64, "flag", true, new string[] { "flag" }, -1, null)] + [InlineData(InputType.UInt64, "flag", true, new string[] { "flag" }, 1, null)] + // Invalid name + [InlineData(InputType.UInt64, "flag", true, new string[] { "" }, 0, null)] + [InlineData(InputType.UInt64, "flag", true, new string[] { "flag2" }, 0, null)] + // Valid name, no following + [InlineData(InputType.UInt64, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.UInt64, "flag", false, new string[] { "flag" }, 0, ulong.MinValue)] + // Valid name, invalid following + [InlineData(InputType.UInt64, "flag", true, new string[] { "flag", "invalid" }, 0, null)] + [InlineData(InputType.UInt64, "flag", false, new string[] { "flag", "invalid" }, 0, ulong.MinValue)] + // Valid name, valid following + [InlineData(InputType.UInt64, "flag", true, new string[] { "flag", "1" }, 0, (ulong)1)] + public void ProcessAsUInt64Test(InputType type, string name, bool required, string[] parts, int index, ulong? expected) + { + Input input = new Input(type, name, required); + ulong? actual = input.ProcessAsUInt64(parts, ref index); + Assert.Equal(expected, actual); + } + + #endregion + + #region ProcessAsString + + [Theory] + // Invalid type + [InlineData(InputType.None, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.Flag, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.Boolean, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.Int8, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.UInt8, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.Int16, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.UInt16, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.Int32, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.UInt32, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.Int64, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.UInt64, "flag", true, new string[] { "flag" }, 0, null)] + // Invalid parts + [InlineData(InputType.String, "flag", true, new string[0], 0, null)] + // Invalid index + [InlineData(InputType.String, "flag", true, new string[] { "flag" }, -1, null)] + [InlineData(InputType.String, "flag", true, new string[] { "flag" }, 1, null)] + // Invalid name + [InlineData(InputType.String, "flag", true, new string[] { "" }, 0, null)] + [InlineData(InputType.String, "flag", true, new string[] { "flag2" }, 0, null)] + // Valid name, no following + [InlineData(InputType.String, "flag", true, new string[] { "flag" }, 0, null)] + [InlineData(InputType.String, "flag", false, new string[] { "flag" }, 0, "")] + // Valid name, following + [InlineData(InputType.String, "flag", true, new string[] { "flag", "value" }, 0, "value")] + public void ProcessAsString(InputType type, string name, bool required, string[] parts, int index, string? expected) + { + Input input = new Input(type, name, required); + string? actual = input.ProcessAsString(parts, ref index); + Assert.Equal(expected, actual); + } + + #endregion + + #region DoesExist + + [Fact] + public void DoesExist_Empty_False() + { + string[] parts = []; + int index = 0; + bool actual = Input.DoesExist(parts, index); + Assert.False(actual); + } + + [Fact] + public void DoesExist_Negative_False() + { + string[] parts = ["item"]; + int index = -1; + bool actual = Input.DoesExist(parts, index); + Assert.False(actual); + } + + [Fact] + public void DoesExist_Greater_False() + { + string[] parts = ["item"]; + int index = 1; + bool actual = Input.DoesExist(parts, index); + Assert.False(actual); + } + + [Fact] + public void DoesExist_Valid_True() + { + string[] parts = ["item"]; + int index = 0; + bool actual = Input.DoesExist(parts, index); + Assert.True(actual); + } + + #endregion + + #region ExtractFactorFromValue + + [Theory] + [InlineData("1", "1", 1)] + [InlineData("1c", "1", 1)] + [InlineData("1w", "1", 2)] + [InlineData("1d", "1", 4)] + [InlineData("1q", "1", 8)] + [InlineData("1k", "1", 1024)] + [InlineData("1M", "1", 1024 * 1024)] + [InlineData("1G", "1", 1024 * 1024 * 1024)] + public void ExtractFactorFromValueTest(string value, string expected, long expectedFactor) + { + string actual = Input.ExtractFactorFromValue(value, out long factor); + Assert.Equal(expected, actual); + Assert.Equal(expectedFactor, factor); + } + + #endregion + + #region RemoveHexIdentifier + + [Theory] + [InlineData("", "")] + [InlineData("0", "0")] + [InlineData("00", "00")] + [InlineData("0x", "0x")] + [InlineData("0X", "0X")] + [InlineData("A", "A")] + [InlineData("A0", "A0")] + [InlineData("Ax", "Ax")] + [InlineData("AX", "AX")] + [InlineData("012345", "012345")] + [InlineData("0x12345", "12345")] + [InlineData("0X12345", "12345")] + public void RemoveHexIdentifierTest(string value, string expected) + { + string actual = Input.RemoveHexIdentifier(value); + Assert.Equal(expected, actual); + } + + #endregion + } +} \ No newline at end of file diff --git a/MPF.ExecutionContexts/Input.cs b/MPF.ExecutionContexts/Input.cs new file mode 100644 index 00000000..818c67b6 --- /dev/null +++ b/MPF.ExecutionContexts/Input.cs @@ -0,0 +1,753 @@ +using System; +using System.Globalization; + +namespace MPF.ExecutionContexts +{ + /// + /// Indicates the input type + /// + public enum InputType + { + None, + Flag, + Boolean, + Int8, + UInt8, + Int16, + UInt16, + Int32, + UInt32, + Int64, + UInt64, + String, + } + + /// + /// Represents a single input for an execution context + /// + public class Input + { + #region Properties + + /// + /// Input type for parsing + /// + public readonly InputType InputType; + + /// + /// Primary name for the input + /// + public readonly string Name; + + /// + /// Verbose name for the input + /// + private readonly string? _longName; + + /// + /// Indicates if the value following is required or not + /// + private readonly bool _required; + + #endregion + + #region Constructors + + /// Type of input for parsing + /// Flag name / value + public Input(InputType type, string name) + { + InputType = type; + Name = name; + _longName = null; + _required = true; + } + + /// Type of input for parsing + /// Flag name / value + /// Indicates if a following value is required + public Input(InputType type, string name, bool required) + { + InputType = type; + Name = name; + _longName = null; + _required = required; + } + + /// Type of input for parsing + /// Flag name / value + /// Verbose flag name / value + public Input(InputType type, string shortName, string longName) + { + InputType = type; + Name = shortName; + _longName = longName; + _required = true; + } + + /// Type of input for parsing + /// Flag name / value + /// Verbose flag name / value + /// Indicates if a following value is required + public Input(InputType type, string shortName, string longName, bool required) + { + InputType = type; + Name = shortName; + _longName = longName; + _required = required; + } + + #endregion + + #region Processors + + /// + /// Process the input as a flag + /// + /// Parts array to be referenced + /// Reference to the position in the parts + /// True if the flag was set, false otherwise + public bool ProcessAsFlag(string[] parts, ref int index) + { + // Check the input type + if (InputType != InputType.Flag) + return false; + + // Check the parts array + if (parts.Length == 0) + return false; + + // Check the index + if (index < 0 || index >= parts.Length) + return false; + + // Check the name + if (parts[index] == Name || (_longName != null && parts[index] == _longName)) + return true; + + return false; + } + + /// + /// Process the input as a boolean + /// + /// Parts array to be referenced + /// Reference to the position in the parts + /// Boolean value if set, null otherwise + public bool? ProcessAsBoolean(string[] parts, ref int index) + { + // Check the input type + if (InputType != InputType.Boolean) + return null; + + // Check the parts array + if (parts == null || parts.Length == 0) + return null; + + // Check the index + if (index < 0 || index >= parts.Length) + return null; + + // Check the name + if (parts[index] == Name || (_longName != null && parts[index] == _longName)) + { + // Ensure the value exists + if (!DoesExist(parts, index + 1)) + return _required ? null : true; + + // If the next value is valid + if (!bool.TryParse(parts[index + 1], out bool value)) + return _required ? null : true; + + index++; + return value; + } + + return null; + } + + /// + /// Process the input as an Int8 + /// + /// Parts array to be referenced + /// Reference to the position in the parts + /// Int8 value if set, minimum value if not required and missing, null otherwise + public sbyte? ProcessAsInt8(string[] parts, ref int index) + { + // Check the input type + if (InputType != InputType.Int8) + return null; + + // Check the parts array + if (parts == null || parts.Length == 0) + return null; + + // Check the index + if (index < 0 || index >= parts.Length) + return null; + + // Check the name + if (parts[index] == Name || (_longName != null && parts[index] == _longName)) + { + // Ensure the value exists + if (!DoesExist(parts, index + 1)) + return _required ? null : sbyte.MinValue; + + // If the next value is valid + if (sbyte.TryParse(parts[index + 1], out sbyte value)) + { + index++; + return value; + } + + // Try to process as a formatted string + string baseVal = ExtractFactorFromValue(parts[index + 1], out long factor); + if (sbyte.TryParse(baseVal, out value)) + { + index++; + return (sbyte)(value * factor); + } + + // Try to process as a hex string + string hexValue = RemoveHexIdentifier(baseVal); + if (sbyte.TryParse(hexValue, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out value)) + { + index++; + return (sbyte)(value * factor); + } + + // Return value based on required flag + return _required ? null : sbyte.MinValue; + } + + return null; + } + + /// + /// Process the input as a UInt8 + /// + /// Parts array to be referenced + /// Reference to the position in the parts + /// UInt8 value if set, minimum value if not required and missing, null otherwise + public byte? ProcessAsUInt8(string[] parts, ref int index) + { + // Check the input type + if (InputType != InputType.UInt8) + return null; + + // Check the parts array + if (parts == null || parts.Length == 0) + return null; + + // Check the index + if (index < 0 || index >= parts.Length) + return null; + + // Check the name + if (parts[index] == Name || (_longName != null && parts[index] == _longName)) + { + // Ensure the value exists + if (!DoesExist(parts, index + 1)) + return _required ? null : byte.MinValue; + + // If the next value is valid + if (byte.TryParse(parts[index + 1], out byte value)) + { + index++; + return value; + } + + // Try to process as a formatted string + string baseVal = ExtractFactorFromValue(parts[index + 1], out long factor); + if (byte.TryParse(baseVal, out value)) + { + index++; + return (byte)(value * factor); + } + + // Try to process as a hex string + string hexValue = RemoveHexIdentifier(baseVal); + if (byte.TryParse(hexValue, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out value)) + { + index++; + return (byte)(value * factor); + } + + // Return value based on required flag + return _required ? null : byte.MinValue; + } + + return null; + } + + /// + /// Process the input as an Int16 + /// + /// Parts array to be referenced + /// Reference to the position in the parts + /// Int16 value if set, minimum value if not required and missing, null otherwise + public short? ProcessAsInt16(string[] parts, ref int index) + { + // Check the input type + if (InputType != InputType.Int16) + return null; + + // Check the parts array + if (parts == null || parts.Length == 0) + return null; + + // Check the index + if (index < 0 || index >= parts.Length) + return null; + + // Check the name + if (parts[index] == Name || (_longName != null && parts[index] == _longName)) + { + // Ensure the value exists + if (!DoesExist(parts, index + 1)) + return _required ? null : short.MinValue; + + // If the next value is valid + if (short.TryParse(parts[index + 1], out short value)) + { + index++; + return value; + } + + // Try to process as a formatted string + string baseVal = ExtractFactorFromValue(parts[index + 1], out long factor); + if (short.TryParse(baseVal, out value)) + { + index++; + return (short)(value * factor); + } + + // Try to process as a hex string + string hexValue = RemoveHexIdentifier(baseVal); + if (short.TryParse(hexValue, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out value)) + { + index++; + return (short)(value * factor); + } + + // Return value based on required flag + return _required ? null : short.MinValue; + } + + return null; + } + + /// + /// Process the input as a UInt16 + /// + /// Parts array to be referenced + /// Reference to the position in the parts + /// UInt16 value if set, minimum value if not required and missing, null otherwise + public ushort? ProcessAsUInt16(string[] parts, ref int index) + { + // Check the input type + if (InputType != InputType.UInt16) + return null; + + // Check the parts array + if (parts == null || parts.Length == 0) + return null; + + // Check the index + if (index < 0 || index >= parts.Length) + return null; + + // Check the name + if (parts[index] == Name || (_longName != null && parts[index] == _longName)) + { + // Ensure the value exists + if (!DoesExist(parts, index + 1)) + return _required ? null : ushort.MinValue; + + // If the next value is valid + if (ushort.TryParse(parts[index + 1], out ushort value)) + { + index++; + return value; + } + + // Try to process as a formatted string + string baseVal = ExtractFactorFromValue(parts[index + 1], out long factor); + if (ushort.TryParse(baseVal, out value)) + { + index++; + return (ushort)(value * factor); + } + + // Try to process as a hex string + string hexValue = RemoveHexIdentifier(baseVal); + if (ushort.TryParse(hexValue, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out value)) + { + index++; + return (ushort)(value * factor); + } + + // Return value based on required flag + return _required ? null : ushort.MinValue; + } + + return null; + } + + /// + /// Process the input as an Int32 + /// + /// Parts array to be referenced + /// Reference to the position in the parts + /// Int32 value if set, minimum value if not required and missing, null otherwise + public int? ProcessAsInt32(string[] parts, ref int index) + { + // Check the input type + if (InputType != InputType.Int32) + return null; + + // Check the parts array + if (parts == null || parts.Length == 0) + return null; + + // Check the index + if (index < 0 || index >= parts.Length) + return null; + + // Check the name + if (parts[index] == Name || (_longName != null && parts[index] == _longName)) + { + // Ensure the value exists + if (!DoesExist(parts, index + 1)) + return _required ? null : int.MinValue; + + // If the next value is valid + if (int.TryParse(parts[index + 1], out int value)) + { + index++; + return value; + } + + // Try to process as a formatted string + string baseVal = ExtractFactorFromValue(parts[index + 1], out long factor); + if (int.TryParse(baseVal, out value)) + { + index++; + return (int)(value * factor); + } + + // Try to process as a hex string + string hexValue = RemoveHexIdentifier(baseVal); + if (int.TryParse(hexValue, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out value)) + { + index++; + return (int)(value * factor); + } + + // Return value based on required flag + return _required ? null : int.MinValue; + } + + return null; + } + + /// + /// Process the input as a UInt32 + /// + /// Parts array to be referenced + /// Reference to the position in the parts + /// UInt32 value if set, minimum value if not required and missing, null otherwise + public uint? ProcessAsUInt32(string[] parts, ref int index) + { + // Check the input type + if (InputType != InputType.UInt32) + return null; + + // Check the parts array + if (parts == null || parts.Length == 0) + return null; + + // Check the index + if (index < 0 || index >= parts.Length) + return null; + + // Check the name + if (parts[index] == Name || (_longName != null && parts[index] == _longName)) + { + // Ensure the value exists + if (!DoesExist(parts, index + 1)) + return _required ? null : uint.MinValue; + + // If the next value is valid + if (uint.TryParse(parts[index + 1], out uint value)) + { + index++; + return value; + } + + // Try to process as a formatted string + string baseVal = ExtractFactorFromValue(parts[index + 1], out long factor); + if (uint.TryParse(baseVal, out value)) + { + index++; + return (uint)(value * factor); + } + + // Try to process as a hex string + string hexValue = RemoveHexIdentifier(baseVal); + if (uint.TryParse(hexValue, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out value)) + { + index++; + return (uint)(value * factor); + } + + // Return value based on required flag + return _required ? null : uint.MinValue; + } + + return null; + } + + /// + /// Process the input as an Int64 + /// + /// Parts array to be referenced + /// Reference to the position in the parts + /// Int64 value if set, minimum value if not required and missing, null otherwise + public long? ProcessAsInt64(string[] parts, ref int index) + { + // Check the input type + if (InputType != InputType.Int64) + return null; + + // Check the parts array + if (parts == null || parts.Length == 0) + return null; + + // Check the index + if (index < 0 || index >= parts.Length) + return null; + + // Check the name + if (parts[index] == Name || (_longName != null && parts[index] == _longName)) + { + // Ensure the value exists + if (!DoesExist(parts, index + 1)) + return _required ? null : long.MinValue; + + // If the next value is valid + if (long.TryParse(parts[index + 1], out long value)) + { + index++; + return value; + } + + // Try to process as a formatted string + string baseVal = ExtractFactorFromValue(parts[index + 1], out long factor); + if (long.TryParse(baseVal, out value)) + { + index++; + return (long)(value * factor); + } + + // Try to process as a hex string + string hexValue = RemoveHexIdentifier(baseVal); + if (long.TryParse(hexValue, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out value)) + { + index++; + return (long)(value * factor); + } + + // Return value based on required flag + return _required ? null : long.MinValue; + } + + return null; + } + + /// + /// Process the input as a UInt64 + /// + /// Parts array to be referenced + /// Reference to the position in the parts + /// UInt64 value if set, minimum value if not required and missing, null otherwise + public ulong? ProcessAsUInt64(string[] parts, ref int index) + { + // Check the input type + if (InputType != InputType.UInt64) + return null; + + // Check the parts array + if (parts == null || parts.Length == 0) + return null; + + // Check the index + if (index < 0 || index >= parts.Length) + return null; + + // Check the name + if (parts[index] == Name || (_longName != null && parts[index] == _longName)) + { + // Ensure the value exists + if (!DoesExist(parts, index + 1)) + return _required ? null : ulong.MinValue; + + // If the next value is valid + if (ulong.TryParse(parts[index + 1], out ulong value)) + { + index++; + return value; + } + + // Try to process as a formatted string + string baseVal = ExtractFactorFromValue(parts[index + 1], out long factor); + if (ulong.TryParse(baseVal, out value)) + { + index++; + return (ulong)(value * (ulong)factor); + } + + // Try to process as a hex string + string hexValue = RemoveHexIdentifier(baseVal); + if (ulong.TryParse(hexValue, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out value)) + { + index++; + return (ulong)(value * (ulong)factor); + } + + // Return value based on required flag + return _required ? null : ulong.MinValue; + } + + return null; + } + + /// + /// Process the input as a UInt64 + /// + /// Parts array to be referenced + /// Reference to the position in the parts + /// String value if set, empty value if not required and missing, null otherwise + public string? ProcessAsString(string[] parts, ref int index) + { + // Check the input type + if (InputType != InputType.String) + return null; + + // Check the parts array + if (parts == null || parts.Length == 0) + return null; + + // Check the index + if (index < 0 || index >= parts.Length) + return null; + + // Check the name + if (parts[index] == Name || (_longName != null && parts[index] == _longName)) + { + // Ensure the value exists + if (!DoesExist(parts, index + 1)) + return _required ? null : string.Empty; + + index++; + return parts[index]; + } + + return null; + } + + #endregion + + #region Helpers + + /// + /// Returns whether or not the selected item exists + /// + /// Parts array to be referenced + /// Current index + /// True if the next item exists, false otherwise + internal static bool DoesExist(string[] parts, int index) + => index >= 0 && index < parts.Length; + + /// + /// Get the trimmed value and multiplication factor from a value + /// + /// String value to treat as suffixed number + /// Trimmed value and multiplication factor + internal static string ExtractFactorFromValue(string value, out long factor) + { + value = value.Trim('"'); + factor = 1; + + // Characters + if (value.EndsWith("c", StringComparison.Ordinal)) + { + factor = 1; + value = value.TrimEnd('c'); + } + + // Words + else if (value.EndsWith("w", StringComparison.Ordinal)) + { + factor = 2; + value = value.TrimEnd('w'); + } + + // Double Words + else if (value.EndsWith("d", StringComparison.Ordinal)) + { + factor = 4; + value = value.TrimEnd('d'); + } + + // Quad Words + else if (value.EndsWith("q", StringComparison.Ordinal)) + { + factor = 8; + value = value.TrimEnd('q'); + } + + // Kilobytes + else if (value.EndsWith("k", StringComparison.Ordinal)) + { + factor = 1024; + value = value.TrimEnd('k'); + } + + // Megabytes + else if (value.EndsWith("M", StringComparison.Ordinal)) + { + factor = 1024 * 1024; + value = value.TrimEnd('M'); + } + + // Gigabytes + else if (value.EndsWith("G", StringComparison.Ordinal)) + { + factor = 1024 * 1024 * 1024; + value = value.TrimEnd('G'); + } + + return value; + } + + /// + /// Removes a leading 0x if it exists, case insensitive + /// + /// String with removed leading 0x + /// + internal static string RemoveHexIdentifier(string value) + { + if (value.Length <= 2) + return value; + if (value[0] != '0') + return value; + if (value[1] != 'x' && value[1] != 'X') + return value; + + return value.Substring(2); + } + + #endregion + } +} \ No newline at end of file