diff options
Diffstat (limited to 'src/TrainerMod/Framework/Commands/ArgumentParser.cs')
-rw-r--r-- | src/TrainerMod/Framework/Commands/ArgumentParser.cs | 159 |
1 files changed, 159 insertions, 0 deletions
diff --git a/src/TrainerMod/Framework/Commands/ArgumentParser.cs b/src/TrainerMod/Framework/Commands/ArgumentParser.cs new file mode 100644 index 00000000..6bcd3ff8 --- /dev/null +++ b/src/TrainerMod/Framework/Commands/ArgumentParser.cs @@ -0,0 +1,159 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using StardewModdingAPI; + +namespace TrainerMod.Framework.Commands +{ + /// <summary>Provides methods for parsing command-line arguments.</summary> + internal class ArgumentParser : IReadOnlyList<string> + { + /********* + ** Properties + *********/ + /// <summary>The command name for errors.</summary> + private readonly string CommandName; + + /// <summary>The arguments to parse.</summary> + private readonly string[] Args; + + /// <summary>Writes messages to the console and log file.</summary> + private readonly IMonitor Monitor; + + + /********* + ** Accessors + *********/ + /// <summary>Get the number of arguments.</summary> + public int Count => this.Args.Length; + + /// <summary>Get the argument at the specified index in the list.</summary> + /// <param name="index">The zero-based index of the element to get.</param> + public string this[int index] => this.Args[index]; + + /// <summary>A method which parses a string argument into the given value.</summary> + /// <typeparam name="T">The expected argument type.</typeparam> + /// <param name="input">The argument to parse.</param> + /// <param name="output">The parsed value.</param> + /// <returns>Returns whether the argument was successfully parsed.</returns> + public delegate bool ParseDelegate<T>(string input, out T output); + + + /********* + ** Public methods + *********/ + /// <summary>Construct an instance.</summary> + /// <param name="commandName">The command name for errors.</param> + /// <param name="args">The arguments to parse.</param> + /// <param name="monitor">Writes messages to the console and log file.</param> + public ArgumentParser(string commandName, string[] args, IMonitor monitor) + { + this.CommandName = commandName; + this.Args = args; + this.Monitor = monitor; + } + + /// <summary>Try to read a string argument.</summary> + /// <param name="index">The argument index.</param> + /// <param name="name">The argument name for error messages.</param> + /// <param name="value">The parsed value.</param> + /// <param name="required">Whether to show an error if the argument is missing.</param> + /// <param name="oneOf">Require that the argument match one of the given values (case-insensitive).</param> + public bool TryGet(int index, string name, out string value, bool required = true, string[] oneOf = null) + { + value = null; + + // validate + if (this.Args.Length < index + 1) + { + if (required) + this.LogError($"Argument {index} ({name}) is required."); + return false; + } + if (oneOf?.Any() == true && !oneOf.Contains(this.Args[index], StringComparer.InvariantCultureIgnoreCase)) + { + this.LogError($"Argument {index} ({name}) must be one of {string.Join(", ", oneOf)}."); + return false; + } + + // get value + value = this.Args[index]; + return true; + } + + /// <summary>Try to read an integer argument.</summary> + /// <param name="index">The argument index.</param> + /// <param name="name">The argument name for error messages.</param> + /// <param name="value">The parsed value.</param> + /// <param name="required">Whether to show an error if the argument is missing.</param> + /// <param name="min">The minimum value allowed.</param> + /// <param name="max">The maximum value allowed.</param> + public bool TryGetInt(int index, string name, out int value, bool required = true, int? min = null, int? max = null) + { + value = 0; + + // get argument + if (!this.TryGet(index, name, out string raw, required)) + return false; + + // parse + if (!int.TryParse(raw, out value)) + { + this.LogIntFormatError(index, name, min, max); + return false; + } + + // validate + if ((min.HasValue && value < min) || (max.HasValue && value > max)) + { + this.LogIntFormatError(index, name, min, max); + return false; + } + + return true; + } + + /// <summary>Returns an enumerator that iterates through the collection.</summary> + /// <returns>An enumerator that can be used to iterate through the collection.</returns> + public IEnumerator<string> GetEnumerator() + { + return ((IEnumerable<string>)this.Args).GetEnumerator(); + } + + /// <summary>Returns an enumerator that iterates through a collection.</summary> + /// <returns>An <see cref="T:System.Collections.IEnumerator" /> object that can be used to iterate through the collection.</returns> + IEnumerator IEnumerable.GetEnumerator() + { + return this.GetEnumerator(); + } + + + /********* + ** Private methods + *********/ + /// <summary>Log a usage error.</summary> + /// <param name="message">The message describing the error.</param> + private void LogError(string message) + { + this.Monitor.Log($"{message} Type 'help {this.CommandName}' for usage.", LogLevel.Error); + } + + /// <summary>Print an error for an invalid int argument.</summary> + /// <param name="index">The argument index.</param> + /// <param name="name">The argument name for error messages.</param> + /// <param name="min">The minimum value allowed.</param> + /// <param name="max">The maximum value allowed.</param> + private void LogIntFormatError(int index, string name, int? min, int? max) + { + if (min.HasValue && max.HasValue) + this.LogError($"Argument {index} ({name}) must be an integer between {min} and {max}."); + else if (min.HasValue) + this.LogError($"Argument {index} ({name}) must be an integer and at least {min}."); + else if (max.HasValue) + this.LogError($"Argument {index} ({name}) must be an integer and at most {max}."); + else + this.LogError($"Argument {index} ({name}) must be an integer."); + } + } +} |