From 25df7a589f4d6ab1a5a853d5d7190eaf42fd1fff Mon Sep 17 00:00:00 2001 From: Dave Walker Date: Sun, 27 Aug 2023 11:22:10 +0100 Subject: [PATCH 1/3] Added command line parser for specifying arguments --- README.md | 26 +-- .../BaseStationReader.Data.csproj | 4 +- .../BaseStationReader.Entities.csproj | 4 +- .../Config/CommandLineOption.cs | 16 ++ .../Config/CommandLineOptionType.cs | 15 ++ .../Config/CommandLineOptionValue.cs | 11 ++ .../MalformedCommandLineException.cs | 31 +++ .../MissingMandatoryOptionException.cs | 31 +++ .../Exceptions/TooFewValuesException.cs | 31 +++ .../Exceptions/TooManyValuesException.cs | 36 ++++ .../UnrecognisedCommandLineOptionException.cs | 31 +++ .../BaseStationReader.Logic.csproj | 4 +- .../CommandLineParser.cs | 183 ++++++++++++++++++ .../BaseStationReader.Terminal.csproj | 6 +- src/BaseStationReader.Terminal/Program.cs | 56 +++++- .../CommandLineParserTest.cs | 104 ++++++++++ 16 files changed, 567 insertions(+), 22 deletions(-) create mode 100644 src/BaseStationReader.Entities/Config/CommandLineOption.cs create mode 100644 src/BaseStationReader.Entities/Config/CommandLineOptionType.cs create mode 100644 src/BaseStationReader.Entities/Config/CommandLineOptionValue.cs create mode 100644 src/BaseStationReader.Entities/Exceptions/MalformedCommandLineException.cs create mode 100644 src/BaseStationReader.Entities/Exceptions/MissingMandatoryOptionException.cs create mode 100644 src/BaseStationReader.Entities/Exceptions/TooFewValuesException.cs create mode 100644 src/BaseStationReader.Entities/Exceptions/TooManyValuesException.cs create mode 100644 src/BaseStationReader.Entities/Exceptions/UnrecognisedCommandLineOptionException.cs create mode 100644 src/BaseStationReader.Logic/CommandLineParser.cs create mode 100644 src/BaseStationReader.Tests/CommandLineParserTest.cs diff --git a/README.md b/README.md index b5b5f52..6c815bb 100644 --- a/README.md +++ b/README.md @@ -37,18 +37,20 @@ ### Configuration File - The appsettings.json file in the console application project contains the following keys for controlling the application: -| Section | Key | Purpose | -| --- | --- | --- | -| ApplicationSettings | Host | Host the reader connects to for reading messages | -| ApplicationSettings | Port | Port the reader connects to for reading messages | -| ApplicationSettings | TimeToRecent | Threshold, in ms, after the most recent message at which an aircraft is considered "recent" (see states, below) | -| ApplicationSettings | TimeToStale | Threshold, in ms, after the most recent message at which an aircraft is considered "stale" (see states, below) | -| ApplicationSettings | TimeToRemoval | Threshold, in ms, after the most recent message at which an aircraft is removed from tracking (see states, below) | -| ApplicationSettings | LogFile | Path and name of the log file | -| ApplicationSettings | EnableSqlWriter | Set to true to enable the SQL writer or false to disable it | -| ApplicationSettings | WriterInterval | Interval, in ms, at which the writer writes batches of changes from the queue to the database | -| ApplicationSettings | WriterBatchSize | Maximum number of changes to consider on each WriterInterval | -| ConnectionStrings | BaseStationReaderDB | SQLite connection string for the database | +| Section | Key | Command Line | Short Name | Purpose | +| --- | --- | --- | --- | --- | +| ApplicationSettings | Host | --host | -h | Host the reader connects to for reading messages | +| ApplicationSettings | Port | --port | -p | Port the reader connects to for reading messages | +| ApplicationSettings | TimeToRecent | --recent | -r | Threshold, in ms, after the most recent message at which an aircraft is considered "recent" (see states, below) | +| ApplicationSettings | TimeToStale | --stale | -s | Threshold, in ms, after the most recent message at which an aircraft is considered "stale" (see states, below) | +| ApplicationSettings | TimeToRemoval | --remove | -x | Threshold, in ms, after the most recent message at which an aircraft is removed from tracking (see states, below) | +| ApplicationSettings | LogFile | --log-file | -l | Path and name of the log file | +| ApplicationSettings | EnableSqlWriter | --enable-sql-writer | -w | Set to true to enable the SQL writer or false to disable it | +| ApplicationSettings | WriterInterval | --writer-interval | -i | Interval, in ms, at which the writer writes batches of changes from the queue to the database | +| ApplicationSettings | WriterBatchSize | --writer-batch-size | -b | Maximum number of changes to consider on each WriterInterval | +| ConnectionStrings | BaseStationReaderDB | - | - | SQLite connection string for the database | + +- Values may also be passed using the indicated command line arguments, in which case the values are first read from the configuration file and then any values specified on the command line are then applied ## Aircraft Tracking diff --git a/src/BaseStationReader.Data/BaseStationReader.Data.csproj b/src/BaseStationReader.Data/BaseStationReader.Data.csproj index 1f00129..183c469 100644 --- a/src/BaseStationReader.Data/BaseStationReader.Data.csproj +++ b/src/BaseStationReader.Data/BaseStationReader.Data.csproj @@ -5,7 +5,7 @@ enable enable BaseStationReader.Data - 1.12.0.0 + 1.13.0.0 Dave Walker Copyright (c) Dave Walker 2023 Dave Walker @@ -17,7 +17,7 @@ https://github.com/davewalker5/ADS-B-BaseStationReader MIT false - 1.12.0.0 + 1.13.0.0 diff --git a/src/BaseStationReader.Entities/BaseStationReader.Entities.csproj b/src/BaseStationReader.Entities/BaseStationReader.Entities.csproj index eaf4a2c..8bbc385 100644 --- a/src/BaseStationReader.Entities/BaseStationReader.Entities.csproj +++ b/src/BaseStationReader.Entities/BaseStationReader.Entities.csproj @@ -5,7 +5,7 @@ enable enable BaseStationReader.Entities - 1.12.0.0 + 1.13.0.0 Dave Walker Copyright (c) Dave Walker 2023 Dave Walker @@ -17,7 +17,7 @@ https://github.com/davewalker5/ADS-B-BaseStationReader MIT false - 1.12.0.0 + 1.13.0.0 diff --git a/src/BaseStationReader.Entities/Config/CommandLineOption.cs b/src/BaseStationReader.Entities/Config/CommandLineOption.cs new file mode 100644 index 0000000..e61d986 --- /dev/null +++ b/src/BaseStationReader.Entities/Config/CommandLineOption.cs @@ -0,0 +1,16 @@ +using System.Diagnostics.CodeAnalysis; + +namespace BaseStationReader.Entities.Config +{ + [ExcludeFromCodeCoverage] + public class CommandLineOption + { + public CommandLineOptionType OptionType { get; set; } + public bool Mandatory { get; set; } = false; + public string Name { get; set; } = ""; + public string ShortName { get; set; } = ""; + public string Description { get; set; } = ""; + public int MinimumNumberOfValues { get; set; } = 1; + public int MaximumNumberOfValues { get; set; } = 1; + } +} diff --git a/src/BaseStationReader.Entities/Config/CommandLineOptionType.cs b/src/BaseStationReader.Entities/Config/CommandLineOptionType.cs new file mode 100644 index 0000000..5491fea --- /dev/null +++ b/src/BaseStationReader.Entities/Config/CommandLineOptionType.cs @@ -0,0 +1,15 @@ +namespace BaseStationReader.Entities.Config +{ + public enum CommandLineOptionType + { + Host, + Port, + TimeToRecent, + TimeToStale, + TimeToRemoval, + LogFile, + EnableSqlWriter, + WriterInterval, + WriterBatchSize + } +} diff --git a/src/BaseStationReader.Entities/Config/CommandLineOptionValue.cs b/src/BaseStationReader.Entities/Config/CommandLineOptionValue.cs new file mode 100644 index 0000000..6c0189e --- /dev/null +++ b/src/BaseStationReader.Entities/Config/CommandLineOptionValue.cs @@ -0,0 +1,11 @@ +using System.Diagnostics.CodeAnalysis; + +namespace BaseStationReader.Entities.Config +{ + [ExcludeFromCodeCoverage] + public class CommandLineOptionValue + { + public CommandLineOption? Option { get; set; } + public List Values { get; private set; } = new List(); + } +} diff --git a/src/BaseStationReader.Entities/Exceptions/MalformedCommandLineException.cs b/src/BaseStationReader.Entities/Exceptions/MalformedCommandLineException.cs new file mode 100644 index 0000000..e310056 --- /dev/null +++ b/src/BaseStationReader.Entities/Exceptions/MalformedCommandLineException.cs @@ -0,0 +1,31 @@ +using System.Diagnostics.CodeAnalysis; +using System.Runtime.Serialization; + +namespace BaseStationReader.Entities.Exceptions +{ + [Serializable] + [ExcludeFromCodeCoverage] + public class MalformedCommandLineException : Exception + { + public MalformedCommandLineException() + { + } + + public MalformedCommandLineException(string message) : base(message) + { + } + + public MalformedCommandLineException(string message, Exception inner) : base(message, inner) + { + } + + protected MalformedCommandLineException(SerializationInfo serializationInfo, StreamingContext streamingContext) : base(serializationInfo, streamingContext) + { + } + + public override void GetObjectData(SerializationInfo info, StreamingContext context) + { + base.GetObjectData(info, context); + } + } +} \ No newline at end of file diff --git a/src/BaseStationReader.Entities/Exceptions/MissingMandatoryOptionException.cs b/src/BaseStationReader.Entities/Exceptions/MissingMandatoryOptionException.cs new file mode 100644 index 0000000..372305f --- /dev/null +++ b/src/BaseStationReader.Entities/Exceptions/MissingMandatoryOptionException.cs @@ -0,0 +1,31 @@ +using System.Diagnostics.CodeAnalysis; +using System.Runtime.Serialization; + +namespace BaseStationReader.Entities.Exceptions +{ + [Serializable] + [ExcludeFromCodeCoverage] + public class MissingMandatoryOptionException : Exception + { + public MissingMandatoryOptionException() + { + } + + public MissingMandatoryOptionException(string message) : base(message) + { + } + + public MissingMandatoryOptionException(string message, Exception inner) : base(message, inner) + { + } + + protected MissingMandatoryOptionException(SerializationInfo serializationInfo, StreamingContext streamingContext) : base(serializationInfo, streamingContext) + { + } + + public override void GetObjectData(SerializationInfo info, StreamingContext context) + { + base.GetObjectData(info, context); + } + } +} \ No newline at end of file diff --git a/src/BaseStationReader.Entities/Exceptions/TooFewValuesException.cs b/src/BaseStationReader.Entities/Exceptions/TooFewValuesException.cs new file mode 100644 index 0000000..04e4895 --- /dev/null +++ b/src/BaseStationReader.Entities/Exceptions/TooFewValuesException.cs @@ -0,0 +1,31 @@ +using System.Diagnostics.CodeAnalysis; +using System.Runtime.Serialization; + +namespace BaseStationReader.Entities.Exceptions +{ + [Serializable] + [ExcludeFromCodeCoverage] + public class TooFewValuesException : Exception + { + public TooFewValuesException() + { + } + + public TooFewValuesException(string message) : base(message) + { + } + + public TooFewValuesException(string message, Exception inner) : base(message, inner) + { + } + + protected TooFewValuesException(SerializationInfo serializationInfo, StreamingContext streamingContext) : base(serializationInfo, streamingContext) + { + } + + public override void GetObjectData(SerializationInfo info, StreamingContext context) + { + base.GetObjectData(info, context); + } + } +} \ No newline at end of file diff --git a/src/BaseStationReader.Entities/Exceptions/TooManyValuesException.cs b/src/BaseStationReader.Entities/Exceptions/TooManyValuesException.cs new file mode 100644 index 0000000..48f6646 --- /dev/null +++ b/src/BaseStationReader.Entities/Exceptions/TooManyValuesException.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Runtime.Serialization; +using System.Text; +using System.Threading.Tasks; + +namespace BaseStationReader.Entities.Exceptions +{ + [Serializable] + [ExcludeFromCodeCoverage] + public class TooManyValuesException : Exception + { + public TooManyValuesException() + { + } + + public TooManyValuesException(string message) : base(message) + { + } + + public TooManyValuesException(string message, Exception inner) : base(message, inner) + { + } + + protected TooManyValuesException(SerializationInfo serializationInfo, StreamingContext streamingContext) : base(serializationInfo, streamingContext) + { + } + + public override void GetObjectData(SerializationInfo info, StreamingContext context) + { + base.GetObjectData(info, context); + } + } +} \ No newline at end of file diff --git a/src/BaseStationReader.Entities/Exceptions/UnrecognisedCommandLineOptionException.cs b/src/BaseStationReader.Entities/Exceptions/UnrecognisedCommandLineOptionException.cs new file mode 100644 index 0000000..9c7b800 --- /dev/null +++ b/src/BaseStationReader.Entities/Exceptions/UnrecognisedCommandLineOptionException.cs @@ -0,0 +1,31 @@ +using System.Diagnostics.CodeAnalysis; +using System.Runtime.Serialization; + +namespace BaseStationReader.Entities.Exceptions +{ + [Serializable] + [ExcludeFromCodeCoverage] + public class UnrecognisedCommandLineOptionException : Exception + { + public UnrecognisedCommandLineOptionException() + { + } + + public UnrecognisedCommandLineOptionException(string message) : base(message) + { + } + + public UnrecognisedCommandLineOptionException(string message, Exception inner) : base(message, inner) + { + } + + protected UnrecognisedCommandLineOptionException(SerializationInfo serializationInfo, StreamingContext streamingContext) : base(serializationInfo, streamingContext) + { + } + + public override void GetObjectData(SerializationInfo info, StreamingContext context) + { + base.GetObjectData(info, context); + } + } +} diff --git a/src/BaseStationReader.Logic/BaseStationReader.Logic.csproj b/src/BaseStationReader.Logic/BaseStationReader.Logic.csproj index 30271d6..fb23901 100644 --- a/src/BaseStationReader.Logic/BaseStationReader.Logic.csproj +++ b/src/BaseStationReader.Logic/BaseStationReader.Logic.csproj @@ -5,7 +5,7 @@ enable enable BaseStationReader.Logic - 1.12.0.0 + 1.13.0.0 Dave Walker Copyright (c) Dave Walker 2023 Dave Walker @@ -17,7 +17,7 @@ https://github.com/davewalker5/ADS-B-BaseStationReader MIT false - 1.12.0.0 + 1.13.0.0 diff --git a/src/BaseStationReader.Logic/CommandLineParser.cs b/src/BaseStationReader.Logic/CommandLineParser.cs new file mode 100644 index 0000000..28eb807 --- /dev/null +++ b/src/BaseStationReader.Logic/CommandLineParser.cs @@ -0,0 +1,183 @@ +using BaseStationReader.Entities.Config; +using BaseStationReader.Entities.Exceptions; + +namespace BaseStationReader.Logic +{ + public class CommandLineParser + { + private readonly List _options = new List(); + private readonly Dictionary _values = new Dictionary(); + + /// + /// Add an option to the available command line options + /// + /// + /// + /// + /// + /// + /// + /// + public void Add(CommandLineOptionType optionType, bool mandatory, string name, string shortName, string description, int minimumNumberOfValues, int maximumNumberOfValues) + { + _options.Add(new CommandLineOption + { + OptionType = optionType, + Mandatory = mandatory, + Name = name, + ShortName = shortName, + Description = description, + MinimumNumberOfValues = minimumNumberOfValues, + MaximumNumberOfValues = maximumNumberOfValues + }); + } + + /// + /// Parse a command line supplied as an enumerable list of strings + /// + /// + /// + public void Parse(IEnumerable args) + { + // Perform the intial parsing of the command line + BuildValueList(args); + + // Check that all arguments have the required number of values + CheckForMinimumValues(); + + // Check that all mandatory arguments have been supplied + CheckForMandatoryOptions(); + } + + /// + /// Return the valus for the specified option type + /// + /// + /// + public List? GetValues(CommandLineOptionType optionType) + { + List? values = null; + + if (_values.ContainsKey(optionType)) + { + values = _values[optionType].Values; + } + + return values; + } + + /// + /// Check that all mandatory options have been specified + /// + /// + private void CheckForMandatoryOptions() + { + foreach (var option in _options.Where(x => x.Mandatory)) + { + if (!_values.ContainsKey(option.OptionType)) + { + var message = $"Missing mandatory option '{option.Name}"; + throw new MissingMandatoryOptionException(message); + } + } + } + + /// + /// Check that each supplied option has sufficient values with it + /// + /// + private void CheckForMinimumValues() + { + foreach (var value in _values.Values) + { + if (value.Values.Count < value.Option?.MinimumNumberOfValues) + { + var message = $"Too few values supplied for '{value.Option.Name}': Expected {value.Option.MinimumNumberOfValues}, got {value.Values.Count}"; + throw new TooFewValuesException(message); + } + } + } + + /// + /// Build the value list from the command line + /// + /// + /// + /// + private void BuildValueList(IEnumerable args) + { + CommandLineOptionValue? current = null; + + // Iterate over the command line arguments extracting options and associated values + foreach (string arg in args) + { + if (!string.IsNullOrEmpty(arg)) + { + if (arg.StartsWith("--")) + { + // Starts with "--" so this is the full name of an option. Create a new value + current = new CommandLineOptionValue + { + Option = FindOption(arg, true) + }; + + // Add the value to the list of all values + _values.Add(current.Option.OptionType, current); + } + else if (arg.StartsWith("-")) + { + // Starts with "-" so this is the short name of an option. Create a new value + current = new CommandLineOptionValue + { + Option = FindOption(arg, false) + }; + + // Add the value to the list of all values + _values.Add(current.Option.OptionType, current); + } + else if (current != null) + { + // No prefix but we have a current option so add this to the values for it, check that + // this doesn't exceed the maximum number of values for that option and raise an exception + // if it does + current.Values.Add(arg); + if (current.Values.Count > current.Option?.MaximumNumberOfValues) + { + var message = $"Too many values for '{current.Option.Name}' at '{arg}'"; + throw new TooManyValuesException(message); + } + } + else + { + // Doesn't start with a prefix indicating this is the start of a new option and + // we don't have a current option - malformed command line + var message = $"Malformed command line at '{arg}'"; + throw new MalformedCommandLineException(message); + } + } + } + } + + /// + /// Find an argument by name or short name + /// + /// + /// + /// + private CommandLineOption FindOption(string argument, bool byName) + { + // Look for the argument in the available options and, if found, return it + foreach (var option in _options) + { + if ((byName && option.Name.Equals(argument)) || (!byName && option.ShortName.Equals(argument))) + { + return option; + } + } + + // Not found, so raise an exception + var message = $"Unrecognised command line option {argument}"; + throw new UnrecognisedCommandLineOptionException(message); + } + } +} diff --git a/src/BaseStationReader.Terminal/BaseStationReader.Terminal.csproj b/src/BaseStationReader.Terminal/BaseStationReader.Terminal.csproj index 2b201cd..15f1906 100644 --- a/src/BaseStationReader.Terminal/BaseStationReader.Terminal.csproj +++ b/src/BaseStationReader.Terminal/BaseStationReader.Terminal.csproj @@ -3,9 +3,9 @@ Exe net7.0 - 1.12.0.0 - 1.12.0.0 - 1.12.0 + 1.13.0.0 + 1.13.0.0 + 1.13.0 enable enable diff --git a/src/BaseStationReader.Terminal/Program.cs b/src/BaseStationReader.Terminal/Program.cs index 75c4597..8f62254 100644 --- a/src/BaseStationReader.Terminal/Program.cs +++ b/src/BaseStationReader.Terminal/Program.cs @@ -21,7 +21,7 @@ public class Program public static async Task Main(string[] args) { // Read the application config - ApplicationSettings? settings = new ConfigReader().Read("appsettings.json"); + ApplicationSettings? settings = BuildSettings(args); // Configure the log file #pragma warning disable CS8602 @@ -65,6 +65,60 @@ await AnsiConsole.Live(_table) }); } + /// + /// Construct the application settings from the configuration file and any command line arguments + /// + /// + /// + private static ApplicationSettings? BuildSettings(IEnumerable args) + { + // Read the config file to provide default settings + var settings = new ConfigReader().Read("appsettings.json"); + + // Parse the command line + var parser = new CommandLineParser(); + parser.Add(CommandLineOptionType.Host, false, "--host", "-h", "Host to connect to for data stream", 1, 1); + parser.Add(CommandLineOptionType.Port, false, "--port", "-p", "Port to connect to for data stream", 1, 1); + parser.Add(CommandLineOptionType.TimeToRecent, false, "--recent", "-r", "Time (ms) to 'recent' staleness", 1, 1); + parser.Add(CommandLineOptionType.TimeToStale, false, "--stale", "-s", "Time (ms) to 'stale' staleness", 1, 1); + parser.Add(CommandLineOptionType.TimeToRemoval, false, "--remove", "-x", "Time (ms) removal of stale records", 1, 1); + parser.Add(CommandLineOptionType.LogFile, false, "--log-file", "-l", "Log file path and name", 1, 1); + parser.Add(CommandLineOptionType.EnableSqlWriter, false, "--enable-sql-writer", "-w", "Log file path and name", 1, 1); + parser.Add(CommandLineOptionType.WriterInterval, false, "--writer-interval", "-i", "SQL write interval (ms)", 1, 1); + parser.Add(CommandLineOptionType.WriterBatchSize, false, "--writer-batch-size", "-b", "SQL write batch size", 1, 1); + parser.Parse(args); + + // Apply the command line values over the defaults + var values = parser.GetValues(CommandLineOptionType.Host); + if (values != null) settings!.Host = values.First(); + + values = parser.GetValues(CommandLineOptionType.Port); + if (values != null) settings!.Port = int.Parse(values.First()); + + values = parser.GetValues(CommandLineOptionType.TimeToRecent); + if (values != null) settings!.TimeToRecent = int.Parse(values.First()); + + values = parser.GetValues(CommandLineOptionType.TimeToStale); + if (values != null) settings!.TimeToStale = int.Parse(values.First()); + + values = parser.GetValues(CommandLineOptionType.TimeToRemoval); + if (values != null) settings!.TimeToRemoval = int.Parse(values.First()); + + values = parser.GetValues(CommandLineOptionType.LogFile); + if (values != null) settings!.LogFile = values.First(); + + values = parser.GetValues(CommandLineOptionType.EnableSqlWriter); + if (values != null) settings!.EnableSqlWriter = bool.Parse(values.First()); + + values = parser.GetValues(CommandLineOptionType.WriterInterval); + if (values != null) settings!.WriterInterval = int.Parse(values.First()); + + values = parser.GetValues(CommandLineOptionType.WriterBatchSize); + if (values != null) settings!.WriterBatchSize = int.Parse(values.First()); + + return settings; + } + /// /// Display and continuously update the tracking table /// diff --git a/src/BaseStationReader.Tests/CommandLineParserTest.cs b/src/BaseStationReader.Tests/CommandLineParserTest.cs new file mode 100644 index 0000000..eeb2916 --- /dev/null +++ b/src/BaseStationReader.Tests/CommandLineParserTest.cs @@ -0,0 +1,104 @@ +using BaseStationReader.Entities.Config; +using BaseStationReader.Entities.Exceptions; +using BaseStationReader.Logic; +using System.Diagnostics.CodeAnalysis; + +namespace BaseStationReader.Tests +{ + [ExcludeFromCodeCoverage] + [TestClass] + public class CommandLineParserTest + { + private CommandLineParser? _parser; + + [TestInitialize] + public void TestInitialise() + { + _parser = new CommandLineParser(); + _parser.Add(CommandLineOptionType.Host, true, "--host", "-h", "Host to connect to for data stream", 1, 1); + _parser.Add(CommandLineOptionType.Port, false, "--port", "-p", "Port to connect to for data stream", 1, 1); + } + + [TestMethod] + public void ValidUsingNamesTest() + { + string[] args = new string[]{ "--host", "192.168.0.98", "--port", "30003" }; + _parser?.Parse(args); + + var values = _parser?.GetValues(CommandLineOptionType.Host); + Assert.IsNotNull(values); + Assert.AreEqual(1, values.Count); + Assert.AreEqual("192.168.0.98", values.First()); + + values = _parser?.GetValues(CommandLineOptionType.Port); + Assert.IsNotNull(values); + Assert.AreEqual(1, values.Count); + Assert.AreEqual("30003", values.First()); + } + + [TestMethod] + public void ValidUsingShortNamesTest() + { + string[] args = new string[] { "-h", "192.168.0.98", "-p", "30003" }; + _parser?.Parse(args); + + var values = _parser?.GetValues(CommandLineOptionType.Host); + Assert.IsNotNull(values); + Assert.AreEqual(1, values.Count); + Assert.AreEqual("192.168.0.98", values.First()); + + values = _parser?.GetValues(CommandLineOptionType.Port); + Assert.IsNotNull(values); + Assert.AreEqual(1, values.Count); + Assert.AreEqual("30003", values.First()); + } + + [TestMethod] + [ExpectedException(typeof(MissingMandatoryOptionException))] + public void MissingMandatoryFailsTest() + { + string[] args = new string[] { "-p", "30003" }; + _parser?.Parse(args); + } + + [TestMethod] + [ExpectedException(typeof(TooFewValuesException))] + public void TooFewArgumentsFailsTest() + { + string[] args = new string[] { "-h", "-p", "30003" }; + _parser?.Parse(args); + } + + [TestMethod] + [ExpectedException(typeof(TooManyValuesException))] + public void TooManyArgumentsFailsTest() + { + string[] args = new string[] { "-h", "192.168.0.98", "127.0.0.1", "-p", "30003" }; + _parser?.Parse(args); + } + + [TestMethod] + [ExpectedException(typeof(UnrecognisedCommandLineOptionException))] + public void UnrecognisedOptionNameFailsTest() + { + string[] args = new string[] { "--oops", "192.168.0.98", "-p", "30003" }; + _parser?.Parse(args); + } + + [TestMethod] + [ExpectedException(typeof(UnrecognisedCommandLineOptionException))] + public void UnrecognisedOptionShortNameFailsTest() + { + string[] args = new string[] { "-o", "192.168.0.98", "-p", "30003" }; + _parser?.Parse(args); + } + + [TestMethod] + [ExpectedException(typeof(MalformedCommandLineException))] + public void MalformedCommandLineFailsTest() + { + string[] args = new string[] { "192.168.0.98", "-p", "30003" }; + _parser?.Parse(args); + } + } +} From 1b9e6c8d291e2a80aa2fe6203634cd60df75bf15 Mon Sep 17 00:00:00 2001 From: Dave Walker Date: Sun, 27 Aug 2023 11:26:32 +0100 Subject: [PATCH 2/3] Exclude main program from coverage --- src/BaseStationReader.Terminal/Program.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/BaseStationReader.Terminal/Program.cs b/src/BaseStationReader.Terminal/Program.cs index 8f62254..e836a66 100644 --- a/src/BaseStationReader.Terminal/Program.cs +++ b/src/BaseStationReader.Terminal/Program.cs @@ -8,10 +8,12 @@ using Serilog; using Spectre.Console; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Reflection; namespace BaseStationReader.Terminal { + [ExcludeFromCodeCoverage] public class Program { private readonly static Table _table = new Table().Expand().BorderColor(Spectre.Console.Color.Grey); From dccfc701ffcb8c98137aafc10e7bd974dfa415b9 Mon Sep 17 00:00:00 2001 From: Dave Walker Date: Sun, 27 Aug 2023 11:56:23 +0100 Subject: [PATCH 3/3] SonarCloud comments --- src/BaseStationReader.Logic/CommandLineParser.cs | 2 +- src/BaseStationReader.Logic/ConfigReader.cs | 4 ++-- src/BaseStationReader.Terminal/Program.cs | 16 ++++++++-------- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/BaseStationReader.Logic/CommandLineParser.cs b/src/BaseStationReader.Logic/CommandLineParser.cs index 28eb807..287e130 100644 --- a/src/BaseStationReader.Logic/CommandLineParser.cs +++ b/src/BaseStationReader.Logic/CommandLineParser.cs @@ -124,7 +124,7 @@ private void BuildValueList(IEnumerable args) // Add the value to the list of all values _values.Add(current.Option.OptionType, current); } - else if (arg.StartsWith("-")) + else if (arg.StartsWith('-')) { // Starts with "-" so this is the short name of an option. Create a new value current = new CommandLineOptionValue diff --git a/src/BaseStationReader.Logic/ConfigReader.cs b/src/BaseStationReader.Logic/ConfigReader.cs index 371894d..5a19997 100644 --- a/src/BaseStationReader.Logic/ConfigReader.cs +++ b/src/BaseStationReader.Logic/ConfigReader.cs @@ -5,13 +5,13 @@ namespace BaseStationReader.Logic { [ExcludeFromCodeCoverage] - public class ConfigReader + public static class ConfigReader { /// /// Load and return the application settings from the named JSON-format application settings file /// /// - public ApplicationSettings? Read(string jsonFileName) + public static ApplicationSettings? Read(string jsonFileName) { IConfiguration configuration = new ConfigurationBuilder() .AddJsonFile(jsonFileName) diff --git a/src/BaseStationReader.Terminal/Program.cs b/src/BaseStationReader.Terminal/Program.cs index e836a66..fc42043 100644 --- a/src/BaseStationReader.Terminal/Program.cs +++ b/src/BaseStationReader.Terminal/Program.cs @@ -75,7 +75,7 @@ await AnsiConsole.Live(_table) private static ApplicationSettings? BuildSettings(IEnumerable args) { // Read the config file to provide default settings - var settings = new ConfigReader().Read("appsettings.json"); + var settings = ConfigReader.Read("appsettings.json"); // Parse the command line var parser = new CommandLineParser(); @@ -95,28 +95,28 @@ await AnsiConsole.Live(_table) if (values != null) settings!.Host = values.First(); values = parser.GetValues(CommandLineOptionType.Port); - if (values != null) settings!.Port = int.Parse(values.First()); + if (values != null) settings!.Port = int.Parse(values[0]); values = parser.GetValues(CommandLineOptionType.TimeToRecent); - if (values != null) settings!.TimeToRecent = int.Parse(values.First()); + if (values != null) settings!.TimeToRecent = int.Parse(values[0]); values = parser.GetValues(CommandLineOptionType.TimeToStale); - if (values != null) settings!.TimeToStale = int.Parse(values.First()); + if (values != null) settings!.TimeToStale = int.Parse(values[0]); values = parser.GetValues(CommandLineOptionType.TimeToRemoval); - if (values != null) settings!.TimeToRemoval = int.Parse(values.First()); + if (values != null) settings!.TimeToRemoval = int.Parse(values[0]); values = parser.GetValues(CommandLineOptionType.LogFile); if (values != null) settings!.LogFile = values.First(); values = parser.GetValues(CommandLineOptionType.EnableSqlWriter); - if (values != null) settings!.EnableSqlWriter = bool.Parse(values.First()); + if (values != null) settings!.EnableSqlWriter = bool.Parse(values[0]); values = parser.GetValues(CommandLineOptionType.WriterInterval); - if (values != null) settings!.WriterInterval = int.Parse(values.First()); + if (values != null) settings!.WriterInterval = int.Parse(values[0]); values = parser.GetValues(CommandLineOptionType.WriterBatchSize); - if (values != null) settings!.WriterBatchSize = int.Parse(values.First()); + if (values != null) settings!.WriterBatchSize = int.Parse(values[0]); return settings; }