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."); } } }