using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace StardewModdingAPI.Framework
{
/// Manages console commands.
internal class CommandManager
{
/*********
** Properties
*********/
/// The commands registered with SMAPI.
private readonly IDictionary Commands = new Dictionary(StringComparer.InvariantCultureIgnoreCase);
/*********
** Public methods
*********/
/// Add a console command.
/// The mod adding the command (or null for a SMAPI command).
/// The command name, which the user must type to trigger it.
/// The human-readable documentation shown when the player runs the built-in 'help' command.
/// The method to invoke when the command is triggered. This method is passed the command name and arguments submitted by the user.
/// Whether to allow a null argument; this should only used for backwards compatibility.
/// The or is null or empty.
/// The is not a valid format.
/// There's already a command with that name.
public void Add(IModMetadata mod, string name, string documentation, Action callback, bool allowNullCallback = false)
{
name = this.GetNormalisedName(name);
// validate format
if (string.IsNullOrWhiteSpace(name))
throw new ArgumentNullException(nameof(name), "Can't register a command with no name.");
if (name.Any(char.IsWhiteSpace))
throw new FormatException($"Can't register the '{name}' command because the name can't contain whitespace.");
if (callback == null && !allowNullCallback)
throw new ArgumentNullException(nameof(callback), $"Can't register the '{name}' command because without a callback.");
// ensure uniqueness
if (this.Commands.ContainsKey(name))
throw new ArgumentException(nameof(callback), $"Can't register the '{name}' command because there's already a command with that name.");
// add command
this.Commands.Add(name, new Command(mod, name, documentation, callback));
}
/// Get a command by its unique name.
/// The command name.
/// Returns the matching command, or null if not found.
public Command Get(string name)
{
name = this.GetNormalisedName(name);
this.Commands.TryGetValue(name, out Command command);
return command;
}
/// Get all registered commands.
public IEnumerable GetAll()
{
return this.Commands
.Values
.OrderBy(p => p.Name);
}
/// Try to parse a raw line of user input into an executable command.
/// The raw user input.
/// The parsed command name.
/// The parsed command arguments.
/// The command which can handle the input.
/// Returns true if the input was successfully parsed and matched to a command; else false.
public bool TryParse(string input, out string name, out string[] args, out Command command)
{
// ignore if blank
if (string.IsNullOrWhiteSpace(input))
{
name = null;
args = null;
command = null;
return false;
}
// parse input
args = this.ParseArgs(input);
name = this.GetNormalisedName(args[0]);
args = args.Skip(1).ToArray();
// get command
return this.Commands.TryGetValue(name, out command);
}
/// Trigger a command.
/// The command name.
/// The command arguments.
/// Returns whether a matching command was triggered.
public bool Trigger(string name, string[] arguments)
{
// get normalised name
name = this.GetNormalisedName(name);
if (name == null)
return false;
// get command
if (this.Commands.TryGetValue(name, out Command command))
{
command.Callback.Invoke(name, arguments);
return true;
}
return false;
}
/*********
** Private methods
*********/
/// Parse a string into command arguments.
/// The string to parse.
private string[] ParseArgs(string input)
{
bool inQuotes = false;
IList args = new List();
StringBuilder currentArg = new StringBuilder();
foreach (char ch in input)
{
if (ch == '"')
inQuotes = !inQuotes;
else if (!inQuotes && char.IsWhiteSpace(ch))
{
args.Add(currentArg.ToString());
currentArg.Clear();
}
else
currentArg.Append(ch);
}
args.Add(currentArg.ToString());
return args.Where(item => !string.IsNullOrWhiteSpace(item)).ToArray();
}
/// Get a normalised command name.
/// The command name.
private string GetNormalisedName(string name)
{
name = name?.Trim().ToLower();
return !string.IsNullOrWhiteSpace(name)
? name
: null;
}
}
}