summaryrefslogtreecommitdiff
path: root/src/TrainerMod/Framework/Commands/ArgumentParser.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/TrainerMod/Framework/Commands/ArgumentParser.cs')
-rw-r--r--src/TrainerMod/Framework/Commands/ArgumentParser.cs159
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.");
+ }
+ }
+}